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.

Überblick über die Programmorganisationseinheiten: Das Programm PRG_Silo ruft drei Instanzen aSchnecke 1 bis 3 des Funktionsbausteins FB_Antrieb auf. Der Funktionsbaustein ist der Bauplan mit VAR_INPUT (xEin, xRueckmeld, xQuittieren), VAR_OUTPUT (xSchuetz, xStoerung) und VAR (tonUeberwach); jede Instanz hat eigene Instanzdaten mit eigenem Störspeicher und eigener Zeit. Daneben die Funktion FC_Skalieren ohne Gedächtnis, die aus einem Rohwert einen Füllstand in Prozent berechnet (mit KI erstellt)

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:

KOP und FUP im Vergleich mit ST
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.

Wichtige Datentypen in ST
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: INT und REAL lassen sich nicht direkt verrechnen. Umwandeln mit Funktionen wie INT_TO_REAL oder REAL_TO_INT.

  • Zuweisung ist kein Vergleich: := weist zu, = vergleicht. IF iSchritt = 2 THEN fragt ab, iSchritt := 2; setzt.

  • Operatoren: NOT, AND (auch &), OR, XOR für Bits; + - * / und MOD zum Rechnen; = <> < > <= >= zum Vergleichen. NOT und AND binden stärker als OR; 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:

Programmorganisationseinheiten nach IEC 61131-3
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:

Variablenbereiche eines Funktionsbausteins
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 Zeit tonUeberwach nicht 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 Methoden Starten, Stoppen und Quittieren.

  • Eigenschaft (PROPERTY mit GET/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, der IMPLEMENTS I_Antrieb angibt, 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_Antrieb erbt Überwachung, Störspeicher und Quittieren und ergänzt die zweite Drehrichtung. Eine geerbte Methode kann mit OVERRIDE überschrieben werden; über SUPER ruft sie bei Bedarf die Fassung des Basisbausteins auf.

  • Zugriffsrechte: PUBLIC, PRIVATE, PROTECTED und INTERNAL legen 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.

UML-Klassendiagramm einer Bausteinbibliothek für Antriebe: Die Schnittstelle I_Antrieb mit den Methoden Starten, Stoppen und Quittieren wird vom Funktionsbaustein FB_Antrieb realisiert. FB_Antrieb hat die geschützten Attribute xBefehl, xStoerung und tonUeberwach, die öffentlichen Ausgänge xSchuetz und xStoerMeldung sowie die Eigenschaft Betriebsstunden. FB_AntriebReversierbar erbt von FB_Antrieb, ergänzt den Ausgang xSchuetzLinks, die Methode StartenLinks und überschreibt Stoppen (mit KI erstellt)

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, a für den Datentyp; fb, ton für Instanzen) und sprechende Namen mit Bezug zum Betriebsmittel (xB1_Lichtschranke statt x7).

  • 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:

Testprotokoll für FB_Antrieb (Ausschnitt)
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:

  1. Pflichtenheft lesen und die geforderte Änderung in Anforderungen übersetzen (was soll sich wann wie verhalten?). Unklarheiten mit dem Auftraggeber klären, bevor programmiert wird.

  2. 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.

  3. Ä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).

  4. Ändern und kommentieren (Änderungshistorie im Kopfkommentar).

  5. Testen in der Simulation: neue Testfälle für die Änderung und die alten Testfälle erneut (Regressionstest), damit nichts Bewährtes kaputtgeht.

  6. 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.

Virtuelle SPS und KI-Assistenten – Chancen und Grenzen
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?

Leitfragen

  1. 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?“

  1. 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“

  1. 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“

  1. 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“

  1. 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“

  1. 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“

  1. 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“

  1. 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“


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]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: