Je nach Aufgabenstellung kann es erforderlich sein, dass Funktionsblöcke Parameter benötigen, die nur einmalig für Initialisierungsaufgaben verwendet werden. Ein möglicher Weg, diese elegant zu übergeben, bietet die Methode FB_init().
Vor TwinCAT 3 wurden Initialisierungs-Parameter sehr häufig über Eingangsvariablen übergeben.
(* TwinCAT 2 *) FUNCTION_BLOCK FB_SerialCommunication VAR_INPUT nDatabits : BYTE(7..8); eParity : E_Parity; nStopbits : BYTE(1..2); END_VAR
Dieses hatte den Nachteil, dass in den graphischen Darstellungsarten die Funktionsblöcke unnötig groß wurden. Auch war es nicht möglich, ein Ändern der Parameter zur Laufzeit zu verhindern.
Sehr hilfreich ist hierbei die Methode FB_init(). Diese Methode wird vor dem Start der SPS-Task einmal implizit ausgeführt und kann dazu dienen, Initialisierungsaufgaben durchzuführen.
Der Dialog zum Hinzufügen von Methoden bietet hierzu eine fertige Vorlage an.
In der Methode sind zwei Eingangsvariablen enthalten, welche Auskunft darüber geben, unter welchen Bedingungen die Methode ausgeführt wird. Die Variablen dürfen weder gelöscht noch verändert werden. Allerdings kann FB_init() um weitere Eingangsvariablen ergänzt werden.
Beispiel
Als Beispiel soll ein Baustein zur Kommunikation über eine serielle Schnittstelle dienen (FB_SerialCommunication). Dieser Baustein soll ebenfalls die serielle Schnittstelle mit den notwendigen Parametern initialisieren. Aus diesem Grund werden zu FB_init() drei Variablen hinzugefügt:
METHOD FB_init : BOOL VAR_INPUT bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start) bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change) nDatabits : BYTE(7..8); eParity : E_Parity; nStopbits : BYTE(1..2); END_VAR
Das Initialisieren der seriellen Schnittstelle erfolgt nicht direkt in FB_init(). Deshalb müssen die Parameter in Variablen kopiert werden, die sich im Funktionsblock befinden.
FUNCTION_BLOCK PUBLIC FB_SerialCommunication VAR nInternalDatabits : BYTE(7..8); eInternalParity : E_Parity; nInternalStopbits : BYTE(1..2); END_VAR
In diesen drei Variablen werden die Werte aus FB_init() während der Initialisierung kopiert.
METHOD FB_init : BOOL VAR_INPUT bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start) bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change) nDatabits : BYTE(7..8); eParity : E_Parity; nStopbits : BYTE(1..2); END_VAR THIS^.nInternalDatabits := nDatabits; THIS^.eInternalParity := eParity; THIS^.nInternalStopbits := nStopbits;
Wird eine Instanz von FB_SerialCommunication angelegt, so sind diese drei zusätzlichen Parameter mit anzugeben. Die Werte werden direkt nach dem Namen des Funktionsblocks in runden Klammern angegeben:
fbSerialCommunication : FB_SerialCommunication(nDatabits := 8, eParity := E_Parity.None, nStopbits := 1);
Noch bevor die SPS-Task startet, wird die Methode FB_init() implizit aufgerufen, so dass die internen Variablen des Funktionsblocks die gewünschten Werte erhalten.
Mit dem Start der SPS-Task und dem Aufruf der Instanz von FB_SerialCommunication kann jetzt die Initialisierung der seriellen Schnittstelle erfolgen.
Es ist immer notwendig alle Parameter anzugeben. Eine Deklaration ohne eine vollständige Auflistung der Parameter ist nicht erlaubt und erzeugt beim Compilieren eine Fehlermeldung:
Arrays
Wird FB_init() bei Arrays verwendet, so sind für jedes Element die vollständigen Parameter anzugeben (mit eckige Klammern):
aSerialCommunication : ARRAY[1..2] OF FB_SerialCommunication[ (nDatabits := 8, eParity := E_Parity.None, nStopbits := 1), (nDatabits := 7, eParity := E_Parity.Even, nStopbits := 1)];
Sollen alle Elemente die gleichen Initialisierungswerte erhalten, so ist es ausreichend, wenn die Parameter einmal vorhanden sind (ohne eckige Klammern):
aSerialCommunication : ARRAY[1..2] OF FB_SerialCommunication(nDatabits := 8, eParity := E_Parity.None, nStopbits := 1);
Mehrdimensionale Arrays sind ebenfalls möglich. Auch hier müssen alle Initialisierungswerte angegeben werden:
aSerialCommunication : ARRAY[1..2, 5..6] OF FB_SerialCommunication[ (nDatabits := 8, eParity := E_Parity.None, nStopbits := 1), (nDatabits := 7, eParity := E_Parity.Even, nStopbits := 1), (nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 2), (nDatabits := 7, eParity := E_Parity.Even, nStopbits := 2)];
Vererbung
Kommt Vererbung zum Einsatz, so wird die Methode FB_init() immer mit vererbt. Als Beispiel soll hier FB_SerialCommunicationRS232 dienen:
FUNCTION_BLOCK PUBLIC FB_SerialCommunicationRS232 EXTENDS FB_SerialCommunication
Wird eine Instanz von FB_SerialCommunicationRS232 angelegt, so müssen auch die Parameter von FB_init() angegeben werden, welche von FB_SerialCommunication geerbt wurden:
fbSerialCommunicationRS232 : FB_SerialCommunicationRS232(nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 1);
Es besteht außerdem die Möglichkeit FB_init() zu überschreiben. In diesem Fall müssen die gleichen Eingangsvariablen in der gleichen Reihenfolge und vom gleichen Datentyp vorhanden sein, wie bei dem Basis-FB (FB_SerialCommunication). Es können aber weitere Eingangsvariablen hinzugefügt werden, so dass der abgeleitete Funktionsblock (FB_SerialCommunicationRS232) zusätzliche Parameter erhält:
METHOD FB_init : BOOL VAR_INPUT bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start) bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change) nDatabits : BYTE(7..8); eParity : E_Parity; nStopbits : BYTE(1..2); nBaudrate : UDINT; END_VAR THIS^.nInternalBaudrate := nBaudrate;
Wird eine Instanz von FB_SerialCommunicationRS232 angelegt, so sind alle Parameter, auch die von FB_SerialCommunication, anzugeben:
fbSerialCommunicationRS232 : FB_SerialCommunicationRS232(nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 1, nBaudRate := 19200);
In der Methode FB_init() von FB_SerialCommunicationRS232 ist nur das Kopieren des neuen Parameters (nBaudrate) notwendig. Dadurch, dass FB_SerialCommunicationRS232 von FB_SerialCommunication erbt, wird vor dem Start der SPS-Task auch FB_init() von FB_SerialCommunication implizit ausgeführt. Es werden immer beide FB_init() Methoden implizit aufgerufen, sowohl die von FB_SerialCommunication, als auch die von FB_SerialCommunicationRS232. Der Aufruf von FB_init() erfolgt bei Vererbung immer von ‚unten‘ nach ‚oben‘. Also erst von FB_SerialCommunication und anschließend von FB_SerialCommunicationRS232.
Parameter weiterleiten
Als Beispiel soll der Funktionsblock (FB_SerialCommunicationCluster) dienen, in dem mehrere Instanzen von FB_SerialCommunication deklariert werden:
FUNCTION_BLOCK PUBLIC FB_SerialCommunicationCluster VAR fbSerialCommunication01 : FB_SerialCommunication(nDatabits := nInternalDatabits, eParity := eInternalParity, nStopbits := nInternalStopbits); fbSerialCommunication02 : FB_SerialCommunication(nDatabits := nInternalDatabits, eParity := eInternalParity, nStopbits := nInternalStopbits); nInternalDatabits : BYTE(7..8); eInternalParity : E_Parity; nInternalStopbits : BYTE(1..2); END_VAR
Damit die Parameter der Instanzen von außen einstellbar sind, erhält auch FB_SerialCommunicationCluster die Methode FB_init() mit den notwendigen Eingangsvariablen.
METHOD FB_init : BOOL VAR_INPUT bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start) bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change) nDatabits : BYTE(7..8); eParity : E_Parity; nStopbits : BYTE(1..2); END_VAR THIS^.nInternalDatabits := nDatabits; THIS^.eInternalParity := eParity; THIS^.nInternalStopbits := nStopbits;
Hierbei gibt es allerdings einiges zu beachten. Die Aufrufreihenfolge von FB_init() ist in diesem Fall nicht eindeutig definiert. In meiner Testumgebung erfolgen die Aufrufe von ‚innen‘ nach ‚außen‘. Erst wird fbSerialCommunication01.FB_init() und fbSerialCommunication02.FB_init() aufgerufen, danach erst fbSerialCommunicationCluster.FB_init(). Es ist nicht möglich, die Parameter von ‚außen‘ nach ‚innen‘ durchzureichen. Die Parameter stehen in den beiden inneren Instanzen von FB_SerialCommunication somit nicht zur Verfügung.
Die Reihenfolge der Aufrufe ändert sich, sobald FB_SerialCommunication und FB_SerialCommunicationRS232 vom gleichen Basis-FB abgeleitet werden. In diesem Fall wird FB_init() von ‚außen‘ nach ‚innen‘ aufgerufen. Dieser Ansatz ist aus zwei Gründen nicht immer umzusetzen:
- Liegt FB_SerialCommunication in einer Bibliothek, so kann die Vererbung nicht ohne weiteres geändert werden.
- Die Aufrufreihenfolge von FB_init() ist bei Verschachtelung nicht weiter definiert. Es ist also nicht auszuschließen, dass sich dieses in zukünftigen Versionen ändern kann.
Eine Variante das Problem zu lösen, ist der explizite Aufruf von FB_SerialCommunication.FB_init() aus FB_SerialCommunicationCluster.FB_init().
fbSerialCommunication01.FB_init(bInitRetains := bInitRetains, bInCopyCode := bInCopyCode, nDatabits := 7, eParity := E_Parity.Even, nStopbits := nStopbits); fbSerialCommunication02.FB_init(bInitRetains := bInitRetains, bInCopyCode := bInCopyCode, nDatabits := 8, eParity := E_Parity.Even, nStopbits := nStopbits);
Alle Parameter, auch bInitRetains und bInCopyCode, werden direkt weitergegeben.
Achtung: Der Aufruf von FB_init() hat immer zur Folge das alle lokalen Variablen der Instanz initialisiert werden. Das muss beachtet werden, sobald FB_init() aus der SPS-Task explizit aufgerufen wird, statt implizit vor der SPS-Task.
Zugriff über Eigenschaften
Durch die Übergabe der Parameter per FB_init(), können diese zur Laufzeit weder von Außen gelesen noch verändert werden. Die einzige Ausnahme wäre der explizite Aufruf von FB_init() aus der SPS-Task. Dieses sollte aber grundsätzlich vermieden werden, da dadurch alle lokalen Variablen der Instanz werden neu initialisiert.
Soll der Zugriff aber dennoch möglich sein, so können für die Parameter entsprechende Eigenschaften angelegt werden:
Die Setter und Getter der jeweiligen Eigenschaften greifen auf die entsprechenden lokalen Variablen in dem Funktionsblock zu (nInternalDatabits, eInternalParity und nInternalStopbits). Somit lassen sich die Parameter bei der Deklaration, als auch zur Laufzeit vorgeben.
Durch das Entfernen der Setter kann ein Ändern der Parameter zur Laufzeit verhindert werden. Sind die Setter vorhanden kann allerdings auch auf FB_init() verzichtet werden. Eigenschaften können ebenfalls direkt bei der Deklaration einer Instanz initialisiert werden.
fbSerialCommunication : FB_SerialCommunication := (Databits := 8, Parity := E_Parity.Odd, Stopbits := 1);
Es können die Parameter von FB_init() und die Eigenschaften auch gleichzeitig angegeben werden:
fbSerialCommunication : FB_SerialCommunication(nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 1) := (Databits := 8, Parity := E_Parity.Odd, Stopbits := 1);
Vorrang haben in diesem Fall die Initialisierungswerte der Eigenschaften. Die Übergabe per Eigenschaft und FB_init() hat hier den Nachteil, das die Deklaration des Funktionsblocks unnötig lang wird. Beides zu implementieren erscheint mir auch nicht notwendig. Sind alle Parameter auch über Eigenschaften schreibbar, so kann auf die Initialisierung per FB_init() verzichtet werden. Als Fazit gilt: Dürfen Parameter zur Laufzeit nicht änderbar sein, so ist der Einsatz von FB_init() in Betracht zu ziehen. Soll der Schreibzugriff möglich sein, so bieten sich Eigenschaften an.
Excellent and well-laid out article Stefan!
Hervorragender Artikel, vielen Dank!
Für die Problematik “Parameter weiterleiten” könnte man noch die Alternative mit dem Attribut-Pragma “call_after_init” (in Verbindung mit einer eigenen Init-Methode) zeigen. Hiermit lässt sich diese einigermaßen elegant lösen, ohne das eine Ableitung vom gleichen Basistyp nötig ist.