SPS mit ATmega

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

von andreasr

Viele Steuerungsaufgaben in der Hausautomation können mit einfachen SPS-Funktionen gelöst werden (Treppenhaus-Automat, Tast-Funktion, Und-/Oder-/Xor-/Not-Funktionen, Flankenerkennung, Selbsthaltung, Reaktion auf Zählerstände, …). Der Nachteil einer käuflichen SPS ist der hohe Preis. Auf der anderen Seite kann ein aktueller Mikrocontroller die SPS-Funktionen spielend leicht abbilden. Nachteil ist hier aber die komplizierte Programmierung bzw. die Pflege des Programms. C und C++ sind nicht unbedingt die erste Wahl für die Programmierung logischer Ablaufsequenzen. Was liegt näher als mit einen Mikrocontroller eine SPS zu implementieren, die sich in einer AWL-ähnlichen Sprache programmieren lässt?

Features

  • 12 V oder 24 V Betriebsspannung
  • Platinengröße halbes Euro-Format
  • 8 Eingänge, Kontroll-LED's
  • 8 Ausgänge, 500mA, direktes Schalten induktiver Lasten, Kontroll-LED's
  • möglichst preiswerte und einfach zu beschaffende Komponenten
  • optinale Zusatzmodule anschließbar, z.B. LCD, Taster, RFM12, DCF77 - Empfänger
  • optionale Softwaremodule realiserbar, z.B. Zeitschaltuhr, smarthomatic Power Switch Implementation

Theory of Operation

Die Abarbeitung eines SPS-Programmes erfolgt innerhalb von sogenannten Zyklen. Das sind feste Zeitabschnitte, in denen das gesamte Programm durchlaufen wird und aus Eingangssignalen Ausgangssignale kombiniert (berechnet) werden. Diese Zeitabschnitte sollten möglichst kurz sein, da sie die minimale Reaktionszeit des SPS-Programms bestimmen. Da mit einer SPS üblicherweise kontaktbehaftete Steuerungen nachgebildet werden, ist eine Zykluszeit von 10-100 ms meistens ausreichend. Das SPS-Programm sollte für den Mikrocontroller in einem Format vorliegen, dass er leicht einlesen und interpretieren kann; ähnlich einem Maschinenprogramm für die CPU. Um die Komplexität gering zu halten, besteht jeder Programm-Befehl aus 2 Bytes; einem Opcode und einer Adresse. Als Ergebnis-Zwischenspeicher wird ein Akkumulator-Register benutzt. Operationen, die 2 Operanden verarbeiten benutzen als zweiten Operanden ebenfalls den Akkumulator. Alle Operationen werden auf Bit-Ebene durchgeführt. Der Akkumulator ist ebenfalls 1 Bit groß.

Hardware

Als Mikrocontroller wird ein ATmega328 verwendet. Um genügend Eingänge und Ausgänge zur Verfügung zu haben wird eine Porterweiterung verwendet. Die Ansteuerung der Schieberegister erfolgt per Software-SPI. Das SPS-Programm wird im EEProm des Mikrocontrollers abgelegt und zur Laufzeit in das RAM geladen. Um Zusatzfunktionalitäten zu realisieren, werden einige Port-Pins des Mikrocontrollers auf einer Stiftleiste herausgeführt. Die Spannung für den Mikrocontroller und die Peripherie wird über einen LM2574 erzeugt. Es kann - je nach verwendeter Peripherie - die 3,3 V oder die 5 V – Variante verwendet werden. Falls z.B ein RFM12 angeschlossen werden soll empfiehlt sich die 3,3 V Variante; falls ein LCD angeschlossen werden soll die 5 V – Variante. Der ATmega selbst hat einen Spannungsbereich von ca. 3 V – 5 V falls man mit der Taktfrequenz nicht über 8 MHz geht. Die Versorgungsspannung kann sich in einem weiten Bereich bewegen und wird im Wesentlichen durch die max. Eingangsspannung des LM2574 begrenzt - 45 V. Es müssen nur die Vorwiderstände für die Optokoppler angepasst werden, das der Strom durch die LED's ungefähr 10 mA beträgt; also etwa 1,2 kOhm für 12 V und 2,2 kOhm für 24 V. Die Eingangsbeschaltung für einen einzelnen Eingang sieht so aus:

