With the object orientated options we’ve got available with IEC 61131-3 last years the time has come to talk about design patterns and best practices in the world of PLC programming. In this series of three posts ‘The three pillars of OOP‘ I will explain the concepts of: Encapsulation, Inheritance and Polymorphism with some examples in TwinCAT. This post is about the third pillar ‘Polymorphism’.
What is polymorphism?
The term polymorphism comes from the Greek word for “many forms”. With this term we refer to a single type or function blocks ability to take many forms. For example let’s say that we want our PLC to control three doors: An electric, a hydraulic and a pneumatic door. We have (very different) function blocks for each door to control them. However they all have the same methods to open and close the door. Polymorphism tells us that we can create a generic door type which can be either a electric, a hydraulic or pneumatic door. This allows us to write a process flow using our generic door type. Meaning that our process flow will work with either door type.
How do we achieve polymorphism in TwinCAT?
Let’s go back to the load cell from the posts about encapsulation and inheritance. The function block ‘fbLoadCell’ provided the basic behavior of a load cell. The calculation of the actual weight was based on the bridge input voltage with a fixed supply voltage. However, most load cell modules read the supply voltage as well. Taking the actual supply voltage in account we can compensate for any fluctuations the power supply has.
OK, sounds good.. so let’s create a function block based on our fbLoadCell which supports the additional supply voltage by overriding the calculation of fbLoadCell.
The UML diagram would look like this:
Notice that in this case the fbOverridenLoadCell does not introduce any new interface but merely modifies the existing ones.
To create fbOverridenLoadCell in TwinCAT we start with a new function block which extends fbLoadCell and can take the supply voltage as input:
FUNCTION_BLOCK fbOverridenLoadCell EXTENDS fbLoadCell VAR USupply AT %I* : DINT; END_VAR
We can override an existing property from our base function block fbLoadCell by adding a property with exactly the same name. In our case we want to override the property ‘ActualWeight’:
PROPERTY PUBLIC ActualWeight : LREAL
VAR UDiff:LREAL; UReff:LREAL; END_VAR
UDiff := UBridge * (20.0 / 2147483647); UReff := USupply * (12.0 / 2147483647); ActualWeight:= (UDiff/UReff)/(EMax/Cn);
Now this property overrides the behavior of the property from it’s base. But how do we use this?
We could simply create a variable (instance) of fbOverridenLoadCell and use this in our program. But this doesn’t represent the power polymorphism has. We want to create a single type which can take many forms. In TwinCAT we can do this by creating a reference variable to our base function block (fbLoadCell). We can then assign an instance of either fbLoadCell or fbOverridenLoadCell to it, and use the reference variable in our program. In a simple example this could look like this:
PROGRAM MAIN VAR LoadCellBase:fbLoadCell; LoadCellOverriden:fbOverridenLoadCell; LoadCellRef:REFERENCE TO fbLoadCell; UseOverridenLoadCell:BOOL; Weight:LREAL; END_VAR
IF UseOverridenLoadCell THEN LoadCellRef REF=LoadCellOverriden; ELSE LoadCellRef REF= LoadCellBase; END_IF //Complicated program which uses one of our load cell function blocks, but doesn't care which: Weight:= LoadCellRef.ActualWeight;
We determine right at the start of our program if we want to use our basic load cell function block or our overridden one. After that our complicated program doesn’t care any more which load cell function block is used. We could even make a second load cell function block based on fbLoadCell and add it to our program without the need the change our complicated program.
There is another way to archive polymorphism in TwinCAT. This involves the use of an interface type. This is however lecture for another post 🙂 .
The THIS and SUPER pointer
If we are talking about inheritance and polymorphism we need to discuss two pointers: the THIS pointer and the SUPER pointer. These two pointer are automatically available in each function block. The THIS pointer always refers to it’s own instance. The SUPER pointer refers to the function block instance from which this function block was created.
For example let’s go our function block fbOverridenLoadCell:
FUNCTION_BLOCK fbOverridenLoadCell EXTENDS fbLoadCell VAR USupply AT %I* : DINT; WeightFromOwn:LREAL; WeightFromBase:LREAL; END_VAR
WeightFromThis := THIS^.ActualWeight; WeightFromSuper := SUPER^.ActualWeight;
It now has two additional variables: ‘WeightFromThis’ and ‘WeightFromSuper’. Calling the function block base will assign an value to the two variables. The ‘WeightFromThis ‘ variable will be assigned with the actual weight using the property from the fbOverridenLoadCell thus using the overriding behavior. However it is the algorithm from fbLoadCell which assigns the variable ‘WeightFromSuper’!
Polymorphism is the third pillar of OOP. With polymorphism we refer to single type or function blocks ability to take many forms. Like inheritance this is useful to extent existing functionality without rewriting the same code. We have seen an example of an overridden load cell function block as example of polymorphism .
Additionally we’ve shown the available pointers ‘THIS’ and ‘SUPER’. With the ‘THIS’ pointer you can invoke a function blocks own instance or one of it’s implemented method or properties. The ‘SUPER’ pointer enables you to invoke a function blocks base, from which it was created.
This was the last post in the series of ‘The three pillars of OOP’. Hopefully you learned something from it! I hope to see you again in a next post, in the mean time some more information on this topic could for example be found here and here!
Happy coding 🙂