| 

.NET C# Java Javascript Exception

2
Eine bewährte Methode um die Komplexität von Softwaresystemen zu bewältigen ist die objektorientierte Programmierung (OOP). Bisher eine Domäne von Programmiersprachen wie C++, Java oder C#, steht dieses Konzept mit der IEC 61131-3 auch der SPS-Programmierung zur Verfügung. Eine kurze Einführung bietet mein Post IEC 61131-3: Vorteile der objektorientierten Spracherweiterungen bei CoDeSys V3 aus dem Jahr […]

Eine bewährte Methode um die Komplexität von Softwaresystemen zu bewältigen ist die objektorientierte Programmierung (OOP). Bisher eine Domäne von Programmiersprachen wie C++, Java oder C#, steht dieses Konzept mit der IEC 61131-3 auch der SPS-Programmierung zur Verfügung.

Eine kurze Einführung bietet mein Post IEC 61131-3: Vorteile der objektorientierten Spracherweiterungen bei CoDeSys V3 aus dem Jahr 2010. Dort werden die Entstehung der objektorientierten Programmierung sowie deren Eigenschaften erläutert.

Dieser Post ist eine überarbeitete Version von IEC 61131-3: Methoden, Eigenschaften und Vererbung und ebenfalls aus dem Jahr 2010. Neben einigen Anpassungen wurden außerdem einige Themen hinzugefügt, wie z.B. die Zugriffsbezeichner (Access Specifier).

Methoden

Bisher bestand ein Funktionsblock aus internen Variablen, Eingangs- und Ausgangsvariablen. Es gab nur eine Möglichkeit, die internen Variablen von außen zu verändern. Die entsprechende Instanz des Funktionsblocks wurde mit den jeweiligen Eingangsvariablen aufgerufen. Je nach Zustand der Eingangsvariablen wurden unterschiedliche Bereiche im Funktionsblock ausgeführt und dadurch die internen Variablen verändert. Beispiel:

Motor starten:

fbEngine(bStart := true, bStop := false, nGear := 1, fVelocity := 7.5);

Motor stoppen:

fbEngine(bStart := false, bStop := true);

Innerhalb des Funktionsblocks 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 musste. Ansonsten würde bei jedem weiteren Versuch, den Motor neu zu starten, keine steigende Flanke erkannt werden. Auch musste bekannt sein, dass beim Starten des Motors der Parameter nGear mit anzugeben ist. Beim Stoppen war dieses 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 (Methoden) innerhalb des Funktionsblocks zu definieren, die der Anwender aufrufen kann.

Methoden sind vergleichbar mit Aktionen. Beim Aufruf können aber Parameter übergeben werden. Auch können Methoden lokale Variablen enthalten, so wie es bei Funktionen möglich ist. Somit könnte das obige Beispiel wie folgt mit dem Einsatz von Methoden umgesetzt werden:

Motor starten:

fbEngine.Start(nGear := 1, fVelocity := 7.5); 

Motor stoppen:

fbEngine.Stop(); 

Die Methoden Start() und Stop() können auf interne Variablen des Funktionsblocks zugreifen, aber auch eigene Variablen enthalten, die weder von außen noch durch andere Methoden des Funktionsblocks veränderbar sind. Ein unbeabsichtigtes Überschreiben wird hierdurch verhindert.

Ebenso können Methoden einen Rückgabewert enthalten, welcher an den Aufrufer der Methode zurückgegeben wird. Falls notwendig, können weitere Ausgangsvariablen zwischen VAR_OUTPUT und END_VAR deklariert werden. Diese Möglichkeit besteht seit TwinCAT 3 auch bei Funktionen. Auch ist es seit TwinCAT 3 erlaubt, Funktionen oder Methoden ohne Rückgabewert zu deklarieren.

Nur POUs vom Typ FUNCTION_BLOCK oder PROGRAM können Methoden enthalten. Bei einer POU vom Typ FUNCTION ist dieses nicht möglich.

Beim Aufruf einer Methode müssen immer alle Parameter angegeben werden. Hierbei kann alternativ auf die Parameternamen verzichtet werden:

fbEngine.Start(1, 7.5);

Werden die Parameternamen explizit angegeben, so ist die Reihenfolge der Parameter beliebig:

fbEngine.Start(fVelocity := 7.5, nGear := 1); 

Methoden werden in den graphischen Darstellungsarten durch eine separate Box aufgerufen:

