IEC 61131-3: Methods, Properties and Inheritance

Object-oriented programming (OOP) is a proven way of keeping the complexity of software systems in check. Until recently the preserve of languages such as C++, Java and C#, IEC 61131-3 introduces the concept to PLC programming.

Methods

Previously, a function block consisted of internal, input and output variables. There was only one opportunity to modify internal variables from outside the function block. Instances of the function block were called with the relevant input variables. Different sections within the function block would be executed depending on the value of these input variables, which in turn affected the value of internal variables. For example:

Start engine:

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

Stop engine:

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

Code within the function block listens for the values of bStart and bStop to switch from FALSE to TRUE. The function block user needs to know that bStart needs to be set to FALSE when stopping the engine. Failure to do so would mean that there would be no switch from FALSE to TRUE on attempting to restart the engine. The user also needs to know that they need to specify a value for the nGear parameter when starting the engine (but not when stopping it).

The OOP extensions introduce a clearer separation between internal variables and the ability to modify them. It is now possible to define additional sub-functions (methods) within a function block, which are available to be called by the user.

Picture01

Methods are comparable to actions, except that parameters can be passed to a method when it is called. Just like functions, methods can also contain local variables. Using methods, the above example can be realised thus:

Start engine:

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

Stop engine:

fbEngine.M_Stop();

The Start() and Stop() methods can access internal function block variables, but can also contain variables which can neither be modified from the outside nor be modified by other methods within the function block. This ensures that they cannot be inadvertently overwritten.

Methods can also include a return value, which is returned to the calling entity. If required, it is also possible to declare additional output variables between VAR_OUTPUT and END_VAR. Since TwinCAT 3, functions have also had this capability. Again since TwinCAT 3, it has also been possible to declare functions and methods with no return value.

Only FUNCTION_BLOCK and PROGRAM POUs can contain methods – this option is not available to FUNCTION POUs.

When calling a method, all parameters must be specified. Parameter names can be omitted if required:

fbEngine.Start(1, 7.5);

Where parameter names are used, parameters can be given in any order:

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

In the graphical languages, methods are depicted using a separate box:

Picture02

Some programming languages offer the ability to define multiple methods with the same name. Which method is executed is determined by the parameters specified when the method is called (signature). This is known as overloading. Currently, methods in IEC 61131-3 cannot be overloaded. It is not possible to differentiate between methods using signatures. Method names must be unique.

Access specifiers

The method declaration may include an optional access specifier. This restricts access to the method.

PUBLIC The method can be called by anyone – there are no restrictions.
PRIVATE The method is available from within the POU only. It cannot be called from outside the POU.
PROTECTED Only its own POU or POUs derived from it can access the method. Derivation is discussed below.
INTERNAL The method is accessible from within the same namespace only. This allows methods to be available from within a certain library only, for example.
FINAL The method cannot be overwritten by another method. Overwriting of methods is described below.

The default setting where no access specifier is defined is PUBLIC.

A method declaration therefore has the following structure:

METHOD <Access specifier> <Name> : <Datatype return value>

Properties

Previously, the only way of passing parameters was to call a function block with input variables. State information was returned to the calling entity via output variables. Properties offer a defined way of passing general parameters and state information outside of the function block call.

Properties are distinguished by the fact that access is via a pair of special methods. There is one method for writing and one for reading the property. These methods are designated setters (write) and getters (read). Together they are referred to as accessors. These accessor methods can also perform function such as range checking or unit conversion.

Setters and getters can contain local variables, but no additional inputs or outputs. On exiting the setter or getter method, the values of these local variables are, however, lost. In this respect they behave like functions. The setter therefore needs to provide an appropriate mechanism to ensure that the value of the property is preserved. This can, for example, be achieved by declaring a local variable in the function block.

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

The setter method assigns the value of the MyProperty property to the local variable.

myPropertyInternalValue := MyProperty;

The getter does the opposite and reassigns the value of the local variable to the property.

MyProperty := myPropertyInternalValue;

The local variable myPropertyInternalValue can be used within the function block for internal calculations. Access to this variable is exclusively via the MyProperty property.

Let us add three further properties to the above example, one to specify the maximum permissible velocity (MaxVelocity) and two more to output the current temperature (Temperature) and velocity (Velocity).