Eingang.png

Die Ausgänge werden über einen Highside-Treiber vom Typ UDN2981 angesteuert. Der Treiber besitzt TTL-kompatible Eingänge und kann bis zu 500mA schalten. Freilaufdioden für induktive Lasten sind integriert.

Optional können 2 Eingänge über Optokoppler isoliert mit Netzspannung angesteuert werden. Es wird ein kapazitiver Spannungsteiler verwendet. Die Entprellung muss mit einer entsprechenden Zeitkonstante ausgeführt werden, um die 100 Hz-Frequenz zu eliminieren.

Eingang230 neu.png

Sicherheitshinweis

Beim Arbeiten mit Netzspannung sind die einschlägigen Sicherheitsvorschriften bezüglich Berührungsschutz, Isolationsabstand, Kriechstrecken etc. einzuhalten.
Kondensatoren, die mit Netzspannung beaufschlagt werden müssen X2-Typen sein!

Software

Die eigentliche Befehlsausführung ist in der Funktion

void SPSExec(const uint8_t* prog)

implementiert.

Es werden immer 2 Bytes gelesen: Opcode und Adresse. Zunächst wird der Opcode aufgespalten in Operand und in Attribute.

Opcode
7 6 5 4 3 2 1 0
attrSubBlock attrNot Operand Operand Operand Operand Operand Operand


Die 6 Bits Operand werden als Zahl aufgefasst und durch folgenden enum beschrieben:

enum Operands	{
	opEnd, 
	opStore,
	opSet,
	opReset,
	opLoad,
	opAND,
	opOR,
	opXOR,
	opFP,       // Flanke Positiv
	opFN,       // Flanke Negativ

	opSE,       // Einschaltverz.
	opSA,       // Ausschaltverz.
	
	opSC,       // Set Counter
	opRC,       // Reset Counter
};

Die Operanden opLoad, opAND, opOR, opXOR können mit dem Attribute attrSubBlock kombiniert werden. Das führt dazu, dass die folgenden Berechnungen bis zum Operanden opEnd als Teilausdruck aufgefasst werden, der anschließend mit dem aktuellen Ergebnis kombiniert wird (Klammerausdruck). Bei der Berechnung von Teilausdrücken werden zuerst der aktuelle Operand und der Akkumulator-Inhalt auf den Stack geschoben und dann der Teilausdruck (Klammer) berechnet. Danach werden Operand und alter Akkumulator-Inhalt vom Stack zurückgeholt und mit dem Teilausdrucks-Ergebnis kombiniert. Das ganze funktioniert durch die Stack-Architektur auch rekursiv.

Danach wird das Address-Byte ausgewertet. Das Address-Byte codiert das Device, einen Byte-Index und die Bit-Nummer:

Address
7 6 5 4 3 2 1 0
Device Device Device #Byte #Byte #Bit #Bit #Bit


Die 3 Bits Device werden als Zahl aufgefasst und durch folgenden enum beschrieben:

enum AdressTypes	{
	adrIn =          0<<5,
	adrOut =         1<<5,
	adrMerker =      2<<5,
	adrSHC =         3<<5,
	adrClockSwitch = 4<<5,
	adrTimer =       5<<5,
	adrCounter =     6<<5,
	
	adrInvalid =     7<<5
};

Mit den 2 Bits #Byte können 4 aufeinanderfolgende Bytes adressiert werden. Mit den 3 Bits #Bit wird das Bit im Jeweiligen Byte adressiert.

Der Ausgang #11 hätte also folgende Adresse adrOut + Byte 1 + Bit 2 = (1 << 5) + (1 << 3) + 2

Schreiben des SPS-Programmes

Die Programmierung soll möglichst einfach sein aber auch intuitiv. Das wird erreicht durch eine Kombination von bestimmten Operanden- und Adress-Makros. In Wirklichkeit ist das Programm eine Deklaration eines Byte-Arrays was im EEProm abgelegt wird. Zum Einspielen eines neuen SPS-Programmes reicht es also, das EEProm des ATmega neu zu beschreiben
(= Speicherprogrammierbare Steuerung)

Beispiele

Q1 = I1 AND (I2 OR NOT I3)

#define I1		IN(0)
#define I2		IN(1)
#define I3		IN(2)
#define Q1		OUT(0)

