SPS-Programmierung in Strukturiertem Text: Funktionen, Funktionsbausteine und objektorientierte Erweiterungen
https://bildung.social/@oerinformatik/
https://oer-informatik.de/sps-programmierung-st
Strukturierter Text, SPS-Programmierung, IEC 61131-3, Funktionsbaustein, Programmorganisationseinheit, Instanz, CASE-Schrittkette, Methode, Interface, Vererbung, Simulation, virtuelle SPS
tl/dr; (ca. 27 min Lesezeit): Strukturierter Text (ST) ist die textuelle Hochsprache der IEC 61131-3. Dieser Artikel zeigt Schritt für Schritt Datentypen, Zuweisungen, IF, CASE und Schleifen, vergleicht ST mit KOP und FUP, unterscheidet Programm, Funktion und Funktionsbaustein, baut einen eigenen Antriebsbaustein mit Schnittstelle und Instanzdaten, setzt eine GRAFCET-Schrittkette mit CASE um, erklärt Methoden, Interfaces und Vererbung bei Funktionsbausteinen, zeigt, wie Du ein Programm in der Simulation testest und nach Pflichtenheft änderst, und ordnet virtuelle Steuerungen und KI-Assistenten im Engineering ein [AI-Assisted]. (Zuletzt geändert am 13.11.2026)
Dieser Artikel ist eine Vertiefung zu den Artikeln SPS-Grundlagen und Ablaufsteuerungen mit GRAFCET.
Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.
Dieser Artikel baut auf der Kann-Liste der Artikel SPS-Grundlagen und Ablaufsteuerungen mit GRAFCET auf.
Als Elektrotechnik-Grundlagen kann ich nach Durcharbeiten des Artikels…
… Variablen mit passenden Datentypen deklarieren und einfache Steuerungsaufgaben in Strukturiertem Text mit Zuweisungen, IF-, CASE-Anweisungen und Schleifen programmieren.
… eine Lösung in ST mit einer Lösung in KOP oder FUP vergleichen und begründen, welche Sprache für welche Aufgabe besser passt.
… Programm, Funktion und Funktionsbaustein unterscheiden und für eine Aufgabe die passende Programmorganisationseinheit wählen.
… einen Funktionsbaustein mit Eingangs-, Ausgangs- und statischen Variablen anlegen, mehrfach instanziieren und aufrufen.
… eine Schrittkette aus einem GRAFCET mit einer CASE-Anweisung in ST umsetzen.
… ein SPS-Programm in einer Simulationsumgebung mit simulierten Ein- und Ausgängen und Beobachtungstabelle testen, die Testfälle dokumentieren und ein Programm nach Pflichtenheft ändern.
Darüber hinaus kann ich als Elektrotechnik-Expertenwissen…
… Methoden, Schnittstellen (Interfaces) und Vererbung bei Funktionsbausteinen beschreiben und ihren Nutzen für wiederverwendbare Bausteinbibliotheken begründen.
… Chancen und Grenzen virtueller bzw. softwaredefinierter Steuerungen und KI-gestützter Programmerstellung im SPS-Engineering beurteilen.
In einem Futtermittelwerk tragen drei gleich gebaute Förderschnecken das Material aus drei Silos aus. Jede braucht dieselbe Logik: einschalten auf Anforderung, Rückmeldung des Schützes überwachen, bei Störung abschalten und erst nach dem Quittieren wieder anlaufen. Im Kontaktplan zeichnest Du diese Logik dreimal – und änderst sie später auch dreimal. In Strukturiertem Text schreibst Du sie einmal als Funktionsbaustein und rufst ihn für jede Schnecke mit eigenen Daten auf. Das Bild zeigt, wie Programm, Funktionsbaustein, Instanzen und Funktion zusammenhängen; die folgenden Schritte bauen genau dieses Beispiel auf.
Zum Üben gibt es einen eigenen Artikel mit Aufgaben und einblendbaren Beispiellösungen: Übungsaufgaben zur SPS-Programmierung in ST.
Schritt 1: Warum Strukturierter Text?
Die Norm IEC 61131-3 (DIN EN 61131-3) kennt die grafischen Sprachen Kontaktplan (KOP), Funktionsplan (FUP) und Ablaufsprache (AS) sowie die textuelle Sprache Strukturierter Text (ST); die Anweisungsliste ist mit der Ausgabe von 2025 entfallen (siehe SPS-Grundlagen, Schritt 6). ST ähnelt Pascal: Anweisungen enden mit Semikolon, Zuweisungen schreibst Du mit :=, Blöcke schließen mit END_IF, END_CASE usw. Manche Hersteller nennen ST anders, etwa SCL; der Sprachkern ist derselbe.
Welche Sprache passt, hängt von der Aufgabe ab:
| Kriterium | KOP / FUP | ST |
|---|---|---|
| einfache Verknüpfungen, Selbsthaltung, Verriegelung | sehr anschaulich, nah am Stromlaufplan | kurz, aber weniger anschaulich |
| Berechnungen, Skalieren, Vergleiche mit REAL-Werten | viele Bausteine, schnell unübersichtlich | eine Zeile je Formel |
| Fallunterscheidungen, Schrittketten, Betriebsarten | viele Netzwerke mit Sprüngen oder Speichern | IF und CASE lesen sich wie die Beschreibung |
| Wiederholungen über viele gleiche Anlagenteile | je Teil ein Netzwerk | Schleife über ein Feld |
| Online-Beobachtung bei der Fehlersuche | Signalfluss farbig sichtbar | Werte neben den Zeilen, Ablauf weniger sichtbar |
| Austausch zwischen Herstellern, Versionsverwaltung | grafische Formate schwer vergleichbar | reiner Text, gut vergleichbar und kopierbar |
In der Praxis mischst Du: Verknüpfungen nah an der Hardware bleiben oft in KOP oder FUP, damit Elektriker sie bei der Fehlersuche lesen können; Bausteine mit Logik, Berechnungen und Schrittketten entstehen in ST.
Schritt 2: Variablen, Datentypen und Zuweisungen
Jede Variable wird vor ihrer Verwendung in einem Deklarationsteil mit Namen und Datentyp angelegt. Statt absoluter Adressen wie %IX0.1 arbeitest Du mit symbolischen Namen; die Zuordnung zur Klemme steht einmal in der Variablenliste.
| Datentyp | Inhalt | Beispiel-Literal | typische Verwendung |
|---|---|---|---|
BOOL |
0 oder 1 (FALSE, TRUE) |
TRUE |
Taster, Endschalter, Schütze, Meldungen |
INT |
ganze Zahl, 16 bit, −32 768 bis 32 767 | 250 |
Zählerstände, Schrittnummern, Rohwerte von Analogbaugruppen |
DINT |
ganze Zahl, 32 bit | 150000 |
große Zählerstände, Stückzahlen |
REAL |
Gleitkommazahl, 32 bit | 72.5 |
Temperatur, Füllstand, Drehzahl |
TIME |
Zeitdauer | T#2S, T#1M30S, T#500MS |
Vorgabewerte von Zeitgliedern |
STRING |
Zeichenkette | 'Störung Schnecke 2' |
Meldetexte |
VAR // Ausschnitt, weitere Variablen entsprechend
xStartTaster : BOOL; // -S2, Schließer, Wert 1 = betätigt
iSchritt : INT := 0; // Schrittnummer der Schrittkette
rFuellstand : REAL; // Füllstand in Prozent
tNachlauf : TIME := T#20S; // Nachlaufzeit des Gebläses
axRueckmeld : ARRAY[1..3] OF BOOL; // Rückmeldungen der drei Schütze
END_VAR
xLuefter := xStartTaster AND NOT xUeberlast; // Verknüpfung
iZaehler := iZaehler + 1; // Rechnen mit INT
rFuellstand := INT_TO_REAL(iRohwert) / 327.67; // Typumwandlung, dann REAL-Rechnung
Drei Regeln vermeiden die meisten Fehler:
Typen nicht mischen:
INTundREALlassen sich nicht direkt verrechnen. Umwandeln mit Funktionen wieINT_TO_REALoderREAL_TO_INT.Zuweisung ist kein Vergleich:
:=weist zu,=vergleicht.IF iSchritt = 2 THENfragt ab,iSchritt := 2;setzt.Operatoren:
NOT,AND(auch&),OR,XORfür Bits;+ - * /undMODzum Rechnen;= <> < > <= >=zum Vergleichen.NOTundANDbinden stärker alsOR; setze im Zweifel Klammern.
Die Präfixe im Namen (x für BOOL, i für INT, r für REAL, t für TIME, a für Felder) sind keine Pflicht der Norm, aber eine verbreitete Namenskonvention (Schritt 8). Du siehst am Namen sofort, welchen Typ die Variable hat.
Schritt 3: Kontrollstrukturen – IF, CASE und Schleifen
IF – ELSIF – ELSE wählt nach Bedingungen aus. Die Bedingungen werden von oben nach unten geprüft; es wird nur der erste zutreffende Zweig ausgeführt. Damit legst Du auch den Vorrang fest: Was oben steht, gewinnt.
// Gebläse mit Vorrang AUS: die Störung steht oben und gewinnt immer
IF xUeberlast OR NOT xAusTaster THEN // Öffner: Wert 0 = betätigt
xGeblaese := FALSE;
ELSIF xEinTaster THEN
xGeblaese := TRUE;
END_IF;
Weil xGeblaese nur in den beiden Zweigen geschrieben wird und sonst seinen Wert behält, wirkt dieses IF wie ein rücksetzdominanter Speicher (RS). Das gleiche Netzwerk im FUP wäre ein RS-Baustein mit xEinTaster am Setzeingang und xUeberlast OR NOT xAusTaster am Rücksetzeingang R1. Vertauschst Du die beiden Zweige, wird daraus ein setzdominanter Speicher – ein typischer Fehler.
CASE wählt nach dem Wert einer ganzzahligen Variablen aus. Das passt zu Betriebsarten und Schrittketten (Schritt 6):
CASE iBetriebsart OF
0: xAutomatik := FALSE; // Aus
1: xAutomatik := FALSE; xHandFrei := TRUE; // Hand
2: xAutomatik := TRUE; xHandFrei := FALSE; // Automatik
3..9: xStoerungBA := TRUE; // ungültige Werte
ELSE
xStoerungBA := TRUE;
END_CASE;
Schleifen wiederholen Anweisungen innerhalb eines Zyklus:
FOR i := 1 TO 3 DO … END_FOR;– feste Anzahl, z. B. über alle Elemente eines Feldes.WHILE Bedingung DO … END_WHILE;– solange die Bedingung gilt, Prüfung vor dem Durchlauf.REPEAT … UNTIL Bedingung END_REPEAT;– Prüfung nach dem Durchlauf.
// Summe der Füllstände aller drei Silos und Meldung "ein Silo fast leer"
rSumme := 0.0;
xFastLeer := FALSE;
FOR i := 1 TO 3 DO
rSumme := rSumme + arFuellstand[i];
IF arFuellstand[i] < 10.0 THEN
xFastLeer := TRUE;
END_IF;
END_FOR;
Achtung Zykluszeit: Eine Schleife läuft vollständig durch, bevor die SPS die Ausgänge schreibt. Warte daher nie mit WHILE auf ein Eingangssignal („solange Endschalter nicht betätigt“): Das Prozessabbild der Eingänge ändert sich während des Zyklus nicht, die Schleife endet nie, und die Zykluszeitüberwachung (Watchdog) stoppt die CPU. Auf Ereignisse wartest Du in einer SPS, indem Du in jedem Zyklus neu abfragst, z. B. in einem Schritt der Schrittkette.
Schritt 4: Programmorganisationseinheiten – Programm, Funktion und Funktionsbaustein
Ein SPS-Projekt besteht aus Programmorganisationseinheiten (POE, englisch POU). Die Norm kennt drei Arten:
| POE | Schlüsselwort | Gedächtnis | Aufruf | Beispiel |
|---|---|---|---|---|
| Programm | PROGRAM … END_PROGRAM |
ja, Variablen bleiben erhalten | von der Task (zyklisch, zeitgesteuert, ereignisgesteuert) | PRG_Silo: ruft alle Bausteine einer Anlage auf |
| Funktion | FUNCTION … END_FUNCTION |
nein, gleiche Eingänge ergeben immer dasselbe Ergebnis | wie eine Formel, liefert einen Rückgabewert | FC_Skalieren: Rohwert in Prozent umrechnen |
| Funktionsbaustein | FUNCTION_BLOCK … END_FUNCTION_BLOCK |
ja, je Instanz eigene Daten | über eine Instanz | FB_Antrieb, aber auch die Standardbausteine TON, CTU, RS |
Die Faustregel: Braucht der Baustein ein Gedächtnis (Speicher, Zeit, Zähler, Schrittnummer), ist es ein Funktionsbaustein; rechnet er nur aus seinen Eingängen etwas aus, eine Funktion. Ein Zeitglied TON muss sich merken, seit wann der Eingang ansteht – deshalb ist es ein Funktionsbaustein, und Du legst für jede Zeit eine eigene Instanz an.
Eine Funktion zur Umrechnung des Rohwerts einer Analogbaugruppe (hier angenommen: 0 bis 32 767 für den ganzen Messbereich; die Werte stehen im Datenblatt der Baugruppe):
FUNCTION FC_Skalieren : REAL
VAR_INPUT
iRoh : INT; // Rohwert der Analogbaugruppe, 0 ... 32767
rMin : REAL; // physikalischer Wert bei Rohwert 0
rMax : REAL; // physikalischer Wert bei Rohwert 32767
END_VAR
FC_Skalieren := rMin + INT_TO_REAL(iRoh) * (rMax - rMin) / 32767.0;
END_FUNCTION
Der Rückgabewert wird dem Funktionsnamen zugewiesen. Aufgerufen wird die Funktion wie eine Formel: arFuellstand[1] := FC_Skalieren(iRoh := iAI_Silo1, rMin := 0.0, rMax := 100.0);
Schritt 5: Einen eigenen Funktionsbaustein schreiben und mehrfach instanziieren
Ein Funktionsbaustein hat eine Schnittstelle und einen Anweisungsteil. Die Schnittstelle legt fest, was von außen hinein- und herausgeht:
| Abschnitt | Bedeutung | Zugriff von außen |
|---|---|---|
VAR_INPUT |
Eingangsvariablen, werden beim Aufruf übergeben | nur schreiben (beim Aufruf) |
VAR_OUTPUT |
Ausgangsvariablen, Ergebnis des Bausteins | nur lesen: Instanz.Ausgang |
VAR_IN_OUT |
Durchgangsvariable, wird als Verweis übergeben und im Baustein verändert | Variable des Aufrufers wird direkt geändert |
VAR |
statische (interne) Variablen, z. B. Speicher und Instanzen von TON |
von außen nicht zugänglich (gekapselt) |
VAR_TEMP |
temporäre Variablen, nur während eines Aufrufs gültig | nicht zugänglich |
Der Antriebsbaustein für die Förderschnecken überwacht die Rückmeldung des Schützes (Hilfskontakt): Ist das Schütz angesteuert und meldet nach 2 s nicht zurück – oder meldet es zurück, obwohl es nicht angesteuert ist (Kontakt verschweißt) –, speichert der Baustein eine Störung. Die Störung schaltet den Antrieb ab und lässt sich erst quittieren, wenn kein Einschaltbefehl mehr anliegt.
FUNCTION_BLOCK FB_Antrieb
VAR_INPUT
xEin : BOOL; // Einschaltbefehl
xRueckmeld : BOOL; // Hilfskontakt des Schützes, 1 = angezogen
xQuittieren : BOOL; // Störung quittieren
tUeberwach : TIME := T#2S; // Zeit bis zur Rückmeldung
END_VAR
VAR_OUTPUT
xSchuetz : BOOL; // Ansteuerung des Schützes
xStoerung : BOOL; // Störung gespeichert
END_VAR
VAR
tonUeberwach : TON; // eigene Zeit je Instanz
END_VAR
// Diskrepanz: Ansteuerung und Rückmeldung passen nicht zusammen
tonUeberwach(IN := xSchuetz XOR xRueckmeld, PT := tUeberwach);
IF tonUeberwach.Q THEN
xStoerung := TRUE; // Vorrang: Störung setzen
ELSIF xQuittieren AND NOT xEin THEN
xStoerung := FALSE; // Quittieren nur ohne Befehl
END_IF;
xSchuetz := xEin AND NOT xStoerung;
END_FUNCTION_BLOCK
Der Funktionsbaustein ist nur der Bauplan. Erst eine Instanz – eine Variable vom Typ des Bausteins – bekommt Speicher für xStoerung, xSchuetz und die Zeit tonUeberwach. Drei Schnecken brauchen drei Instanzen, die sich gegenseitig nicht beeinflussen. Mit einem Feld von Instanzen und einer Schleife wird das Programm kurz:
PROGRAM PRG_Silo
VAR
aSchnecke : ARRAY[1..3] OF FB_Antrieb; // drei Instanzen
i : INT;
END_VAR
FOR i := 1 TO 3 DO
aSchnecke[i](xEin := axAnforderung[i] AND xAnlageEin,
xRueckmeld := axRueckmeld[i],
xQuittieren := xQuittTaster);
axSchuetz[i] := aSchnecke[i].xSchuetz;
axStoerLampe[i] := aSchnecke[i].xStoerung;
END_FOR;
END_PROGRAM
Zwei Punkte sind wichtig:
Jede Instanz in jedem Zyklus genau einmal aufrufen. Ein Funktionsbaustein arbeitet nur, wenn er aufgerufen wird. Steht der Aufruf in einem
IF-Zweig, der nicht durchlaufen wird, läuft auch die ZeittonUeberwachnicht weiter, und eine Störung wird nicht erkannt.Ausgänge über die Instanz lesen:
aSchnecke[2].xStoerung. Die internen Variablen (tonUeberwach) sind von außen nicht zugänglich; sie sind gekapselt.
Bibliotheken und Wiederverwendung: Ein geprüfter Baustein wie FB_Antrieb gehört in eine Bibliothek, die alle Projekte des Betriebs einbinden. Eine Korrektur wird dann einmal in der Bibliothek gemacht, mit neuer Versionsnummer freigegeben und in den Projekten aktualisiert. Die Standardbausteine TON, TOF, TP, CTU, R_TRIG stammen selbst aus der Standardbibliothek der Norm. Damit Wiederverwendung funktioniert, muss die Schnittstelle stabil und dokumentiert sein: Kopfkommentar mit Zweck, Version, Änderungen und Bedeutung jeder Variablen.
Schritt 6: Eine GRAFCET-Schrittkette mit CASE umsetzen
Im Artikel Ablaufsteuerungen mit GRAFCET wird ein GRAFCET mit Schrittspeichern (RS-Speicher je Schritt) umgesetzt. In ST geht es kürzer: Eine ganzzahlige Schrittvariable speichert den aktiven Schritt, eine CASE-Anweisung prüft je Schritt die Transitionsbedingung und schaltet weiter. Weil die Variable nur einen Wert haben kann, ist immer genau ein Schritt aktiv – Doppelaktivierungen wie bei vergessenen Rücksetzbedingungen gibt es nicht.
Beispiel: Ein Etikettierer an einem Kistenband. GRAFCET (in Worten): Schritt 0 Grundstellung; Transition „Start und Andrückzylinder hinten“; Schritt 1 Band läuft; Transition „Lichtschranke -B1 meldet Kiste“; Schritt 2 Andrückzylinder vor (Etikett andrücken); Transition „Schritt 2 seit 1,5 s aktiv“; Schritt 3 Zylinder zurück; Transition „Endlage hinten -B2“; zurück zu Schritt 0.
// Weiterschalten: je Schritt nur die Transition zum Folgeschritt
CASE iSchritt OF
0: // Grundstellung
IF xStart AND xZylHinten THEN iSchritt := 1; END_IF;
1: // Band läuft, bis die Kiste an der Lichtschranke steht
IF xLichtschranke THEN iSchritt := 2; END_IF;
2: // Etikett andrücken
IF tonAndruck.Q THEN iSchritt := 3; END_IF;
3: // Zylinder zurück
IF xZylHinten THEN iSchritt := 0; END_IF;
END_CASE;
// NOT-HALT bzw. Aus: Kette in die Grundstellung (Vorrang, steht unten)
IF NOT xFreigabe THEN iSchritt := 0; END_IF;
// Zeitglied in jedem Zyklus aufrufen
tonAndruck(IN := (iSchritt = 2), PT := T#1500MS);
// Aktionen: Ausgänge aus dem aktiven Schritt (kontinuierlich wirkend)
xBand := (iSchritt = 1);
xZylVor := (iSchritt = 2);
Die Trennung in drei Teile – Weiterschalten, Zeiten und Ausgänge – macht das Programm übersichtlich: Jeder Ausgang wird genau an einer Stelle geschrieben, und Du siehst sofort, in welchen Schritten er an ist. Kontinuierlich wirkende Aktionen des GRAFCET werden zu Zuweisungen wie xBand := (iSchritt = 1);, gespeichert wirkende Aktionen zu IF-Anweisungen, die nur setzen oder rücksetzen.
Wie im GRAFCET-Artikel gilt: Sicherheitsfunktionen wie NOT-HALT werden nicht allein durch das Zurücksetzen der Schrittvariable verwirklicht, sondern durch eine sicherheitsgerichtete Abschaltung der Energie (siehe Steuerungssicherheit). Das Programm sorgt nur dafür, dass die Anlage danach in einem definierten Zustand steht.
Lesbarer werden die Schritte mit Konstanten oder einem Aufzählungstyp, etwa TYPE E_Schritt : (GRUNDSTELLUNG, BAND, ANDRUECKEN, ZURUECK); END_TYPE. Dann heißt es CASE eSchritt OF BAND: … statt 1:. Wer Mikrocontroller programmiert, kennt dieses Muster als Zustandsautomat mit switch … case in C (siehe Mikrocontroller-Programmierung); der Unterschied ist, dass die SPS die Schleife um den Automaten (den Zyklus) selbst übernimmt.
Schritt 7: Objektorientierte Erweiterungen – Methoden, Interfaces und Vererbung
Seit der dritten Ausgabe (2013) kennt die IEC 61131-3 objektorientierte Sprachmittel für Funktionsbausteine. Die Grundbegriffe sind dieselben wie in der Softwareentwicklung (siehe auch Objektorientierte Algorithmen): Der Funktionsbaustein entspricht der Klasse, die Instanz dem Objekt.
Methode (
METHOD): eine Aktion, die zum Baustein gehört und gezielt aufgerufen wird, z. B.fbRuehrwerk.Starten(). Methoden haben eigene Ein- und Ausgänge und greifen auf die Instanzdaten des Bausteins zu. Statt eines großen Bausteins mit vielen Befehlseingängen hat der Antrieb klar benannte MethodenStarten,StoppenundQuittieren.Eigenschaft (
PROPERTYmitGET/SET): ein kontrollierter Zugriff auf einen Wert, z. B. die Betriebsstunden, die gelesen, aber nicht von außen überschrieben werden dürfen. Eigenschaften sind in verbreiteten Entwicklungsumgebungen wie CODESYS als Erweiterung umgesetzt.Schnittstelle (
INTERFACE): eine Liste von Methoden ohne Inhalt – ein Vertrag. Ein Funktionsbaustein, derIMPLEMENTS I_Antriebangibt, muss alle Methoden der Schnittstelle besitzen. Eine Variable vom Typ der Schnittstelle kann dann auf jede Instanz zeigen, die sie umsetzt.Vererbung (
EXTENDS): Ein Baustein übernimmt Variablen und Methoden eines Basisbausteins und ergänzt neue.FB_AntriebReversierbar EXTENDS FB_Antrieberbt Überwachung, Störspeicher und Quittieren und ergänzt die zweite Drehrichtung. Eine geerbte Methode kann mitOVERRIDEüberschrieben werden; überSUPERruft sie bei Bedarf die Fassung des Basisbausteins auf.Zugriffsrechte:
PUBLIC,PRIVATE,PROTECTEDundINTERNALlegen fest, wer eine Methode aufrufen darf. So bleibt die interne Logik gekapselt.
Das Klassendiagramm zeigt die Bausteinbibliothek für Antriebe eines Rührwerks: die Schnittstelle, den Basisbaustein und den abgeleiteten Baustein.
Ein Ausschnitt in ST (Schreibweise nach der Norm, die Entwicklungsumgebungen legen Methoden meist als eigene Objekte unter dem Baustein an):
INTERFACE I_Antrieb
METHOD Starten : BOOL // TRUE, wenn der Start angenommen wurde
END_METHOD
METHOD Stoppen
END_METHOD
METHOD Quittieren
END_METHOD
END_INTERFACE
FUNCTION_BLOCK FB_Antrieb IMPLEMENTS I_Antrieb
VAR_INPUT
xRueckmeld : BOOL;
END_VAR
VAR_OUTPUT
xSchuetz : BOOL;
xStoerMeldung : BOOL;
END_VAR
VAR PROTECTED
xBefehl : BOOL; // gespeicherter Startbefehl
xStoerung : BOOL;
tonUeberwach : TON;
END_VAR
// Rumpf: läuft bei jedem Aufruf der Instanz (einmal je Zyklus)
tonUeberwach(IN := xSchuetz XOR xRueckmeld, PT := T#2S);
IF tonUeberwach.Q THEN xStoerung := TRUE; xBefehl := FALSE; END_IF;
xSchuetz := xBefehl AND NOT xStoerung;
xStoerMeldung := xStoerung;
METHOD PUBLIC Starten : BOOL
IF NOT xStoerung THEN xBefehl := TRUE; END_IF;
Starten := xBefehl;
END_METHOD
METHOD PUBLIC Stoppen
xBefehl := FALSE;
END_METHOD
METHOD PUBLIC Quittieren
IF NOT xBefehl THEN xStoerung := FALSE; END_IF;
END_METHOD
END_FUNCTION_BLOCK
Der Nutzen zeigt sich im Programm: Mit einem Feld von Schnittstellenvariablen stoppst Du alle Antriebe, gleich welchen Typs, mit einer Schleife – ohne zu wissen, ob ein Rührwerk reversierbar ist:
VAR
fbRuehrwerk1 : FB_Antrieb;
fbRuehrwerk2 : FB_AntriebReversierbar;
aAlle : ARRAY[1..2] OF I_Antrieb;
i : INT;
END_VAR
aAlle[1] := fbRuehrwerk1;
aAlle[2] := fbRuehrwerk2;
IF xAnlageAus THEN
FOR i := 1 TO 2 DO
aAlle[i].Stoppen(); // ruft je Instanz die passende Methode
END_FOR;
END_IF;
Warum das für Bibliotheken zählt: Eine Bibliothek mit Schnittstellen und Basisbausteinen lässt sich erweitern, ohne vorhandene, geprüfte Bausteine zu ändern. Ein neuer Antrieb mit Frequenzumrichter erbt von FB_Antrieb oder setzt I_Antrieb um; Programme, die nur die Schnittstelle kennen, laufen unverändert weiter. Der Preis: Die Struktur ist für Instandhalter schwerer zu lesen als ein KOP-Netzwerk, und die Fehlersuche braucht Wissen über Vererbung. Objektorientierung lohnt sich deshalb vor allem für Bibliotheken, die viele Projekte nutzen – nicht für eine einzelne kleine Maschine.
Schritt 8: Normgerechte Programmstruktur, Kommentare und Namen
Ein Programm wird öfter gelesen als geschrieben – von Kolleg*innen, von der Instandhaltung um drei Uhr nachts und von Dir selbst ein Jahr später. Hilfreich sind:
Gliederung nach Anlagenteilen: ein Programm je Anlage oder Anlagenteil, Funktionsbausteine je Gerätetyp, Funktionen für Berechnungen. Ein- und Ausgänge an einer Stelle (Eingänge lesen, Bausteine aufrufen, Ausgänge schreiben), nicht über das ganze Projekt verstreut.
Kapselung: Bausteine greifen nur über ihre Schnittstelle auf Daten zu, nicht direkt auf globale Variablen oder Eingangsadressen. Nur dann lassen sie sich mehrfach instanziieren und getrennt testen.
Namenskonvention: einheitliche Präfixe (z. B.
PRG_,FB_,FC_,I_für die POE-Art;x,i,r,t,afür den Datentyp;fb,tonfür Instanzen) und sprechende Namen mit Bezug zum Betriebsmittel (xB1_Lichtschrankestattx7).Kommentare: Kopfkommentar je Baustein (Zweck, Ersteller, Version, Datum, Änderungen), Kommentar an jeder Variablen (Bedeutung, Einheit, Wert 1 bedeutet …) und dort, wo eine Zeile nicht für sich spricht. Kommentare erklären das Warum, nicht das, was ohnehin in der Zeile steht.
Keine magischen Zahlen: Zeiten und Grenzwerte als benannte Konstanten oder Eingänge (
tUeberwach,rGrenzLeer), nicht als Zahl mitten im Code.
Schritt 9: In der Simulation testen und ein Programm nach Pflichtenheft ändern
Ein Programm wird getestet, bevor es auf die Anlage geladen wird. Dafür gibt es Simulationsumgebungen: eine Soft-SPS auf dem PC (z. B. die Laufzeitsysteme der Entwicklungsumgebungen) oder einen Simulator der Hersteller-CPU. Im Simulator gibt es keine echten Taster; Du setzt die Eingänge über Testpanels oder eine Beobachtungstabelle (Watch-Tabelle) von Hand oder per Skript und beobachtest Ausgänge, Zeiten und interne Variablen. Weitergehend ist die virtuelle Inbetriebnahme: Ein Anlagenmodell (digitaler Zwilling) simuliert Endschalter, Füllstände und Laufzeiten, sodass auch Abläufe getestet werden können.
Steuern setzt einen Wert einmal, das Programm kann ihn im nächsten Zyklus überschreiben. Forcen hält einen Wert fest, auch gegen das Programm. Geforcte Werte sind gefährlich, wenn sie vergessen werden – an einer echten Anlage gehört das Forcen dokumentiert und nach dem Test aufgehoben.
Testfälle planen und dokumentieren: Jeder Testfall beschreibt Ausgangszustand, Eingabe und erwartetes Ergebnis, bevor getestet wird. Neben dem Normalablauf gehören Grenz- und Fehlerfälle dazu. Für FB_Antrieb zum Beispiel:
| Nr. | Ausgangszustand | Eingabe (simuliert) | erwartetes Ergebnis | beobachtet | i. O. |
|---|---|---|---|---|---|
| T1 | keine Störung, xEin = 0 | xEin := 1, nach 0,5 s xRueckmeld := 1 | xSchuetz = 1, keine Störung | xSchuetz = 1, xStoerung = 0 | ja |
| T2 | keine Störung, xEin = 0 | xEin := 1, xRueckmeld bleibt 0 | nach 2 s xStoerung = 1, xSchuetz = 0 | wie erwartet nach 2,0 s | ja |
| T3 | Störung aus T2, xEin = 1 | xQuittieren := 1 | Störung bleibt (Befehl liegt an) | Störung bleibt | ja |
| T4 | Störung aus T2 | xEin := 0, dann xQuittieren := 1 | xStoerung = 0 | xStoerung = 0 | ja |
| T5 | xEin = 0, xSchuetz = 0 | xRueckmeld := 1 (Kontakt verschweißt) | nach 2 s xStoerung = 1 | wie erwartet | ja |
Zum Protokoll gehören außerdem Datum, Name, Programmversion und die verwendete Simulationsumgebung. Ein Test in der Simulation ersetzt nicht die Inbetriebnahme an der realen Anlage: Verdrahtung, Sensoren, Laufzeiten und vor allem Sicherheitsfunktionen werden an der Maschine geprüft (siehe SPS-Grundlagen, Schritt 11).
Ein vorhandenes Programm nach Pflichtenheft ändern:
Pflichtenheft lesen und die geforderte Änderung in Anforderungen übersetzen (was soll sich wann wie verhalten?). Unklarheiten mit dem Auftraggeber klären, bevor programmiert wird.
Ist-Stand sichern: Projekt aus der Steuerung hochladen bzw. die freigegebene Version holen und mit der Version vergleichen, die auf der Anlage läuft. Neue Versionsnummer vergeben.
Änderung planen: betroffene Bausteine, Variablen und Ein-/Ausgänge bestimmen; prüfen, ob ein Bibliotheksbaustein betroffen ist (dann wirkt die Änderung auf alle Projekte, die ihn nutzen).
Ändern und kommentieren (Änderungshistorie im Kopfkommentar).
Testen in der Simulation: neue Testfälle für die Änderung und die alten Testfälle erneut (Regressionstest), damit nichts Bewährtes kaputtgeht.
Laden, in Betrieb nehmen, dokumentieren: Abnahme mit dem Auftraggeber, Dokumentation und Sicherung aktualisieren.
Schritt 10: Virtuelle Steuerungen und KI-Assistenten im Engineering
Bei der softwaredefinierten Automatisierung läuft die Steuerungslogik nicht mehr auf einer eigenen SPS-Hardware, sondern als virtuelle SPS auf einem Industrie-PC oder Edge-Server, oft gemeinsam mit anderen Anwendungen in Containern oder virtuellen Maschinen. Ein Beispiel ist die SIMATIC S7-1500V, die als App auf einer Industrial-Edge-Plattform läuft; andere Hersteller bieten vergleichbare Laufzeitsysteme an. Die PC-basierte Steuerung aus SPS-Grundlagen, Schritt 2 wird damit vom Gerät gelöst: Die Hardware wird austauschbar, die Steuerung wird eine Software.
Parallel erzeugen KI-Assistenten in den Entwicklungsumgebungen aus einer Beschreibung in Alltagssprache ST- oder KOP-Code, Kommentare, Variablenlisten und Testfälle.
| Chancen | Grenzen und Risiken | |
|---|---|---|
| virtuelle SPS | Hardware zentral und austauschbar, Updates und Sicherung wie bei Servern, mehrere Steuerungen auf einem Rechner, einfache Kopplung mit Datenanalyse und Leitsystem, Test im selben Laufzeitsystem wie im Betrieb | Echtzeit hängt von Rechner, Virtualisierung und Netzwerk ab; Ein- und Ausgänge laufen über einen Feldbus, dessen Ausfall die ganze Anlage trifft; IT-Sicherheit (Patchen, Zugriffsrechte, Angriffsfläche) wird zur Aufgabe der Automatisierung; Sicherheitsfunktionen brauchen zertifizierte Lösungen; Instandhaltung braucht IT-Wissen |
| KI-Assistent im Engineering | schnelle Entwürfe für Standardaufgaben, Kommentare und Dokumentation, Vorschläge für Testfälle, Umsetzen alter Programme in andere Sprachen | Code kann falsch sein und trotzdem plausibel aussehen (vertauschter Vorrang, Zeitglied nicht in jedem Zyklus aufgerufen, Warteschleife auf Eingänge); die KI kennt Anlage, Verdrahtung und Sicherheitskonzept nicht; Verantwortung bleibt bei der Fachkraft; Betriebsgeheimnisse in Eingaben schützen |
Für KI-erzeugten Steuerungscode gelten dieselben Regeln wie für Mikrocontroller-Code (siehe Mikrocontroller-Programmierung, „Code von KI-Assistenten prüfen“): Jede Zeile lesen und verstehen, gegen das Pflichtenheft prüfen, die Testfälle unabhängig von der KI aus den Anforderungen ableiten und in der Simulation ausführen. Sicherheitsgerichtete Programmteile werden nie ungeprüft übernommen.
Wie geht es weiter?
Aufgaben zu den Schritten dieses Artikels: Übungsaufgaben zur SPS-Programmierung in ST
Aufbau, Zyklus, KOP, FUP, Zeiten und Zähler als Grundlage: SPS-Grundlagen
Schrittketten entwerfen: Ablaufsteuerungen mit GRAFCET
Sicherheitsgerichtete Abschaltung, NOT-HALT und Sicherheits-SPS: Steuerungssicherheit
Funktionen, Zustandsautomaten und Code-Prüfung beim Mikrocontroller: Mikrocontroller-Programmierung
Leitfragen
- Wann ist ST einer grafischen Sprache wie KOP oder FUP überlegen, und wann nicht?
ST ist stark bei Berechnungen, Fallunterscheidungen, Schrittketten und Schleifen über viele gleiche Anlagenteile und lässt sich als Text gut vergleichen und wiederverwenden. Für einfache Verknüpfungen, Selbsthaltung und Verriegelung ist KOP bzw. FUP anschaulicher, und bei der Fehlersuche sieht die Instandhaltung den Signalfluss direkt. siehe Abschnitt „Schritt 1: Warum Strukturierter Text?“
- Warum darfst Du in einer SPS nicht mit einer WHILE-Schleife auf einen Endschalter warten?
Die Schleife läuft innerhalb eines Zyklus, in dem sich das Prozessabbild der Eingänge nicht ändert. Sie endet nie, die Ausgänge werden nicht geschrieben und die Zykluszeitüberwachung stoppt die CPU. Auf Ereignisse wartest Du, indem Du in jedem Zyklus neu abfragst, z. B. in einem Schritt der Schrittkette. siehe Abschnitt „Schritt 3: Kontrollstrukturen – IF, CASE und Schleifen“
- Woran entscheidest Du, ob Du eine Funktion oder einen Funktionsbaustein schreibst?
Braucht der Baustein ein Gedächtnis (Speicher, Zeit, Zähler, Schrittnummer), ist es ein Funktionsbaustein mit Instanzdaten; berechnet er nur aus seinen Eingängen einen Wert, ist es eine Funktion mit Rückgabewert. Das Programm ruft beides auf und wird selbst von der Task gestartet. siehe Abschnitt „Schritt 4: Programmorganisationseinheiten – Programm, Funktion und Funktionsbaustein“
- Was unterscheidet VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT und VAR, und warum braucht jede Förderschnecke eine eigene Instanz?
Eingänge werden beim Aufruf übergeben, Ausgänge über die Instanz gelesen, Durchgangsvariablen als Verweis übergeben und im Baustein geändert, statische Variablen sind gekapselte interne Daten. Jede Instanz hat eigenen Speicher für Störung, Ausgänge und Zeit; mit einer gemeinsamen Instanz würden sich die Schnecken gegenseitig beeinflussen. siehe Abschnitt „Schritt 5: Einen eigenen Funktionsbaustein schreiben und mehrfach instanziieren“
- Wie setzt Du einen GRAFCET mit CASE in ST um, und warum ist dabei immer genau ein Schritt aktiv?
Eine ganzzahlige Schrittvariable speichert den aktiven Schritt; im CASE-Zweig jedes Schritts wird die Transitionsbedingung geprüft und die Variable auf den Folgeschritt gesetzt. Zeitglieder werden außerhalb des CASE in jedem Zyklus aufgerufen, Ausgänge aus der Schrittnummer gebildet. Da die Variable nur einen Wert hat, ist immer genau ein Schritt aktiv. siehe Abschnitt „Schritt 6: Eine GRAFCET-Schrittkette mit CASE umsetzen“
- Was leisten Methoden, Interfaces und Vererbung bei Funktionsbausteinen für eine Bausteinbibliothek?
Methoden geben einem Baustein klar benannte Aktionen wie Starten und Stoppen, ein Interface legt diese Aktionen als Vertrag fest, und mit EXTENDS übernimmt ein neuer Baustein die geprüfte Logik eines Basisbausteins und ergänzt nur das Neue. So lässt sich die Bibliothek erweitern, ohne vorhandene Bausteine und Programme zu ändern. siehe Abschnitt „Schritt 7: Objektorientierte Erweiterungen – Methoden, Interfaces und Vererbung“
- Wie testest Du ein Programm in der Simulation, und was gehört in das Testprotokoll?
Du lädst das Programm in eine Soft-SPS bzw. einen Simulator, setzt die Eingänge über eine Beobachtungstabelle und beobachtest Ausgänge und interne Variablen. Testfälle mit Ausgangszustand, Eingabe und erwartetem Ergebnis legst Du vorher fest, auch für Fehler- und Grenzfälle; im Protokoll stehen zusätzlich das beobachtete Ergebnis, die Bewertung, Datum, Name und Programmversion. siehe Abschnitt „Schritt 9: In der Simulation testen und ein Programm nach Pflichtenheft ändern“
- Welche Chancen und Grenzen haben virtuelle Steuerungen und KI-erzeugter Steuerungscode?
Virtuelle Steuerungen machen die Hardware austauschbar und erleichtern Updates und Datenanbindung, hängen aber von Rechner, Netzwerk und IT-Sicherheit ab. KI-Assistenten liefern schnell Entwürfe, können aber plausibel aussehenden falschen Code erzeugen; die Fachkraft prüft jede Zeile, testet mit eigenen Testfällen und bleibt verantwortlich, besonders für Sicherheitsfunktionen. siehe Abschnitt „Schritt 10: Virtuelle Steuerungen und KI-Assistenten im Engineering“
Links und weitere Informationen
zur Norm: Wikipedia: IEC 61131-3
als Nachschlagewerk: Wikipedia: Strukturierter Text
zur objektorientierten Programmierung: Wikipedia: Objektorientierte Programmierung
zur virtuellen Steuerung (Beispiel): Industrial Production: Erste virtuelle Simatic-Steuerung
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “SPS-Programmierung in Strukturiertem Text: Funktionen, Funktionsbausteine und objektorientierte Erweiterungen” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/sps-programmierung-st veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/elektrotechnik/steuerungstechnik. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 13.11.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
