Anlagen-Simulation mit der SPS (Teil 1)

Durch meinen aktuellen Job stehe ich regelmäßig vor der Herausforderung Menschen das Programmieren einer SPS näher zu bringen. Dazu ist eine industrielle Anlage natürlich hilfreich, aber nicht jeder hat eine Anlage rumstehen, an der man mal rumspielen kann. Es gibt natürlich Modelle für den Unterricht, aber auch die kosten Geld und nicht jedes Modell eignet sich für jedes Programmierproblem.

Es geht aber auch ohne viel monetären Einsatz, sozusagen in Software. Ich möchte hier eine kleine Einführung geben, wie es mit einfachen Mitteln möglich ist, eine virtuelle Anlage in einer SPS abzubilden. Da ich selbst mit Siemens Steuerungen und dem TIA-Portal arbeite wird auch dieser Artikel diese Dinge als Grundlage nehmen. Es sollte aber auch mit jeder anderen Steuerung möglich sein.

Benötigt wird eine industrielle Steuerung, wie z.B. eine S7-1500 und ein HMI-Panel, welches mit dieser Steuerung kommunizieren kann. Beides kann auch simuliert werden, aber der Anfang ist leichter, wenn erstmal nur die physikalische Anlage simuliert wird. Ich werde hier nutze für mein Beispiel hier ein S7-1215C und die WinCC Advanced Runtime auf meinem Programmiergerät als HMI.

Die S7-1215C durchläuft, so wie die meisten anderne industriellen Steuerung auch, einen Zyklus mit den folgenden Schritten:

  1. Einlesen der Eingänge
  2. Abarbeiten des Programmes
  3. Ausgabe der Ausgänge

Die physikalische Anlage muss man sich in diesem Ablauf zwischen dem Punkt 3 und 4 vorstellen. Die Maschine stellt der Steuerung Signale zur Verfügung, welche im ersten Schritt gelesen werden. Die Steuerung wiederum steuert mit dem Schritt 3 die eigentlichen Aktuatoren der Anlage. Es wird also etwas benötigt, was der SPS die Eingangssignale zur Verfügung stellt und deren Ausgangssignale verarbeitet. Und dafür kann in der Steuerung selbst einfach ein Baustein (FB) genommen werden.

Dieser Simulationsbaustein kann natürlich nicht irgendwann aufgerufen werden, sondern muss als erster Baustein in der Steuerung ausgeführt werden, da er die physikalischen Eingangssignale mit simulierten überschreiben muss. Dann kann das restliche Programm der Steuerung mit diesen simulierten Signalen arbeiten und die Ausgänge setzen. Die physikalischen Ausgänge wiederum müssen dem Simulationsbaustein übergeben werden, damit er diese verarbeiten kann und die notwendigen Eingangssignale berechnen kann. Und genau da fangen wir nun mal an.

Der Simulationsbaustein

Wichtig ist natürlich sich zu überlegen, was simuliert werden soll. Nehmen wir den nebenstehenden Reaktor als Beispiel.

Insgesamt hat diese Anlage 5 Aktuatoren, 4 Sensoren und 2 Taster.

  • Aktuatoren: Heizung H, Motor M, 3 Ventile Y1 bis Y3
  • Sensoren: 3 Füllstandssensoren S2 bis S4, Temperatursensor S5
  • Taster: S1, RESET

Die Aktuatoren sind die Teile, welche die SPS als Ausgänge ansteuert und die virtuelle Anlage als Eingänge entgegennehmen muss. Die Sensoren und Taster sind Eingänge, welche durch die Steuerung entgegen genommen werden, und somit Ausgänge unserer virtuellen Anlage.

Der Baustein der die virtuelle Anlage abbildet benötigt also fünf Eingänge und sechs Ausgänge. Der Einfachheit halber nehme ich hier für alle Sensoren und Aktuatoren digitale Bauteile. Der Temperatursensor liefert also keinen Analogwert, sondern schaltet bei erreichter Temperatur auf 1. Der Baustein kann weiterhin kompakt gehalten werden, indem er die Ein- und Ausgänge nicht als einzelne Boolsche Variablen entgegennimmt, sondern Byte- oder Wortweise. In SCL könnte der Baustein nun etwa so aussehen.