In the graphical languages, properties are not shown on the relevant box. To access a property, we use the name of the instance and of the property separated by a dot.

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

Picture03

There does not have to be both a getter and a setter. In the absence of a setter, for example, the property will be read-only. In TwinCAT 3, the function block definition, including methods and properties, looks like this:

Picture04 

Access specifiers

As with methods, properties can also take the following access specifiers: PUBLIC, PRIVATE, PROTECTED, INTERNAL and FINAL. Where no access specifier is defined, the property is PUBLIC. In addition, an access specifier can also be specified for each setter and getter. This takes priority over the property’s own access specifier.

A property declaration therefore has the following structure:

PROPERTY <Access specifier> <Name> : <Datatype>

Inheritance

Function blocks are an excellent means of keeping program sections separate from each other. This improves software structure and significantly simplifies reuse. Previously, extending the functionality of an existing function block was always a delicate undertaking. This meant either modifying the code or programming a new function block around the existing block (i.e. the existing function block was effectively embedded within a new function block.) In the latter case, it was necessary to create all input variables anew and assign them to the input variables for the existing function block. The same was required, in the opposite direction, for output variables.

TwinCAT 3 introduces the concept of inheritance. Inheritance is one of the fundamental principles of object-oriented programming. Inheritance involves deriving a new function block from an existing function block. The new block can then be extended. To the extent permitted by the parent function block’s access specifiers, the new function block inherits all properties and methods from the parent function block. Each function block can have any number of child function blocks, but only one parent function block. Derivation of a function block occurs in the new function block declaration. The name of the new function block is followed by the keyword EXTENDS followed by the name of the parent function block. For example:

FUNCTION_BLOCK PUBLIC FB_NewEngine EXTENDS FB_Engine

The new, derived function block (FB_NewEngine) possesses all of the properties and methods of its parent (FB_Engine). Methods and properties are, however, only inherited where the access specifier permits.

The child function block also inherits all local variables, VAR_INPUT, VAR_OUTPUT, and VAR_IN_OUT from the parent function block. This behaviour cannot be modified using access specifiers.

If methods or properties in the parent function block have been declared as PROTECTED, the child function block (FB_NewEngine) is able to access them, but they cannot be accessed from outside FB_NewEngine.

Inheritance applies only to POUs of type FUNCTION_BLOCK.

Access specifiers

FUNCTION_BLOCK, FUNCTION or PROGRAM declarations can include an access specifier. This restricts access and, where applicable, the ability to inherit.

PUBLIC Anyone can call or create an instance of the POU. In addition, if the POU is a FUNCTION_BLOCK, it can be used for inheritance. No restrictions apply.
INTERNAL The POU can only be used within its own namespace. This allows POUs to be available from within a certain library only, for example.
FINAL The FUNCTION_BLOCK cannot serve as a parent function block. Methods and properties in this POU cannot be inherited. FINAL is only permissible for POUs of type FUNCTION_BLOCK.

The default setting where no access specifier is defined is PUBLIC. The access specifiers PRIVATE and PROTECTED are not permitted in POU declarations.

If you plan to utilise inheritance, the function block declaration will therefore have the following structure:

FUNCTION_BLOCK <Access specifier> <Name> EXTENDS <Name basis function block>

Overwriting methods

The new FUNCTION_BLOCK FB_NewEngine, which is derived from FB_Engine, can contain additional properties and methods. For example, we can add the property Gear. This property can be used to query and change the current gear. Getters and setters for this property need to be set up.

However, we also need to ensure that the nGear parameter from the Start() method is passed to this property. Because the parent function block FB_Engine does not have access to this new property, a new method with exactly the same parameters needs to be created in FB_NewEngine. We copy the existing code to the new method and add new code so that the nGear parameter is passed to the property Gear.

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

Line 12 copies the nGear parameter to the Gear property.

Where a method or property which is already present in the parent function block is redefined within the child function block, this is referred to as overwriting. The function block FB_NewEngine overwrites the Start() method.

FB_NewEngine therefore has the new property Gear and overwrites the Start() method.

Picture05

fbNewEngine.Start(1, 7.5);

calls the Start() method in FB_NewEngine, since this method has been redefined (overwritten) in FB_NewEngine.

