IEC 61131-3: Parameter transfer via FB_init

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.

Pic01

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.

Pic02

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:

Pic03

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:

  1. If FB_SerialCommunication is located in a library, the inheritance cannot be changed just offhand.
  2. 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:

Pic04

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.

Sample 1 (TwinCAT 3.1.4022) on GitHub

Author: Stefan Henneken

I’m Stefan Henneken, a software developer based in Germany. This blog is just a collection of various articles I want to share, mostly related to Software Development.

8 thoughts on “IEC 61131-3: Parameter transfer via FB_init”

  1. Totally unrelated and maybe abit on the dump end, but what is the (x..y) part of the byte
    Example | nStopbits : BYTE(1..2);

  2. 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.

    1. Yes i also had problems with it, i solved it with as input PVOID and adr(Var), not so nice but its a workaround

    2. 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

      1. 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…

Leave a comment