Die Heimautomatisierung mit speicherprogrammierbarer Steuerung (SPS)

Seite 2: Beispiele

Inhaltsverzeichnis

Eine SPS erlaubt typischerweise die Konfiguration mehrerer Tasks mit unterschiedlichen Aufrufintervallen und Prioritäten. Im Beispiel ist ein Task dafür zuständig, die Lichtschalter im Haus abzufragen und entsprechend die Lampen zu schalten. Die Logik hierzu erfordert keine komplexen Rechnungen. Die Ausführungszeit eines entsprechenden Programms liegt daher im Milliksekundenbereich – also für den Menschen, der den Lichtschalter betätigt, nicht wahrnehmbar. Der Task für die Lichtsteuerung läuft im Zyklus von 20 Millisekunden, die durchschnittliche Ausführungszeit liegt bei zwei Millisekunden.

Mit der Steuerung der Jalousien gingen Berechnungen des Sonnenstandes einher. Sie basieren auf trigonometrischen Funktionen, die auf dem Controller ohne FPU einige Millisekunden in Anspruch nehmen. Für den aktuellen Anwendungsfall ist eine minütliche Berechnung ausreichend. Aus diesem Grund wurde die Rechnung in einen entsprechenden Task mit niedriger Priorität ausgelagert. Gleiches gilt für die Steuerung der Fußbodenheizung: Auch hier reicht ein Task, der nur einmal pro Minute ausgeführt wird. Kürzere Zyklen wären unnötig, da allein das Öffnen und Schließen der im Haus verbauten thermischen Ventile schon zwei Minuten dauert.

Im Falle eines SPS-Laufzeitsystems mit preemptivem Multitasking können Tasks höherer Priorität solche mit niedrigerer auch unterbrechen. Deren Ausführung wird solange angehalten, bis der höher priorisierte Task abgearbeitet ist, und erst dann wieder fortgesetzt. Solche Fälle können dazu führen, dass sich die Werte der IO-Variablen während der Abarbeitung eines Zyklus plötzlich ändern, weil ein höher priorisierter Task das entsprechende Speicherabbild aktualisiert hat. Dies ist bei der Aufteilung der Steuerungsaufgaben auf die einzelnen Tasks zu berücksichtigen.

Als Beispiel für ein SPS-Programm dient nun die Lichtsteuerung mit einem Stromstoßschalter mit eingebauter Zeituhr. Per Tastendruck soll die angeschlossene Lampe an- oder ausgeschaltet werden, gleichzeitig soll sie nach einer konfigurierbaren Zeit von selbst ausgehen. Die verbleibende Brenndauer wird zusätzlich mit ausgegeben und lässt sich beispielsweise in einer grafischen Oberfläche anzeigen. Als Komfortfunktion soll zudem die Möglichkeit vorgesehen werden, das Licht über einen Zentralschalter aus oder (als Alarmlicht) einschalten zu können, unabhängig vom vorherigen Schaltzustand. Als Funktionsblock soll die Lichtsteuerung also folgendermaßen aussehen:

Die Lichtsteuerung als Baustein in FB-Syntax (Abb. 1)

Der Baustein selbst wird in der Pascal-ähnlichen Sprache ST implementiert. Zunächst die Deklarationen der Ein- und Ausgabeparameter:

FUNCTION_BLOCK lightcontroller
VAR_INPUT
switch: BOOL; (* Schaltereingang *)
all_on: BOOL; (* Eingang für Alarmschalter *)
all_off: BOOL; (* Eingang für "Alles aus" *)
max_seconds: DINT; (* maximale Brenndauer der Lampe in Sekunden *)
END_VAR
VAR_OUTPUT
light: BOOL; (* Ausgang für die Lampe *)
remaining: DINT; (* Restliche Leuchtdauer in Sekunden *)
END_VAR

Um die abgelaufene Zeit zu ermitteln, ist ein Sekundenzähler notwendig. Die Zeitroutinen des IEC-Standards sind in dieser Hinsicht recht minimalistisch. Insbesondere existieren keine Standardfunktionen für die Ermittlung von Datum und Uhrzeit, da der Standard nicht davon ausgeht, dass alle Steuerungen tatsächlich über eine Echtzeituhr verfügen. Steuerungen mit einer ebensolchen bieten den Zugriff auf diese typischerweise über eine entsprechende Bibliotheksfunktion an. Für das Beispiel sei beim Standard geblieben. Er definiert eine Funktion TIME(), welche die Zeit in Millisekunden seit Systemstart liefert. Da der Wert intern als 32-Bit-Wert codiert wird, kommt es alle 49 Tage zu einem "Überlauf", sodass sich der Wert nicht ohne weiteres einsetzen lässt. Als Hilfsfunktion dient hierbei ein Sekundenzähler, der nach einem Initialisierungssignal als Eingang die abgelaufenen Sekunden als Ausgang liefert und auch den Überlauf von TIME() intern abfängt:

Sekundenzähler in FB-Syntax (Abb. 2)

Der Sekundenzähler ist wiederum als Funktionsblock in ST implementiert:

FUNCTION_BLOCK runseconds
VAR_INPUT
init: BOOL; (* startet den Zaehler *)
END_VAR
VAR_OUTPUT
seconds: UDINT;
END_VAR
VAR
runtime: UDINT;
lasttime: UDINT;
currtime: UDINT;
maxudint: UDINT := 4294967295; (* Höchster 32-Bit-Wert *)
END_VAR