Whilst

fbNewEngine.Stop();

calls the Stop() method from FB_Engine. The Stop() method has been inherited by FB_NewEngine from FB_Engine.

Polymorphism

In addition to inheritance, another fundamental property of object-oriented programming is polymorphism (Greek for ‘having many forms’). This means that a variable can take different data types depending on how it is used. Previously, variables were always assigned a type. Polymorphism always occurs in the context of inheritance and interfaces.

We will illustrate polymorphism using the above example with inheritance. We create one instance each of FB_Engine and FB_NewEngine. A reference is assigned to these instances depending on a variable (bInput).

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);

A variable of type REFERENCE TO FB_Engine can take an instance of type FB_Engine (line 10), but can also take all function blocks which are derived from FB_Engine – including therefore FB_NewEngine (line 12).

Line 14 then calls the Start() method. It is not possible to determine from this line alone whether the Start() method from FB_Engine or from FB_NewEngine will be executed.

This ambiguity is frequently used in object-oriented programming to make programs more flexible and expandable. To this end, the parameters for a function may be defined as references to a function block. All FBs which are derived from this function block can then be passed to this function.

Interfaces are also important in this context and this is explored in my post IEC 61131-3: Object composition using interfaces.

SUPER pointer

In the above example, we created a new Start() method in FB_NewEngine and copied the existing code from FB_Engine into the new method. This is not always possible, and it also runs counter to the principle of reuse.

Consequently, every function block which is derived from another function block has access to a pointer called SUPER. This can be used to access elements (methods, properties, local variables, etc.) from the parent function block.

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;

Instead of copying code from the parent function block to the new method, the SUPER pointer can be used to call the method from the FB_Engine function block. This does away with the need to copy the code.

In the CFC editor, SUPER is called as follows:

Picture06

The SUPER pointer always has to be written in upper case.

THIS pointer

The THIS pointer is available to all function blocks and points to the current function block instance. This pointer is required whenever a method contains a local variable which obscures a variable in the function block.

An assignment statement within the method sets the value of the local variable. If we want the method to set the value of the local variable in the function block, we need to use the THIS pointer to access it.

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

As with the SUPER pointer, the THIS pointer must likewise always be upper case.

Sample (TwinCAT 3.1.4020) on GitHub

Effect of FINAL on performance

A method or POU declared with the access specifier FINAL is not able to act as a parent function block. All calls to its methods are direct. This has the effect that there is no longer any need for polymorphism. The compiler is able to take this into account during code generation and optimise the code accordingly. Depending on the application, this optimisation can have a significant effect at runtime. Here, an example:

A function block has two completely identical methods. The only difference is the access specifier. One method has been declared as PUBLIC, the other as FINAL. In a PLC task, first one and then a little later the other method is called.

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

As we can see, the execution time changes significantly.

Picture07

If the method declared as FINAL is called 50,000 times, the running time for the PLC task on my test device is about 6.9 ms. This rises to about 7.5 ms for the method declared as PUBLIC.

Of course, our example program is rather abstract, as it does almost nothing but call the methods. Nonetheless, this is worth taking into account when selecting an access specifier.

UML diagrams

The inheritance hierarchy can be depicted diagrammatically. Unified Modelling Language (UML) is the established standard in this area. UML defines various diagram types which describe both the structure and behaviour of software.

A good tool for describing the function block inheritance hierarchy is the class diagram.

UML diagrams can be created directly in TwinCAT 3. Changes to the UML diagram have a direct effect on the POUs. Function blocks can thus be modified and amended via the UML diagram.

Picture08

Each box stands for one function block and is always divided into three horizontal sections. The top section shows the name of the function block, the middle section lists its properties and the lower section lists all its methods. In this example, the arrows show the direction of inheritance and always point towards the parent function block.

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.