uint8_t EEDataSPSCode[] EEMEM= {
	LD, 	I1,
	AND+KLAUF, 0,	// (
		LD, I2,
		OR+NOT, I3,
	KLZU, 0, 	// )
	ST,	Q1,
	EXIT,
};

Hier ist zu beachten, dass nach den Befehlen AND+KLAUF und KLZU jeweils ein Dummy-Adress Byte (0) eingefügt wird.

Selbsthaltung: I1 = Q1 Ein, !I2 = Q1 Aus

#define I1		IN(0)
#define I2		IN(1)
#define Q1		OUT(0)

uint8_t EEDataSPSCode[] EEMEM= {
	LD, 	I1,
	S,	Q1,	
	LD,	NOT+I2
	R,	Q1,		// dominierend Aus
	EXIT,
};

Flankenerkennung: steigende Flanke an I1 setzt Q1

#define I1		IN(0)
#define Q1		OUT(0)
#define Merk1		M(1)

uint8_t EEDataSPSCode[] EEMEM= {
	LD,			I1,
	FP,			Merk1,		// Flanke Positiv; braucht Hilfs-Merker
	S,			Q1,
	EXIT,
};

Der Compiler legt das Array EEDataSPSCode als EEProm-File ab.

Timer

In der klassischen Steuerungstechnik ist das anzugsverzögerte und das abfallverzögerte Zeitrelais bekannt. In der SPS sind 8 (#define MAX_T 8) Timer vorhanden, die wahlweise als Einschalt- oder als Ausschaltverzögerung benutzt werden können.
Das Verhalten wird durch die Operation bestimmt: opSE programmiert eine Einschaltverzögerung; opSA eine Ausschaltverzögerung.
Die Zeit für die Timer wird als Vorspann zum Programm ebenfalls im EEProm abgelegt (EEDataSPSTTimer).

Beispiel: Setze Ausgang Q1 8 sec. nachdem Eingang I1 high wurde:

uint16_t EEDataSPSTTimer[MAX_T] EEMEM = {
    8*(1000/PRE_TIMER),    // T1 8s
    ...
		
#define T1 T(0)
#define I1 IN(0)
#define Q1 OUT(0)

uint8_t EEDataSPSCode[] EEMEM= {
    LD,  I1,
    SE,  T1,
	
    LD,  T1,
    ST,  Q1,
    EXIT,
};

Counter

Zusatzfunktionen

Um die Flexibilität zu erhöhen, können Zusatzmodule in der Software aktiviert werden. Aktuell gibt es folgende Module:

Uhr

#ifdef WITH_CLOCK

Freilaufende Uhr mit DCF77-Synchronisierung.

Zeitschaltuhr

#ifdef WITH_CLOCK_SWITCH

Zeitschaltuhr mit 8 Bits, die als Eingänge in die SPS gespiegelt werden. Zur Einstellung der Zeiten kann ein LCD und 3 Taster angeschlossen werden (Erweiterungs-Steckerleiste) Die Zeitschaltuhr bezieht die Zeitinformation aus dem Modul Uhr (#ifdef WITH_CLOCK) Es können absolute Zeiten sowie Zeiten relativ zum Sonnen- Auf-/Untergang programmiert werden.

smarthomatic - Potokoll

#ifdef WITH_SHC

Es wird ein smarthomatic Power Switch - Device eingebunden, das 8 Ein-/Ausgänge in die SPS spiegelt. In Verbindung mit FHEM lässt sich die SPS damit als intelligentes Hausautomatisierungs-Gerät verwenden. Der Source-Code von Smarthomatic wurde etwas modifiziert, da die Smarthomatic-Module normalerweise davon ausgehen, dass ihnen der ganze Mikrocontroller gehört. Die Konfiguration der Module erfolgt über ein EEProm-Konfiurations-File. Dieses musste so modifiziert werden, dass noch Platz für das SPS-Programm bleibt. Die Hardware (RFM12B) wird über die Erweiterungs-Steckerleiste angeschlossen.

Bilder

SPS mit Zeitschaltuhr, 230 V
ZeitSchalt1.png

Links und Downloads

smarthomatic

Home of FHEM

Sourcecode: folgt...

Schaltplan: folgt...