(* Implementierung *)
IF init
THEN
runtime := 0;
lasttime := TIME_TO_UDINT(myTIME());
seconds := 0;
ELSE
currtime := TIME_TO_UDINT(myTIME());
IF currtime >= lasttime
THEN
(* Standardfall *)
runtime := runtime + (currtime - lasttime);
ELSE
(* Überlauf *)
runtime := runtime + (maxudint - lasttime) + currtime;
END_IF
lasttime := currtime;
seconds := runtime / 1000;
END_IF

Der Quelltext von runseconds verwendet anstelle der Funktion TIME die Funktion myTime(). Diese multipliziert das Ergebnis von TIME() mit einem konfigurierbaren Multiplikator, um den Test der Überlaufbehandlung zu vereinfachen. Für den Test von runseconds wurde im Beispiel als Multiplikator 1024 eingesetzt. Der Vollständigkeit halber hier der Quelltext von mytime:

FUNCTION mytime : TIME
VAR_INPUT
END_VAR
VAR
(* Für Debugging hier einen Multiplikator eintragen.
1024 erzeugt ca 20x pro Tag einen Überlauf *)
mult : UINT := 1;
END_VAR


(* Die Implementierung passt in eine Zeile *)
mytime := TIME() * mult;

Im Vergleich zum Funktionsblock fällt auf, dass eine Funktion keine Ausgabevariable definiert. Der Rückgabewert der Funktion wird durch Zuweisung an eine implizit definierte Variable mit dem Namen der Funktion (mytime) gesetzt. Der Code liefert außerdem ein Beispiel dafür, wie Variablen in ST initialisiert werden.

In der vorliegenden Implementierung unterstützt runseconds Laufzeiten von bis zu 49 Tagen, was für diesen und die meisten anderen Anwendungsfälle ausreichend sein sollte. Für längere Laufzeiten müsste die Variable runtime vom ULINT 64 Bit sein, wodurch sich allerdings auf den meisten Steuerungen das Laufzeitverhalten mangels nativer 64-Bit-Unterstützung etwas verschlechtern dürfte.

Da eine Instanz von runseconds intern in dem Lightcontroller benötigt wird, wird eine entsprechende Variable angelegt:

VAR
runtime: runseconds;
END_VAR

Nun zur Implementierung. Der einfachste Fall ist all_off:

IF all_off
THEN
remaining := 0;
light := FALSE;
RETURN;
END_IF

RETURN bricht die Verarbeitung der Funktion an dieser Stelle ab. Implizit erhält damit der all_off-Eingang die höchste Priorität, die Belegung der beiden anderen Schaltereingänge bleibt davon unberücksichtigt.

Der zweite Fall – Alarmlicht:

IF all_on
THEN
runtime(init:= TRUE);
remaining := max_seconds;
light := TRUE;
RETURN;
END_IF

Hiermit lässt sich der Laufzeitzähler initialisieren. Das Licht bleibt also so lange an, bis es entweder ausgeschaltet wird oder die maximale Laufzeit erreicht.

Schließlich der Hauptfall: Das Schalten über den Lichtschalter:

IF switch
THEN
IF light
THEN
(* Licht ist an, wir schalten aus *)
light := FALSE;
remaining := 0;
RETURN;
ELSE
(* Licht ist aus, wir schalten an *)
light := TRUE;
remaining := max_seconds;
runtime(init:= TRUE);
RETURN;
END_IF
END_IF

Sobald das Licht aus ist, wird auch der Sekundenzähler nicht mehr aufgerufen. Daher fehlt im ersten Zweig der entsprechende Aufruf. Die meiste Zeit wird allerdings kein Schalter gedrückt sein. Hier noch der Codeblock für diesen Fall:

(* Kein Schalter gedrückt *)
IF light
THEN
(* Prüfen, ob maximale Brenndauer erreicht *)
runtime(init:=FALSE);
remaining := max_seconds - UDINT_TO_DINT(runtime.seconds);
IF remaining <= 0
THEN
light := FALSE;
remaining := 0;
END_IF
END_IF

Die Konvertierung von runtime.seconds könnte man im Prinzip auch weglassen, Codesys erzeugt in dem Fall allerdings eine Warnung. Da es theoretisch sein könnte, dass die Routine seltener als einmal pro Sekunde aufgerufen wird, rechnet man beim Wert remaining mit einem vorzeichenbehafteten Datentyp und reagiert auch auf Werte unter null, also eine Überschreitung der vorgegebenen Laufzeit. Im Hauptprogramm der SPS könnte ein Lightcontroller-Baustein dann folgendermaßen verwendet werden:

Der Controllerbaustein in FB (Abb. 3)

In diesem Fall sind die Eingänge für all_on und all_off mit Konstanten belegt, das Licht schaltet sich nach spätestens vier Stunden selbst aus.

Eine Erweiterung des Anwendungsfalls zeigt die Flexibilität, die man durch den Einsatz einer SPS im Haus gewinnt: Will man beispielsweise das Licht in einem Flur von jedem angrenzenden Zimmer aus schalten können, lässt sich das elegant in der Software durch Hinzunahme einer Oder-Verknüpfung (OR) lösen. Der OR-Baustein gehört zu den Standardfunktionen in IEC61131 und kann beliebig viele Eingangswerte erhalten.

Fünf Lichtschalter schalten die gleiche Lampe (Abb. 4).