Einige Programmiersprachen bieten die Möglichkeit, Methoden mit gleichen Namen mehrfach anzulegen. Diese müssen sich dann durch die Parameter (Signatur) unterscheiden. Dieses wird als Überladen bezeichnet. Bisher können Methoden in der IEC 61131-3 nicht überladen werden. Es findet keine Differenzierung der Methoden über die Signatur statt. Allein der Name der Methode muss eindeutig sein.

Access Specifier

Optional kann bei der Deklaration einer Methode ein Zugriffsbezeichner (Access Specifier) angegeben werden. Dieser schränkt den Zugriff auf die jeweilige Methode ein.

PUBLICJeder darf die Methode aufrufen. Es gibt keine Einschränkungen.
PRIVATE Die Methode steht nur innerhalb des POUs zur Verfügung. Von Außen kann die Methode nicht aufgerufen werden.
PROTECTED Auf die Methode kann nur der eigene POU oder seine Ableitungen zugreifen. Der Begriff der Ableitung wird weiter unten noch erklärt.
INTERNAL Auf die Methode kann nur aus dem eigenen Namensraum zugegriffen werden. Hiermit können z.B. bestimmte Methoden nur innerhalb einer Bibliothek zur Verfügung gestellt werden.
FINAL Die Methode kann nicht durch eine andere Methode überschrieben werden. Das Überschreiben von Methoden wird weiter unten noch erklärt.

Wird auf den Zugriffsbezeichner verzichtet, so ist PUBLIC die Standardeinstellung.

Die Deklaration einer Methode hat somit den folgenden Aufbau:

METHOD <Zugriffsbezeichner> <Name> : <Rückgabetyp> 

Eigenschaften

Parameter konnten bisher nur über den Aufruf des Funktionsblocks und Eingangsvariablen übergeben werden. Zustände wurden durch Ausgangsvariablen nach außen weitergegeben. Mit den sogenannten Eigenschaften (engl.: Properties) gibt es einen fest 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 spezielle Methoden durchgeführt wird. Es gibt eine Methode für das Schreiben und eine für das Lesen der Eigenschaft. Diese Methoden werden auch als Setter (schreiben) und Getter (lesen) oder allgemein Accessor bezeichnet. Innerhalb dieser Methoden kann z.B. eine Bereichsüberprüfung oder eine Einheitenumrechnung stattfinden.

Setter und Getter können lokale Variablen enthalten, aber keine zusätzlichen Ein- und Ausgänge. Die lokalen Variablen verlieren beim Verlassen einer Setter- oder Getter Methode allerdings ihren Wert. Somit verhalten sich diese wie Funktionen. Aus diesem Grund muss der Setter einen entsprechenden Mechanismus bereitstellen, damit der Wert der Eigenschaft erhalten bleibt. Hierzu kann z.B. im Funktionsblock eine lokale Variable deklariert werden.

FUNCTION_BLOCK PUBLIC FB_Engine
VAR
 myPropertyInternalValue : LREAL := 50;
END_VAR 

Im Setter wird der Wert der Eigenschaft MyProperty an die lokale Variable übergeben.

myPropertyInternalValue := MyProperty; 

Der Getter geht den umgekehrten Weg und gibt den Wert der lokalen Variablen zurück an die Eigenschaft.

MyProperty := myPropertyInternalValue; 

Die lokale Variable myPropertyInternalValue kann im Funktionsblock für die internen Berechnungen genutzt werden. Der Zugriff auf diese Variable ist nur über die Eigenschaft MyProperty möglich.

Das obere Beispiel soll um drei Eigenschaften erweitert werden. Eine für die Vorgabe der maximal zulässigen Geschwindigkeit (MaxVelocity) sowie zwei weitere zur Ausgabe der aktuellen Temperatur (Temperature) und der aktuellen Geschwindigkeit (Velocity).

In den graphischen Darstellungsarten werden Eigenschaften nicht an der jeweiligen Box dargestellt. Der Zugriff erfolgt durch den Namen der Instanz und der Eigenschaft, getrennt durch einen Punkt.

fbEngine(); 
bError := fbEngine.Temperature > 130;

Getter und Setter müssen nicht gleichzeitig vorhanden sein. Fehlt z.B. der Setter, so kann die Eigenschaft nur gelesen werden. In TwinCAT 3 sieht die Definition des Funktionsblocks mit den Methoden und Eigenschaften wie folgt aus:

Access Specifier

Wie bei Methoden sind auch die Zugriffsbezeichner PUBLIC, PRIVATE, PROTECTED, INTERNAL und FINAL erlaubt. Wird kein Zugriffsbezeichner angegeben, so ist die Eigenschaft PUBLIC. Zusätzlich lässt sich an jedem Getter und Setter ein Zugriffsbezeichner angeben. Dieser hat Vorrang vor dem Zugriffsbezeichner der Eigenschaft.

Die Deklaration einer Eigenschaft hat somit den folgenden Aufbau:

PROPERTY <Zugriffsbezeichner> <Name> : <Datentyp> 

Vererbung

Funktionsblöcke eignen sich sehr gut um Programmteile voneinander zu trennen. Hierdurch kann eine bessere Strukturierung der Software erreicht und die Wiederverwendung deutlich vereinfacht werden. Bisher war es allerdings immer kritisch, einen vorhandenen Funktionsblock in seinem Funktionsumfang zu erweitern. Entweder musste der Quellcode angepasst, oder ein neuer Funktionsblock um den vorhandenen herum programmiert werden. 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 in umgekehrter Richtung, bei den Ausgangsvariablen gemacht werden.

Mit TwinCAT 3 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, wenn dessen Zugriffsbezeichner (Access Specifier) dieses zulässt. Jeder Funktionsblock kann beliebig viele Nachfolger haben, aber er kann lediglich nur 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 PUBLIC FB_NewEngine EXTENDS FB_Engine 

Nach dem Ableiten besitzt der neue Funktionsblock (FB_NewEngine) alle Eigenschaften und Methoden des Basisfunktionsblocks (FB_Engine). Methoden und Eigenschaften werden aber nur dann vererbt, wenn der Zugriffsbezeichner (Access Specifier) dieses zulässt.

Auch werden alle lokalen Variablen, VAR_INPUT, VAR_OUTPUT und VAR_IN_OUT des Basisfunktionsblocks an den neuen Funktionsblock vererbt. Dieses Verhalten kann durch Zugriffsbezeichner nicht eingeschränkt werden.

Wurden Methoden oder Eigenschaften im Basisfunktionsblocks als PROTECTED deklariert, so kann zwar der abgeleitete Funktionsblock (FB_NewEngine) darauf zugreifen, aber außerhalb von FB_NewEngine ist dieses nicht möglich.

Vererbung ist nur bei POUs vom Typ FUNCTION_BLOCK möglich.

Zugriffsbezeichner (Access Specifier)

Bei der Deklaration eines FUNCTION_BLOCK, FUNCTION oder PROGRAM kann ein Zugriffsbezeichner (Access Specifier) angegeben werden. Dieser schränkt den Zugriff und die Möglichkeiten der Vererbung bei Bedarf ein.

PUBLIC Jeder darf den POU aufrufen bzw. eine Instanz davon anlegen. Auch darf der POU, wenn dieser ein FUNCTION_BLOCK ist, für die Vererbung genutzt werden. Es gibt keine Einschränkungen.
INTERNAL Der POU kann nur im eigenen Namensraum verwendet werden. Hiermit können z.B. bestimmte POUs nur innerhalb einer Bibliothek zur Verfügung gestellt werden.
FINAL Der FUNCTION_BLOCK kann nicht als Basisfunktionsblock verwendet werden. Ein Erben von diesem POU ist nicht zulässig. FINAL ist nur bei POUs vom Typ FUNCTION_BLOCK erlaubt.

Wird auf den Zugriffsbezeichner verzichtet, so ist PUBLIC die Standardeinstellung. Die Zugriffsbezeichner PRIVATE und PROTECTED sind bei der Deklaration von POUs nicht zulässig.

Soll die Vererbung genutzt werden, so hat die Deklaration eines Funktionsblocks folgenden Aufbau:

FUNCTION_BLOCK <Zugriffsbezeichner> <Name> : EXTENDS <Name Basisfunktionsblock>

Überschreiben von Methoden

Der neue FUNCTION_BLOCK FB_NewEngine, der von FB_Engine geerbt hat, kann jetzt zusätzliche Eigenschaften und Methoden erhalten. Als Beispiel wird die Eigenschaft Gear hinzugefügt. Über diese Eigenschaft kann der aktuelle Gang abgefragt und verändert werden. Dementsprechend werden Getter und Setter der Eigenschaft erstellt.

Allerdings muss sichergestellt werden, dass der Parameter nGear der Methode Start() an diese Eigenschaft übergeben wird. Da der Basisfunktionsblock FB_Engine keinen Zugriff auf die neue Eigenschaft hat, wird in FB_NewEngine die Methode mit genau dem gleichen Parameter neu anlegt. Der vorhandene Code wird in die neue Methode kopiert und so ergänzt, dass der Parameter nGear an die Eigenschaft Gear übergeben wird.

METHOD PUBLIC Start
VAR_INPUT
 nGear : INT := 2;
 fVelocity : LREAL := 8.0;
END_VAR 

IF (fVelocity < MaxVelocity) THEN
 velocityInternal := fVelocity;
ELSE
 velocityInternal := MaxVelocity;
END_IF
Gear := nGear; // new 

In Zeile 12 wird der Parameter nGear in die Eigenschaft Gear kopiert.

Wird in einem Funktionsblock eine Methode oder eine Eigenschaft neu definiert, obwohl diese im Basisfunktionsblock schon vorhanden ist, so nennt man dieses Überschreiben. Der Funktionsblock FB_NewEngine überschreibt die Methode Start().

Somit besitzt FB_NewEngine die neue Eigenschaft Gear und überschreibt die Methode Start.

 

Durch

fbNewEngine.Start(1, 7.5);

wird die Methode Start() in FB_NewEngine aufgerufen, da diese in FB_NewEngine neu angelegt (überschrieben) wurde.

Während

fbNewEngine.Stop();

die Methode Stop() aus FB_Engine aufruft. Die Methode Stop() wurde von FB_Engine an FB_NewEngine vereerbt.

Polymorphie

Neben der Vererbung ist Polymorphie (griechisch für Vielgestaltigkeit) eine grundlegende Eigenschaft der objektorientierten Programmierung. Darunter wird verstanden, dass eine Variable in Abhängigkeit seiner Verwendung unterschiedliche Datentypen annehmen kann. Bisher wurde jeder Variablen immer ein Datentyp zugeordnet. Polymorphie tritt immer in Zusammenhang mit Vererbung und Schnittstellen auf.

Hier soll die Polymorphie am obigen Beispiel durch Vererbung gezeigt werden. Dazu wird von FB_Engine und FB_NewEngine jeweils eine Instanz angelegt. In Abhängigkeit einer Variablen (bInput) werden die Instanzen einer Referenz zugewiesen.

PROGRAM MAIN
VAR
 fbEngine : FB_Engine;
 fbNewEngine : FB_NewEngine;
 bInput : BOOL;
 refEngine : REFERENCE TO FB_Engine;
END_VAR

IF (bInput) THEN
 refEngine REF= fbEngine;
ELSE
 refEngine REF= fbNewEngine;
END_IF
refEngine.Start(2, 7.5);

Eine Variable vom Typ REFERENCE TO FB_Engine kann nicht nur eine Instanz vom Typ FB_Engine aufnehmen (Zeile 10), sondern auch alle Funktionsblöcke die von FB_Engine geerbt haben; also auch FB_NewEngine (Zeile 12).

In Zeile 14 wird anschließend die Methode Start() aufgerufen. Allein aus dieser Zeile ist nicht erkennbar, ob die Methode aus FB_Engine oder aus FB_NewEngine ausgeführt wird.

Diese Mehrdeutigkeit wird in der objektorientierten Programmierung häufig eingesetzt, um Programme erweiterbar und flexibel zu gestalten. Hierzu werden z.B. Parameter einer Funktion als Referenz auf einen Funktionsblock definiert. Alle FBs, die von diesem Funktionsblock abgeleitet wurden, können somit an die Funktion übergeben werden.

In diesem Zusammenhang spielt auch das Konzept der Schnittstellen (Interfaces) eine wichtige Rolle, welches in meinem Post IEC 61131-3: Objektkomposition mit Hilfe von Interfaces vorgestellt wird.

SUPER-Zeiger

Bei dem obigen Beispiel wurde in FB_NewEngine die Methode Start() neu angelegt und der schon vorhandene Programmcode aus FB_Engine in die neue Methode kopiert. Das ist nicht immer möglich und entspricht auch nicht dem Gedanken der Wiederverwendung.

Aus diesem Grund steht jedem Funktionsblock, der von einem anderen Funktionsblock erbt, ein Zeiger mit dem Namen SUPER zur Verfügung. Über diesen ist es möglich, auf die Elemente (Methoden, Eigenschaft, lokale Variablen, …) des Basisfunktionsblocks zuzugreifen.

METHOD PUBLIC Start
VAR_INPUT
 nGear : INT := 2;
 fVelocity : LREAL := 8.0;
END_VAR 

SUPER^.Start(nGear, fVelocity); // calls Start() of FB_Engine
Gear := nGear;

Statt den Code aus dem Basisfunktionsblock in die neue Methode zu kopieren, wird über den Zeiger SUPER die Methode aus dem Funktionsblock FB_Engine aufgerufen. Ein Kopieren ist nicht mehr notwendig.

Im CFC-Editor wird SUPER wie folgt aufgerufen:

Der SUPER-Zeiger muss immer in Großbuchstaben geschrieben werden.

THIS-Zeiger

Der THIS-Zeiger steht jedem Funktionsblock zur Verfügung und zeigt auf die eigene Funktionsblockinstanz. Dieser Zeiger ist notwendig, sobald eine Methode eine lokale Variable enthält, die eine Variable im Funktionsblock überdeckt.

Eine Zuweisung in der Methode setzt den Wert der lokalen Variablen. Soll in der Methode die lokale Variable des Funktionsblocks gesetzt werden, so muss der Zugriff über den THIS-Zeiger erfolgen.

nTest := 1; // changes the value of the local variable in the method
THIS^.nTest := 2; // changes the value of the variable in the function block

So wie der SUPER-Zeiger muss auch der THIS-Zeiger immer in Großbuchstaben geschrieben werden.

Beispiel (TwinCAT 3.1.4020)

Einfluss von FINAL auf die Performanz

Wird eine Methode oder eine POU mit dem Zugriffsbezeichner (Access Specifier) FINAL deklariert, so kann dieser Funktionsblock nicht als Basisfunktionsblock dienen. Methodenaufrufe erfolgen immer direkt. Das hat zur Folge das Polymorphie nicht mehr benötigt wird. Der Compiler kann dieses bei der Codegenerierung berücksichtigen und den Code entsprechend optimieren. Je nach Anwendungsfall kann die so erreichte Optimierung sich deutlich auf die Laufzeit auswirken. Hierzu ein Beispiel:

Ein Funktionsblock besitzt zwei völlig identische Methoden. Lediglich der Zugriffsbezeichner ist unterschiedlich. Eine Methode wurde als PUBLIC, die andere als FINAL deklariert. In einer SPS-Task wird erst die eine, einige Zeit später die andere Methode aufgerufen.

IF (bSwitch) THEN
 FOR n := 1 TO 50000 DO
 fbTest.MethodFinal(0.534, 1.78, -2.43);
 END_FOR
ELSE
 FOR n := 1 TO 50000 DO
 fbTest.MethodPublic(0.534, 1.78, -2.43);
 END_FOR
END_IF

Hierbei ist zu beobachten, dass die Durchlaufzeit sich deutlich verändert.

Wird die mit FINAL deklarierte Methode 50.000 mal aufgerufen, so liegt die Durchlaufzeit der SPS-Task auf meinem Testgerät bei ca. 6,9 ms. Diese erhöht sich auf ca. 7,5 ms, sobald die Methode mit PUBLIC deklariert wird.

Dieses Beispielprogramm ist eher theoretischer Natur, da das Programm so gut wie nichts anderes macht, als die Methoden aufzurufen. Trotzdem sollte bei der Auswahl des Zugriffsbezeichners dieses beachtet werden.

UML-Diagramme

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. Eine Beschreibung von UML finden Sie z.B. unter: http://home.f1.htw-berlin.de/scheibl/uml.

UML Diagramme können direkt in TwinCAT 3 erstellt werden. Änderungen am UML-Diagramm wirken sich unmittelbar auf die POUs aus. So können über das UML-Diagramm die Funktionsblöcke verändert und angepasst werden.

Jedes Kästchen steht für einen Funktionsblock und ist immer in drei waagerechte Bereiche aufgeteilt. Im obersten Bereich ist der Name, im mittleren Bereich ist die Auflistung der Eigenschaften und im unteren Bereich sind alle Methoden aufgeführt. Die Pfeile geben in diesem Beispiel die Richtung der Vererbung an und zeigen immer auf den Basisfunktionsblock.


iec-61131-3 oop codesys-v3 plc twincat interfaces vererbung sps methoden
Schreibe einen Kommentar:
Themen:
methoden sps vererbung interfaces twincat plc codesys-v3 oop iec-61131-3
Entweder einloggen... ...oder ohne Wartezeit registrieren
Benutzername
Passwort
Passwort wiederholen
E-Mail