FUNCTION_BLOCK "Virtueller_Reaktor"
VERSION : 0.1
  VAR_INPUT 
    PAA_Byte : Byte;   // Ausgangsbyte der Steuerung
  END_VAR

  VAR_OUTPUT 
    PAE_Byte : Byte;   // Eingangsbyte der Steuerung
  END_VAR

  VAR 
    DI : Struct
      S1 : Bool;   // Taster S1 (NO)
      S2 : Bool;   // Niveausensor Unten (NO)
      S3 : Bool;   // Niveausensor Mitte (NO)
      S4 : Bool;   // Niveausensor Oben (NO)
      S5 : Bool;   // Temperatursensor (NO)
      RESET : Bool;   // Taster RESET (NC)
      RES06 : Bool;   // reserviertes, freies Bit
      RES07 : Bool;   // reserviertes, freies Bit
    END_STRUCT;
    DQ : Struct
      Y1 : Bool;   // Zulaufventil 1
      Y2 : Bool;   // Zulaufventil 2
      Y3 : Bool;   // Ablassventil
      H : Bool;   // Heizung
      M : Bool;   // Motor
      RES05 : Bool;   // reserviertes, freies Bit
      RES06 : Bool;   // reserviertes, freies Bit
      RES07 : Bool;   // reserviertes, freies Bit
    END_STRUCT;
  END_VAR
BEGIN
  // Ausgänge einlesen
  #DQ.Y1 := #PAA_Byte.%X0;
  #DQ.Y2 := #PAA_Byte.%X1;
  #DQ.Y3 := #PAA_Byte.%X2;
  #DQ.H := #PAA_Byte.%X3;
  #DQ.M := #PAA_Byte.%X4;
	
  // Eingänge ausgeben
  #PAE_Byte.%X0 := #DI.S1;
  #PAE_Byte.%X1 := #DI.S2;
  #PAE_Byte.%X2 := #DI.S3;
  #PAE_Byte.%X3 := #DI.S4;
  #PAE_Byte.%X4 := #DI.S5;
  #PAE_Byte.%X5 := NOT #DI.RESET;
  #PAE_Byte.%X6 := FALSE;
  #PAE_Byte.%X7 := FALSE;
END_FUNCTION_BLOCK

Die beiden Strukturen dienen des einfachen Zugriffs auf die Ein- und Ausgänge im Baustein selbst. Dafür müssen die Bytes in die Struktur geschoben bzw. die Struktur in die Bytes geschoben werden. Dies wird hier auf der S7-1200 mit Hilfe des Slice-Zugriffs gemacht, kann aber auch anders gelöst werden.

Es fehlt natürlich noch die Logik und vor allem der Zustand der Anlage. Der physikalische Zustand der Anlage kann in zwei Worten zusammengefasst werden: Füllstand und Temperatur.

Die oben abgebildete Anlage hat als Variable Anteile einen Füllstand und eine Temperatur. Die Sensoren sind direkt abhängig vom Wert dieser Variablen. Ist der Tank leer, sind die Sensoren S2 bis S4 aus. Füllt sich der Tank, so schalten sich nach und nach die Sensoren ein. Das selbe gilt für die Temperatur. Definieren wir den Füllstand als einen Wert in Prozent und für die Temperatur einen Wert in Grad Celsius, dann erweitert sich das Programm um folgende Zeilen.

FUNCTION_BLOCK "Virtueller_Reaktor"
VERSION : 0.1
  ...

  VAR 
    ...
    Fuellstand : Int := 0;  // Füllstand des Tankes in Prozent mal 10
    Temperatur : Int := 200; // Temperatur im Tank in Grad Celsius mal 10
  END_VAR
BEGIN
  ...

  // Eingänge auswerten
  DI.S2 := (Fuellstand > 0);  // S2 setzen, wenn Füllstand größer  0% ist
  DI.S3 := (Fuellstand > 500); // S3 setzen, wenn Füllstand größer 50% ist
  DI.S4 := (Fuellstand > 800); // S4 setzen, wenn Füllstand größer 80% ist
  DI.S5 := (Temperatur > 400); // S5 setzen, wenn Temperatur über 40°C ist
  
  ...
END_FUNCTION_BLOCK

