Objektorientierte Programmierung (OOP) war bisher eine typische Domäne im IT Bereich. Typische Programmiersprachen sind C++, Java oder C#. Mit CoDeSys V3 steht dieses Konzept nun auch dem SPS-Programmierer zur Verfügung.
Die Komplexität von SPS-Programmen nimmt ständig zu. Auch ist es keine Ausnahme mehr, dass an einem Programm mehrere Entwickler arbeiten. Um diesen Anforderungen gerecht zu werden, wurde die Funktionalität der Funktionsblöcke in CoDeSys V3 erweitert.
Methoden
Bisher bestand ein Funktionsblock aus internen Variablen, Eingangsvariablen und Ausgangsvariablen. Es gab nur eine Möglichkeit, die internen Variablen von außen zu verändern, nämlich durch den Aufruf einer entsprechenden Instanz mit den jeweiligen Eingangsvariablen. Je nach Zustand der Eingangsvariablen wurden unterschiedliche Bereiche im Funktionsblock ausgeführt und dadurch die internen Variablen geändert. Bei jeder gewünschten Aktivierung einer Funktionalität musste die Instanz mit unterschiedlichen Eingangsvariablen aufgerufen werden. Beispiel:
Motor starten:
fbEngine(bStart := true, bStop := false, iGear := 1);
Motor stoppen:
fbEngine(bStart := false, bStop := true);
Innerhalb des Funktionblocks wurde auf eine positive Flanke der Variablen bStart und bStop geachtet. Der Anwender des Funktionsblocks musste wissen, dass aus diesem Grund auch beim Stoppen die Variable bStart auf FALSE gesetzt werden muss. Ansonsten blieb die Eingangsvariable bStart auf TRUE. Bei jedem weiteren Versuch, den Motor neu zu starten, würde hierdurch keine steigende Flanke entstehen. Auch musste bekannt sein, dass beim Starten des Motors der Parameter iGear mit angegeben werden muß. Beim Stoppen war dieser nicht notwendig. Mit den OOP-Erweiterungen findet eine deutlichere Trennung zwischen den internen Variablen und den Möglichkeiten diese zu ändern statt. Es ist möglich, weitere ‘Unterfunktionen’ (Fachbegriff: Methoden) innerhalb des Funktionsblocks zu definieren, die der Anwender des Funktionsblocks aufrufen kann. Methoden können Parameter enthalten, so wie übliche Funktionen. Somit könnte das gleiche Beispiel wie folgt aussehen:
Motor starten:
fbEngine.M_Start(iGear := 1);
Motor stoppen:
fbEngine.M_Stop();
Die Methoden M_Start() und M_Stop() können auf die internen Variablen des Funktionsblocks zugreifen, aber auch eigene Variablen enthalten, die weder von außen noch durch andere Methoden des Funktionsblocks verändert werden können. Ein unbeabsichtigtes Überschreiben wird hierdurch verhindert.
Eigenschaften
Neben den Methoden gibt es noch eine verbesserte Möglichkeit, allgemeine Parameter und Zustände eines Funktionsblocks zu definieren. Parameter konnten bisher nur durch den Aufruf des Funktionsblocks als Eingangsvariablen übergeben werden. Zustände wurden durch die Ausgangsvariablen nach außen weitergegeben. Mit den sogenannten Eigenschaften (engl.: Properties) gibt es einen definierten Weg, allgemeine Parameter und Zustände unabhängig vom Aufruf des Funktionsblocks zu übergeben. Das besondere an den Eigenschaften ist, dass der Zugriff durch zwei besondere Methoden durchgeführt wird. Es gibt eine Methode für das Schreiben und eine für das Lesen der Eigenschaft. Diese besonderen Methoden werden auch als ‘Setter’ (schreiben) und ‘Getter’ (lesen) bezeichnet. Innerhalb dieser Methoden kann z.B. eine Bereichsüberprüfung stattfinden oder eine Einheitenumrechnung. Der Aufruf dieser Methoden erfolgt implizit durch den Zuweisungsoperator (:=) oder durch die Vergleichsoperatoren (==, , =, ). Das obere Beispiel könnte durch zwei Eigenschaften erweitert werden. Eine für die Vorgabe der maximal zulässigen Geschwindigkeit (P_MaxVelocity) und eine weitere zur Ausgabe der aktuellen Temperature (P_Temperature).
fbEngine.P_MaxVelocity := 80; fbEngine.M_Start(iGear := 1); IF (fbEngine.P_Temperature >= 130) THEN fbEngine.M_Stop(); END_IF
Es müssen nicht immer Get- und Set Funktionen für eine Eigenschaft gleichzeitig vorhanden sein. Fehlt z.B. die Set Funktion, so kann die Eigenschaft nur gelesen werden. In CoDeSys V3 sieht die Definition des Funktionsblocks wie folgt aus:
Vererbung
Funktionsblöcke eignen sich sehr gut um Programmteile voneinander zu kapseln. Hierdurch kann eine bessere Strukturierung der Software erreicht werden. Die Wiederverwendung wird ebenfalls vereinfacht. Bisher war es allerdings immer kritsch, einen vorhandener Funktionsblock in seinem Funktionsumfang zu erweitert. Entweder musste der Quellcode angepasst, oder es wurde ein neuer Funktionsblock um den vorhandenen programmiert. Der existierende Funktionsblock wurde sozusagen in einen anderen, einen neuen Funktionsblock eingebettet. Bei der letzteren Variante mussten alle Eingangsvariablen erneut angelegt und den Eingangsvariablen des schon vorhandenen Funktionsblocks zugewiesen werden. Das gleiche musste, nur im umgekehrter Richtung, bei den Ausgangsvariablen gemacht werden.
Mit CoDeSys V3 wird das Prinzip der Vererbung eingeführt. Vererbung ist ein Grundprinzip der objektorientierten Programmierung. Bei der Vererbung wird ein neuer Funktionsblock von einem bestehenden Funktionsblock abgeleitet. Dieser kann anschließend erweitert werden. Dabei ‘erbt’ der neue Funktionsblock alle Eigenschaften und Methoden des Basisfunktionsblocks. Jeder Funktionsblock kann beliebig viele Nachfolger haben, aber er kann lediglich einen Vorgänger (Basisfunktionsblock) besitzen. Das Ableiten eines Funktionsblocks geschieht bei der Deklaration des neuen Funktionsblocks. Die Anweisung EXTENDS wird, mit Angabe des Basisfunktionsblocks, hinter den Namen des neuen Funktionsblocks angegeben. Beispiel:
FUNCTION_BLOCK FB_DimmingLight EXTENDS FB_Light
Nach dem Ableiten besitzt der neue Funktionsblock alle Eigenschaften und Methoden des Basisfunktionsblock.
Beispiel
Das folgende Beispiel soll das oben gezeigte verdeutlichen. Es geht hierbei um vier einfache(!) Funktionsblöcke für verschiedene Varianten der Beleuchtung. FB_Light ist der einfachste Funktionsblock und nur in der Lage, mit den Methoden M_On() und M_Off() eine Lampe ein- und auszuschalten. Von FB_Light werden zwei weitere Funktionsblöcke abgeleitet, FB_DimmingLight und FB_OutdoorLight. FB_DimmingLight besitzt zusätzlich eine Eigenschaft für die analoge Stellgröße der Lampe und eine Methode, um diese Stellgröße zu verändern. FB_OutdoorLight dient dazu, eine Lampe in Abhängigkeit der aktuellen Außenhelligkeit zu schalten. Deshalb hat dieser Funktionsblock eine Eigenschaft für die Außenhelligkeit und eine Eigenschaft für den Schwellwert. Der vierte Funktionsblock (FB_StairwellLight) erbt von FB_DimmingLight und soll eine typische Treppenhaussteuerung realisieren. Hierzu hat der Funktionsblock zwei weitere Eigenschaften. Eine für die Schaltzeit (P_PresenceDuration) und eine für die Nachlaufzeit (P_ProlongDuration). Während der Schaltzeit ist die Lampe auf 100 % Helligkeit. Nach der Zeit P_PresenceDuration geht die Lampe automatisch auf 50 % Helligkeit und schaltet nach der Zeit P_ProlongDuration aus.
Die Vererbungshierarchie kann durch grafische Diagramme dokumentiert werden. Als Standard hat sich hierbei die Unified Modeling Language (UML) durchgesetzt. UML definiert verschiedene Arten von Diagrammen, die sowohl die Struktur als auch das Verhalten von Software beschreiben. Für das Beschreiben der Vererbungshierarchie von Funktionsblöcken eignet sich die Variante Klassendiagramm. UML Diagramme können z.B. mit Microsoft Visio erstellt werden. Von unserem Beispiel sieht das Klassendiagramm wie folgt aus (erstellt mit Microsoft Visio 2010):
Jedes Kästchen steht für einen Funktionsblock und ist immer in drei waagerechte Bereiche aufgeteilt. Im obersten Bereich steht der Name. Im mittleren Bereich ist die Auflistung der Eigenschaften und im unteren Bereich sind alle Methoden aufgeführt. Die Pfeile geben die Richtung der Vererbung an und zeigen immer auf den Basisfunktionsblock.
Zum weiteren Verständnis soll der Funktionsblock FB_DimmingLight genauer betrachtet werden. Dieser Funktionsblock ist von FB_Light abgeleitet. Somit erbt FB_DimmingLight die Eigenschaft P_ControlValue und die Methoden M_On() und M_Off() von FB_Light. M_On() und M_Off() von FB_Light ändern die Eigenschaft P_ControlValue. Eigentlich besitzt FB_DimmingLight schon die Methoden M_On() und M_Off(). Doch die geerbten Methoden von FB_Light kennen die Eigenschaft P_DimmingValue nicht. Deshalb werden in FB_DimmingLight die Methoden M_On() und M_Off() erneut angelegt. Wird eine Instanz von FB_DimmingLight angelegt und M_On() aufgerufen, so wird nicht die Methode von FB_Light ausgeführt, sondern die von FB_DimmingLight. M_On() von FB_DimmingLight ‘überschreibt’ M_On() von FB_Light. In M_On() kann jetzt die lokale Variable, die von der Eigenschaft P_DimmingValue zurückgegeben wird, entsprechend gesetzt werden. Anschließend soll aber die Methode M_On() des Basisfunktionsblocks FB_Light aufgerufen werden. Würde man nur M_On() programmieren, so wird die eigene Methode M_On() erneut aufgerufen. Für den Zugriff auf die Methoden und Eigenschaften des Basisfunktionsblocks dient der Zeiger ‘SUPER’. Dieses Schlüsselwort ist fest vorgegeben und wird von der Laufzeitumgebung automatisch initialisiert. Die Methode M_On() von FB_DimmingLight sieht dementsprechend wie folgt aus:
METHOD M_On nDimmingValue := 100; SUPER^.M_On();
Und für M_Off():
METHOD M_Off nDimmingValue := 0; SUPER^.M_Off();
Die Methode M_SetDimmingValue() verändert die Eigenschaft P_DimmingValue, soll aber auch gleichzeitig die Eigenschaft P_ControlValue passend setzen. Wird der Dimmwert auf 0 gesetzt, so soll P_ControlValue ebenfalls auf FALSE gesetzt werden. Wird für den Dimmwert ein Wert von 1 oder größer eingegeben, so soll P_ControlValue auf TRUE gesetzt werden. Die Methode M_SetDimmingValue() könnte wie folgt implementiert werden:
METHOD M_SetDimmingValue VAR_INPUT nNewValue : BYTE; END_VAR IF (nNewValue > 100) THEN nDimmingValue := 100; SUPER^.M_On(); ELSE nDimmingValue := nNewValue; IF (nNewValue > 0) THEN SUPER^.M_On(); ELSE SUPER^.M_Off(); END_IF END_IF
Die Methode M_SetDimmingValue() hat einen Eingangsparameter; nNewValue. Dieser gibt die neue analoge Stellgröße zwischen 0 und 100 vor. Als erstes wird der Wertebereich des Parameters überprüft. Bei einem Wert größer 100 wird die analoge Stellgröße auf 100 begrenzt und vom Basisfunktionsblock die Methode M_On() aufgerufen. Dieser setzt die geerbte Eigenschaft P_ControlValue auf TRUE. Ansonsten wird die analoge Stellgröße auf den neuen Wert gesetzt. Ist dieser 0, so wird auch P_ControlValue auf FALSE gesetzt, ansonsten auf TRUE. Die Variable nDimmingValue ist eine lokale Variable und wird durch die Eigenschaft P_DimmingValue nach außen offen gelegt.
Eine schöne kleine Vorstellung der neuen Konzepte. Als Entwickler, der über C,C++,C# die meisten Projekte abgewickelt hat, sage ich, es wird besser aber die SPS ist immer noch 20 Jahre hinter der Technik zurück.
Unverständlich ist mir, dass keine Exceptions verwendet werden, die Adress Arithmetik nur rudimentär vorhanden ist, Referenzen mit VAR_IN_OUT nur in FB aber nicht in Funktionen angewendet werden können, und eine unsäglich umständliche Tipperei entsteht, da IF,END_IF etc. nicht einfach durch Blockstrukturen wie
if {….} .
Wie Richie mal sagte:”C ist keine geschwätzige Sprache”.