12 thoughts on “IEC 61131-3: Methods, Properties and Inheritance”

  1. Another great post, Stefan. I’m familiar with these topics already, but the “FINAL” optimisation is new information for me and certainly something I will remember for high performance libraries.

    A useful tip when using inheritance is that it is possible to override a method (and I assume property, but have not tried this) using a different access modifier to the original (of course, assuming it was not “private”). I have used this if I want to inherit a class but perhaps restrict access to a formerly public method. I override the method with a “PRIVATE” access specifier.

    This works well when operating on the child class directly. However when you access the child class through reference to it’s parent (as per your “FB_Engine” example above), the access specifier of the parent class is adopted, and access to the child class’ private method is publicly permitted!

  2. Hello Stefan,
    Great blog. Compliments to the chef!
    OOP could bring a lot of problems because of the increasing complexity of ST-programs. Have you ever thought about test driven programming or something like unit tests for ST programs?

    1. Hello Nikolai,
      Thx for your feedback. I can’t agree that the complexity of a PLC program would increase by using OOP. By using OOP it’s easier to separate a program in modules, especially by using interfaces.
      I like Unit Tests. I use it under C#. At the moment I don’t know about a solution for Unit Tests under TwinCAT 3. Maybe we’ll have a solution in the future.
      Regards
      Stefan

  3. hello, what is the difference between property setter and input ? when to use which? thank you!

    1. – Inputs (variables in the range VAR_INPUT) are always public. With properties, access specifiers (public, private, protected, internal and final) can be used to precisely define how the input should behave when it is inherited.

      – Inputs are displayed in the graphic languages (e.g. CFC) directly at the box of the FB. Properties will be assigned independently of the box.

      – Properties can be used in the definition of interfaces, which is not possible with inputs.

      The decision whether to use an input or a property cannot be answered in a universally valid way. When using interfaces and inheritance, properties play an important role. If FBs are developed which are to be used later in CFC, inputs are necessary in many places.
      Personally, I wish that inputs and outputs also support access specifiers. Also the use within interfaces seems advantageous to me. Maybe with the next version of IEC 61131-3.

      1. Dear Stefan ,

        i have Function block which need a bstart INPUT, very simple
        ===============================================================
        FUNCTION_BLOCK FB_ADDING
        VAR_INPUT
        bstart: BOOL;
        END_VAR

        VAR
        x: INT;
        END_VAR

        if bstart then
        x:=x+1;
        End_if
        ===============================================================
        PROGRAM MAIN
        VAR

        fb_add: FB_ADDING;
        END_VAR

        fb_add(bstart:=TRUE);
        ===============================================================

        later if i want to add Function Block FB_SUBTRACTING
        i try interface, and add method ,
        ===============================================================
        FUNCTION_BLOCK FB_ADDING IMPLEMENTS I_Iterate
        VAR_INPUT
        bstart:BOOL;
        END_VAR

        VAR
        x: INT;
        END_VAR
        ==============================================================
        METHOD iterating : int

        IF bstart THEN
        x:=x+1;

        END_IF
        ==============================================================

        then i expect to write in Main something like this:

        PROGRAM MAIN
        VAR

        fb_add: FB_ADDING;
        fb_sub: FB_SUBTRACTING;
        bswitch: BOOL;
        i_iterator: I_Iterate;

        END_VAR

        IF bswitch THEN
        i_iterator:=fb_add;
        ELSE
        i_iterator:=fb_sub;
        END_IF

        i_iterator.iterating(); //still no bstart to trigger function block

        +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        Problem is:
        I cannot write the “bstart” true, because INTERFACE not allow me to declare INPUT.

        Would you advice. thank you!

  4. sorry Stefan, continue here:
    I find that i can declare bstart inside Interface Method area.
    then i can use it in Main like
    i_iterator.iterating(bstart:=true);
    is it a right approach?

    like above example, when using interface(with input), i must need to add “method” ?
    any difference to do “x:=x+1 ” inside Function Block or inside “Function Block’s Method area”?
    (i mean, in the simple FB x:=x+1 at the top, i don’t need method);
    thank you!

    1. Hi,
      if a property is decorated with the following pragma, it is possible to read/write via symbol name:
      {attribute ‘monitoring’ := ‘call’}
      PROPERTY PUBLIC nProp : BYTE

      Here my C# test program:
      using (AdsClient client = new AdsClient())
      {
      byte valuePlc;
      client.Connect(AmsNetId.Local, 851);
      valuePlc = (byte)client.ReadValue(“MAIN.fbFoo.nProp”, typeof(byte));
      client.WriteValue(“MAIN.fbFoo.nProp”, ++valuePlc);
      }
      Stefan

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: