Depending on the task, it may be necessary for function blocks to require parameters that are only used once for initialization tasks. One possible way to pass them elegantly is to use the FB_init() method.
Before TwinCAT 3, initialisation parameters were very often transferred via input variables.
(* TwinCAT 2 *) FUNCTION_BLOCK FB_SerialCommunication VAR_INPUT nDatabits : BYTE(7..8); eParity : E_Parity; nStopbits : BYTE(1..2); END_VAR
This had the disadvantage that the function blocks became unnecessarily large in the graphic display modes. It was also not possible to prevent changing the parameters at runtime.
Very helpful is the method FB_init(). This method is implicitly executed one time before the PLC task is started and can be used to perform initialization tasks.
The dialog for adding methods offers a finished template for this purpose.
The method contains two input variables that provide information about the conditions under which the method is executed. The variables may not be deleted or changed. However, FB_init() can be supplemented with further input variables.
Example
An example is a block for communication via a serial interface (FB_SerialCommunication). This block should also initialize the serial interface with the necessary parameters. For this reason, three variables are added to FB_init():
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
The serial interface is not initialized directly in FB_init(). Therefore, the parameters must be copied into variables located in the function block.
FUNCTION_BLOCK PUBLIC FB_SerialCommunication VAR nInternalDatabits : BYTE(7..8); eInternalParity : E_Parity; nInternalStopbits : BYTE(1..2); END_VAR
During initialization, the values from FB_init() are copied in these three variables.
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;
If an instance of FB_SerialCommunication is created, these three additional parameters must also be specified. The values are specified directly after the name of the function block in round brackets:
fbSerialCommunication : FB_SerialCommunication(nDatabits := 8, eParity := E_Parity.None, nStopbits := 1);
Even before the PLC task starts, the FB_init() method is implicitly called, so that the internal variables of the function block receive the desired values.
With the start of the PLC task and the call of the instance of FB_SerialCommunication, the serial interface can now be initialized.
It is always necessary to specify all parameters. A declaration without a complete list of the parameters is not allowed and generates an error message when compiling:
Arrays
If FB_init() is used for arrays, the complete parameters must be specified for each element (with square brackets):
aSerialCommunication : ARRAY[1..2] OF FB_SerialCommunication[ (nDatabits := 8, eParity := E_Parity.None, nStopbits := 1), (nDatabits := 7, eParity := E_Parity.Even, nStopbits := 1)];
If all elements are to have the same initialization values, it is sufficient if the parameters exist once (without square brackets):
aSerialCommunication : ARRAY[1..2] OF FB_SerialCommunication(nDatabits := 8, eParity := E_Parity.None, nStopbits := 1);
Multidimensional arrays are also possible. All initialization values must also be specified here:
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)];
Inheritance
If inheritance is used, the method FB_init() is always inherited. FB_SerialCommunicationRS232 is used here as an example:
FUNCTION_BLOCK PUBLIC FB_SerialCommunicationRS232 EXTENDS FB_SerialCommunication
If an instance of FB_SerialCommunicationRS232 is created, the parameters of FB_init(), which were inherited from FB_SerialCommunication, must also be specified:
fbSerialCommunicationRS232 : FB_SerialCommunicationRS232(nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 1);
It is also possible to overwrite FB_init(). In this case, the same input variables must exist in the same order and be of the same data type as in the basic FB (FB_SerialCommunication). However, further input variables can be added so that the derived function block (FB_SerialCommunicationRS232) receives additional parameters:
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;
If an instance of FB_SerialCommunicationRS232 is created, all parameters, including those of FB_SerialCommunication, must be specified:
fbSerialCommunicationRS232 : FB_SerialCommunicationRS232(nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 1, nBaudRate := 19200);
In the method FB_init() of FB_SerialCommunicationRS232, only the copying of the new parameter (nBaudrate) is necessary. Because FB_SerialCommunicationRS232 inherits from FB_SerialCommunication, FB_init() of FB_SerialCommunication is also executed implicitly before the PLC task is started. Both FB_init() methods of FB_SerialCommunication and of FB_SerialCommunicationRS232 are always called implicitly. When inherited, FB_init() is always called from ‘bottom’ to ‘top’, first from FB_SerialCommunication and then from FB_SerialCommunicationRS232.
Forward parameters
The function block (FB_SerialCommunicationCluster) is used as an example, in which several instances of FB_SerialCommunication are declared:
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
FB_SerialCommunicationCluster also receives the method FB_init() with the necessary input variables so that the parameters of the instances can be set externally.
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;
However, there are some things to be taken into consideration here. The call sequence of FB_init() is not clearly defined in this case. In my test environment the calls are made from ‘inside’ to ‘outside’. First fbSerialCommunication01.FB_init() and fbSerialCommunication02.FB_init() are called, then fbSerialCommunicationCluster.FB_init(). It is not possible to pass the parameters from ‘outside’ to ‘inside’. The parameters are therefore not available in the two inner instances of FB_SerialCommunication.
The sequence of the calls changes as soon as FB_SerialCommunication and FB_SerialCommunicationRS232 are derived from the same basic FB. In this case FB_init() is called from ‘outside’ to ‘inside’. This approach cannot always be implemented for two reasons:
- If FB_SerialCommunication is located in a library, the inheritance cannot be changed just offhand.
- The call sequence of FB_init() is not further defined with nesting. So it cannot be excluded that this can change in future versions.
One way to solve the problem is to explicitly call FB_SerialCommunication.FB_init() from 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);
All parameters, including bInitRetains and bInCopyCode, are passed on directly.
Attention: Calling FB_init() always initializes all local variables of the instance. This must be considered as soon as FB_init() is explicitly called from the PLC task instead of implicitly before the PLC task.
Access via properties
By passing the parameters by FB_init(), they can neither be read from outside nor changed at runtime. The only exception would be the explicit call of FB_init() from the PLC task. However, this should principally be avoided, since all local variables of the instance will be reinitialized in this case.
If, however, access should still be possible, appropriate properties can be created for the parameters:
The setter and getter of the respective properties access the corresponding local variables in the function block (nInternalDatabits, eInternalParity and nInternalStopbits). Thus, the parameters can be specified in the declaration as well as at runtime.
By removing the setter, you can prevent the parameters from being changed at runtime. If the setter is available, FB_init() can be omitted. Properties can also be initialized directly when declaring an instance.
fbSerialCommunication : FB_SerialCommunication := (Databits := 8, Parity := E_Parity.Odd, Stopbits := 1);
The parameters of FB_init() and the properties can also be specified simultaneously:
fbSerialCommunication : FB_SerialCommunication(nDatabits := 8, eParity := E_Parity.Odd, nStopbits := 1) := (Databits := 8, Parity := E_Parity.Odd, Stopbits := 1);
In this case, the initialization values of the properties have priority. The transfer by property and FB_init() has the disadvantage that the declaration of the function block becomes unnecessarily long. To implement both does not seem necessary to me either. If all parameters can also be written via properties, the initialization via FB_init() can be omitted. Conclusion: If parameters must not be changeable at runtime, the use of FB_init() has to be considered. If the write access is possible, properties are another opportunity.
Totally unrelated and maybe abit on the dump end, but what is the (x..y) part of the byte
Example | nStopbits : BYTE(1..2);
(x..y) defines a subrange type.
see https://help.codesys.com/webapp/_cds_datatype_subint;product=codesys;version=3.5.15.0
thanks man, you’re the boss! ^^
OOP on IEC61131-3 is a little bit disappointing. I have a huge structure on project level with configuration values and I’m not able to use a reference to this structure via FB_Init, RB_ReInit. To be able to use online update, I have always to update the reference with each cycle. Since we put various config structs from libraries in this global config structure to manage the complexity, we can’t simply construct a class and use an interface, without getting expensive code.
We need a real constructor.
Yes i also had problems with it, i solved it with as input PVOID and adr(Var), not so nice but its a workaround
I had the same struggle. In the End I resolved the issue not using FB_Init, but just by adding a FBInit method. The FBInit method takes a var inout var “Settings” which can be specific for the Function block. In the FBInit method I connect te Reference To the internal object settings. The advantage is that I have full control over what I like to pass into the object on init. A big disadvantage is, when I use Inheritance. I have to make a FBInitBase in the base class, and I must do more effort to separate the settings from the current object that is called and the settings I need to pass to the base class. For the rest it works effective for me.
To be sure the object is initialized (FBInit is called) I throw an exception when the internal settings reference is null
When you say “I throw an exception” – how do you do that with TwinCAT3?
…and on the FB_init issue at hand: I also struggle with this bad version of a constructor called “FB_init”. 😒 Currently I am evaluating a setup where “top level FB’s” come with FB_init an everything else has a “Setup” method with the required arguments…
See here:
https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/6415331211.html&id=
What I usually do is a the {attribute ‘no_copy’} above all the variables set using the constructor.
This ensure that the ‘copy’ cycle is not done after FB_init is called.
So pointer etc. will not be copied and you are sure to deal with proper adress.
I don’t use FB_reinit, but maybe this function could also help