Damit werden die Sensoren also durch den Zustand der Anlage gesteuert. Als nächstes muss nun der Zustand der Anlage durch die Ausgänge verändert werden. Schaltet die Steuerung das Ventil Y1 oder Y2 ein, so fließt Wasser in den Reaktor und der Füllstand muss sich folglich erhöhen. Sollte Y3 geöffnet werden, so muss der Füllstand verringert werden und wird die Heizung eingeschaltet, so muss die Temperatur sich erhöhen.

Da Füllstand und Temperatur sich nur langsam erhöhen bzw. verringern sollen, muss dies zeitlich getaktet werden. Anstatt die Manipulation der Zustände jeden Zyklus vorzunehmen, begrenzen wir es auf alle 100ms. Es wäre auch möglich es jeden 100. Zyklus durchzuführen, aber mit der zeitlichen Lösung könnten auch Regelstrecken simuliert werden, da sich der Baustein dadurch von der Zykluszeit entkoppelt. Den Takt gestalte ich hier mit einer Einschaltverzögerung, welche sich selbst zurücksetzt und deren positive Flanke den Rest des Bausteins aktiviert.

FUNCTION_BLOCK "Virtueller_Reaktor"
VERSION : 0.1
  ...

  VAR 
    ...
    IEC_Timer_Taktsignal : TON_TIME;
    Abtastflanke : Bool;
  END_VAR
BEGIN
  // Abtastflanke erzeugen
  #IEC_Timer_Taktsignal(IN := NOT #Abtastflanke,
	                PT := T#100ms,
	                Q => #Abtastflanke);
  // Baustein verlassen, sofern die 100ms nicht abgelaufen sind
  IF NOT #Abtastflanke THEN
    RETURN;
  END_IF;

  // Ausgänge einlesen
  ...

  // Füllstand erhöhen / verringern
  IF #DQ.Y1 THEN
    #Fuellstand := #Fuellstand + 5;
  END_IF;
  IF #DQ.Y2 THEN
    #Fuellstand := #Fuellstand + 5;
  END_IF;
  IF #DQ.Y3 THEN
    #Fuellstand := #Fuellstand - 5;
  END_IF;

  // Füllstand begrenzen
  #Fuellstand := LIMIT(IN := #Fuellstand, MN := 0, MX := 1000);

  // Temperatur berechnen
  IF #DQ.H THEN
    #Temperatur := #Temperatur + 5;
  ELSE
    #Temperatur := #Temperatur - 1;
  END_IF;

  // Temperatur begrenzen
  #Temperatur := LIMIT(IN := #Temperatur, MN := 0, MX := 1000);

  // Eingänge auswerten
  ...
END_FUNCTION_BLOCK

Zugegeben dies lässt noch viel Platz für Verbesserungen, aber es erfüllt erstmal seinen Job. Der Temperaturan- bzw. abstieg könnte zum Beispiel noch vom Füllstand abhängig gemacht werden. Die Ventile könnten unterschiedliche Druchflussgeschwindigkeiten bekommen, oder gar eine Regelstrecke. Wichtiger wäre aber vielleicht erstmal die Simulation in Betrieb zu nehmen. Dafür definiere ich folgende Ein- und Ausgängsvariablen im Projekt und beschalte den Baustein.

VariableDatentypAdresse
S1BoolE0.0
S2BoolE0.1
S3BoolE0.2
S4BoolE0.3
S5BoolE0.4
RESETBoolE0.5
PAEByteEB0
Y1BoolA0.0
Y2BoolA0.1
Y3BoolA0.2
HBoolA0.3
MBoolA0.4
PAAByteAB0

Die beiden Variablen PAA und PAE und verschalte ich mit der virtuellen Anlage. Nun kann mein SPS-Programm ganz normal mit den Booleschen Variablen arbeiten und die Aus- und Eingänge werden in einem Stück an die virtuelle Maschine gereicht.

Zum Testen des Simulation reicht erstmal ein einfaches Programm, welches die Ventile und die Heizung mit Hilfe von Merkern ansteuert. Danach kann der virtuelle Reaktor beobachtet und die Merker gesteuert werden. Die Variablen Fuellstand und Temperatur sollten sich nun, abhängig von den gesteuerten Ausgängen bzw. Merkern, verändern.

Weiter geht es danach im zweiten Teil mit der Visualisierung der Simulation.