Part 6a - Function blocks and interfaces (IEC 61131-3)

Video

#PLC #TwinCAT3 #Beckhoff #OOP #IEC_61131-3

🔙 Previous Part | Next Part 🔜

↩️ Go Back

Table of Contents:


A) Objectives

In the last part we were discussing structures and functions, which helped us to write and modularize our first programs. In this part we will start to look into the object oriented programming parts of structured text and IEC 61131-3 called "function blocks" and I think it's here things are really starting to get fun!

For those that are Java, C, C++ or just "traditional" programmers, going into the realm of function blocks is like going from C-style structures and functions, and into classes in C++
|300

With function blocks we can go from working in a procedural style programming into objected oriented style programming. I'll soon describe the main difference between these two programming paradigms.

Caviats:

  • In this part of the tutorial, I won't cover object oriented programming in too much detail as OOP is a subject in itself which would require a separate tutorial. I will only scratch on the surface of it.
    '
  • I will also not talk of whether procedural or object oriented programming is better, so this won't be "procedural vs. object oriented" comparison. You can achieve very good maintainable, high quality and beautiful code with either of them, just as you can achieve really messy spaghetti code with both.

I also want to mention that I initially thought that talking about OOP, function blocks and "interfaces" would fit inside one part, but that was a little naive. It's just too much to talk about so I have decided to split it into two parts, 6a and 6b, this one being the first of them.

This part will cover the basics of object oriented programming and function blocks. In the next part will cover a slightly more advanced topic of interfaces.


B) POP and OOP

Within the computer science field there are two major imperative programming paradigms, the "Procedural" and "Object Oriented Programming" paradigms.

In the previous part of this tutorial we were using a Procedural programming paradigm, also abbreviated POP.

1. POP

In POP you have:

|400

In the previous part of our tutorial the global data was our "event structure" and the function we wrote had some local data which were some local variables to store intermediate data for calculation of the system time.

2. OOP

In OOP you have:

|400

In POP importance is given to "functions" as well as "sequence of actions" to be done, while in OOP importance is given to "data".

Summary: With POP the program is divided into functions, while it for object oriented programming the program is divided into objects.


B.1) < Concept > - What is an Object in OOP?

Now what is an object? If we consider the real-world, we find many objects around us:

Or maybe more relevant for us in the industrial automation sector, an object might be:

Note: All these objects have a state and a behavior, which define the object.

|300

One object can talk with another object by sending messages via its methods, (either directly or indirectly as we will look at later).


B.2) < Concept > - What are Function Blocks (aka ''Classes'') in OOP?

We create objects using what is called function blocks in IEC 61131-3. For any "traditional software" developers out there this is the equivalent to "classes"

|300

Function blocks can be seen as structures with attached functions, but instead of functions they are called methods. They can be seen as a "blueprint" from which an object is created.

Let see an example on how to use Function Blocks


B.2.1) TUTORIAL 10 - Making a Motor Function Block (for a Winch and a Conveyor)

Translation:

In this example we might have an object that represents a motor.

Note: But it doesn't have to be a motor. You can represent whatever real-life object you want as one or several function blocks. It doesn't even have to be a real-life object, it can just be something imaginary like a database logger, a mathematical moving average calculation, or an object that takes care of events in the system.

A motor is an object and could have several methods such as:

Next, the motor has data that describes its state. Examples of these can be:

|180


B.2.2) How to ''instantiate'' an object from a Function Block.

Now the function block is only the blue-print of the object. To use the function block, we need to create create an instance of the function block, in this case the FB_Motor.

A new instance of a function block is declared in the same way any other variable. You set a label on the variable, which will be the instance of the function block FB_Motor, in this case fbWinch. This is the object.

|300

This object will have all the methods and data variables of the function block FB_Motor.

Now let's imagine we have a second motor in our machine that we are building, We can again just instantiate another instance of the FB_Motor, in this case fbConveyor.

Now we have two separate objects which we can use to both control the motors and see the state of them.


B.2.3) < Concept > - What is Encapsulation in OOP?

What we have discussed is one of the fundamentals of object oriented programming called encapsulation.

The general idea of this mechanism is simple:
"If you have an attribute that is not visible from the outside of an object and bundle it with methods that provide read or write access to it, then you can hide specific information and control access to the internal state of the object"

Now,

With global data we will end up with lots of unrelated bits of code that will become coupled to one another via the global variables.

We'll most likely end up with spaghetti code - which is code that is hard to reason through and follow. It's hard to understand what is reading and writing to your variables and changing the state.


B.2.4) Example - How to declare Instance Variables of a FB (aka "Local Variables")

Function blocks can have instance variables, that is, variables that are available for every instance of the function block.

Note: these Instance variables are local to the function block. This means that these variables are used for internal computation within the function block, and their scope is limited to the block itself. They are created when the function block starts executing and are destroyed when the function block finishes executing. Therefore, their values are not retained between successive calls to the function block

For a motor these could for example be:

|600

Each and every instance of the function block will holds it own sets of these variables. So let's image we first create one instance of the motor function block called fbWinch.

|500

This might at one moment show that:

Then we might have another instance of the motor function block for a conveyor belt,
|500

and this instance might at the same time show that:

Every instance of the function block will be its own object, representing the real-world object of a motor.

Copy the PLC Code:

FUNCTION BLOCK FB_Motor
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.
END_VAR

VAR_OUPUT
	// outputs from the function block. 
	// The function block can modify the values of these variables, and these can be read or used elsewhere.
END_VAR

VAR
	// local to the function block
	// internal (instance) variables
	
	bPowerOn : BOOL; // [true = on, false = off]
	fPosition : REAL; // [mm]
	fTemperature : REAL; // [°C]
	
END_VAR


PROGRAM MAIN
VAR
	fbWinch : FB_Motor; 
	fbConveyor : FB_Motor;
END_VAR


B.2.5) How to create a Function Block in TC3

A function block in TwinCAT 3 is created by right-clicking in POUs, selecting "Add", and then clicking on "POUs"
|500

This will show a new window.

  1. Write the name of the function block in the "Name" textbox,
  2. next make sure to select "Function block" as the "type".
  3. Click on Open

You have now successfully created an empty shell for a function block.

|350


B.2.6) Example - Internal State and Methods of a FB

Now, how do we access the internal state of the object, and how do we change that state? We do this through something called methods.

Imagine we have the function block FB_Motor. There are various ways we can change its state.
|300

We might have one method to change the position of the motor (or actually, more precisely the axis that the motor is controlling).
|450

Then we might have another method to change the power state of the motor called PowerEnable, which would take as input whether to power on or power off the motor.
|450

Both of these methods change the state of the motor.
|200

Copy the PLC Code:

FUNCTION BLOCK FB_Motor
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.
END_VAR

VAR_OUPUT
	// outputs from the function block. 
	// The function block can modify the values of these variables, and these can be read or used elsewhere.
END_VAR

VAR
	// local to the function block and to its methods
	// internal (instance) variables
END_VAR

METHOD PUBLIC MoveToPosition
VAR_INPUT
	fPosition : REAL; // [mm]
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code


METHOD PUBLIC PowerEnable
VAR_INPUT
	bPowerOn : BOOL; // [true = on, false = off]
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code



PROGRAM MAIN
VAR
	fbWinch : FB_Motor; 
END_VAR
-------------------------------------------------------------
fbWinch.PowerEnable(true);
fbWinch.MoveToPosition(42.0);


B.2.6.1) < Concept > - What is Abstarction in OOP?

Note that each one of these methods might do lots of things internally, but from a perspective of the user of this function block that is not interesting. The user of the function block might just want to know how to power on the motor or change the speed.

This is another pillar of object oriented programming called abstraction. Each and one of these methods might internally do many things. Changing the speed might involve the call to several functions and function blocks internally, but the caller of this method doesn't need to know anything about the internals of this method.

It only needs to know that it can power-on and change-speed, not all details on how this is actually accomplished.


B.2.7) How to create a Method for a FB

To create a method in TwinCAT, right-click on the function block in which you want to create a method in

  1. select Add,

  2. then select Method.

|450

  1. Enter the name of the method and any eventual return type.
    Note: The return type is not required.

  2. Select an access specifier, (we will get back to this shortly)

You have now created an method and you can now implement it
|400


B.2.8) How to change the state of an object (methods vs FB body call)

Now, to change the state of an object there are other ways than just calling the methods.

In structured text it's possible to call the body of the function block directly!

Going back to the motor function block, it means you could declare a function block header like this
|300

And instantiate and call it like this.
|300

And still use methods at the same time!
|500

This looks very much like the functions we used in the previous part. The only difference is that function blocks can store state over multiple PLC cycles in their local variables while a function doesn't retain the state of the local variables between two different calls of it.

Note: This is weird from other traditional langueges, but we have to remeber that a lot of the code we develop for PLCs is intended to be called cyclically and here it actually makes a lot of sense to have some of the code in the body of the function block, while other code in methods.

Copy the PLC Code:

FUNCTION BLOCK FB_Motor
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.
	
	fPosition : REAL; // [mm]
	
END_VAR

VAR_OUPUT
	// outputs from the function block. 
	// The function block can modify the values of these variables, and these can be read or used elsewhere.
END_VAR

VAR
	// local to the function block and to its methods
	// internal (instance) variables
END_VAR

METHOD PUBLIC MoveToPosition
VAR_INPUT
	fPosition : REAL; // [mm]
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code


METHOD PUBLIC PowerEnable
VAR_INPUT
	bPowerOn : BOOL; // [true = on, false = off]
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code



PROGRAM MAIN
VAR
	fbWinch : FB_Motor; 
END_VAR
-------------------------------------------------------------
// using FB body call
fbWinch(fPostion := 42.0);


// using Methods
fbWinch.PowerEnable(true);
fbWinch.MoveToPosition(42.0);


B.2.8.1) When to use "body of the FB" vs Methods (for changing the state of an instance of a FB)?

Now,

It's a little tricky to give general recommendations, and this is one of the topics that always creates long discussions with lots of opinions.

Many developers only use the body of the function block and others only use methods to change the state of an object. It's very hard to give general advice. But as much as I don't like to give general advice I will still give you a general rule that I try to follow, and it's just somewhere in-between these two extremes.

I generally use:

Again, this is just a general advice and there are many exceptions to this.

For example, for some very small function blocks I don't even think it makes sense to have any methods at all, but it only makes sense to use the body of the function block.

And as I will talk about it more in future episodes, you generally want to have very small function blocks that are doing one thing only, but that are doing that one thing very good.

This is however a subject for a coming episode. Now I'll quickly mention inheritance.


B.3) < Concept > - What is Inheritance in OOP?

Inheritance is the procedure in which one function block inherits the attributes and methods of another function block.
|300

The function block whose properties and methods are inherited is known as:

And the function block that inherits the properties from the parent function block is:

The interesting thing is, along with the inherited properties and methods, a child function block can have its own properties and methods.

Inheritance is one of the pillars of object oriented programming.

Let's look at one example,


B.3.1) TUTORIAL 11 - Making a User Notification Function Block with Inheritance (using SMS and Email)

Let's imagine we have a function block FB_Notification, that handles notifications to different users.
|230

The notification will have a few attributes such as:

Now, we can imagine that we want to send out notifications as an SMS. For this we can create a separate function block that only cares and handles about SMS notifications.
|330

This is still though a notification, and it should have all the properties of a normal notification such as the first and last name etc, but with the addition of a phone number.

The phone number is what is explicit only to an SMS notification.

There is a IS-A relationship between the function block FB_SMSNotification and the function block FB_Notification.

Now, imagine we also want to send notifications by e-mail. For this we would need a function block FB_EmailNotification.
It still has all the properties of a normal notification, with the addition of an e-mail address.
|430

By doing this we can have code that is shared and re-used between various function blocks.

Note: it's possible to do multiple levels of inheritance, but a function block can only inherit from exactly one function block.


B.3.2) TUTORIAL 12 - Testing EtherCAT Slave communication with FB Inheritance (using EL1008 and EL2004 terminals)

Let's now use an example that is slightly more related to the world of industrial automation and TwinCAT 3 software.

Generally when communicating with sensors and actuators you need to communicate with the outside world by some means. When using Beckhoff PLCs you generally use something called EtherCAT, which is like Ethernet on steroids, it's basically a deterministic version of Ethernet.

I'm going to get back to this in much further details in part 10 of this tutorial, but for now you only need to know that every type of terminal to get sensor-data or actuate something that you might want to communicate with has something called an EtherCAT slave controller inside of it.

|270

which is defined here by the function block FB_EtherCATSlave.
|430

Every EtherCAT slave will provide some basic diagnostics, which in this case is some infodata which will provide whether the EtherCAT slave is operational.

We can therefore provide a method IsOperational() which just provides a single boolean of whether the EtherCAT slave is operational or not.

Now there are many different terminals depending on what type of sensors and actuators that you want to control. For example we could have a 8 channel digital input terminal. This would be a function block FB_Beckhoff_EL1008,

|540

which is one type of terminals that Beckhoff provides. It provides its own special features, as reading 8 digital booleans, which is indicated by the array with size 8.

Because it inherits the function block FB_EtherCATSlave, it also automatically has all the functionality of that function block which means in this case it would have the IsOperational() method.

And we could have many more type of function blocks that extend the EtherCATSlave function block.

Here we have a 4 channel digital output terminal called EL2004.
|540

Instead of getting 8 input channels, we can now set 4 output channels. It too extends the FB_EtherCATSlave function block as it too has an EtherCAT slave controller and thus provides the IsOperational status.

Now I've used two Beckhoff terminals as examples. But it doesn't have to be a Beckhoff terminal, it can be anything that has an EtherCAT slave controller inside of it. All of them will have the Infodata_State variable and each and one of them will return whether they device is operational or not.

We don't have to implement these specifics for each terminal that has an EtherCAT slave controller as each and one of them inherits the data fields and methods from the EtherCAT slave controller function block.

Note: We will get back to more details of fieldbuses and EtherCAT in episode 10 of this tutorial.


B.3.2.1) How to extend a FB (using Inheritance)

To extend a function block, simply use the EXTENDS keyword.
|400

In this example, the function block FB_Beckhoff_EL1008 extends the function block FB_EtherCATSlave.

Now let's assume we have a program where we declare an instance of the function block FB_Beckhoff_EL1008
|300

Because FB_Beckhoff_EL1008 extends FB_EtherCATSlave, we can do a call of the IsOperational() method on it, and thus make it return whether that particular terminal is operational or not.
|530

Copy the PLC Code:

FUNCTION BLOCK FB_EtherCATSlave
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.	
END_VAR

VAR_OUPUT
	// outputs from the function block. 
	// The function block can modify the values of these variables, and these can be read or used elsewhere.
END_VAR

VAR
	// local to the function block and to its methods
	// internal (instance) variables
	
	nInfoData_State : UINT;
	
END_VAR

METHOD PUBLIC IsOperational
VAR_INPUT
	bStatus : BOOL;
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code



FUNCTION BLOCK FB_Beckhoff_EL1008 EXTENDS FB_EtherCATSlave
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.	
END_VAR

VAR_OUPUT
	// outputs from the function block. 
	// The function block can modify the values of these variables, and these can be read or used elsewhere.
END_VAR

VAR
	// local to the function block
	// internal (instance) variables
END_VAR

METHOD PUBLIC GetChannelInputs
VAR_INPUT
	// Create an Array
	aInputs : ARRAY[1..8] OF BOOL;
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code



FUNCTION BLOCK FB_Beckhoff_EL2004 EXTENDS FB_EtherCATSlave
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.	
END_VAR

VAR_OUTPUT
	// outputs from the function block. 
	// The function block can modify the values of these variables, and these can be read or used elsewhere.
END_VAR

VAR
	// local to the function block
	// internal (instance) variables
END_VAR

METHOD PUBLIC SetChannelOutputs
VAR_INPUT
	// Create an Array
	aOutputs : ARRAY[1..4] OF BOOL;
	// Create a Reference and the value 
	nReference : REFERENCE TO ARRAY REF= aOutputs;
END_VAR
VAR
	// local (scratch) variables
END_VAR
------------------------------------------------------------
// code



PROGRAM MAIN
VAR
	fbEL1008_term1 : FB_Beckhoff_EL1008; 
	bIsTerm1_Op : BOOL;  
END_VAR
-------------------------------------------------------------
// code
bIsTerm1_Op := fbEL1008I_term1.IsOperational();

Some thoughts on "Inheritance":
Used in the right places inheritance can be very useful, but generally I would say it causes a lot of problems.
It's a common misconception amongst beginners that inheritance is the true power of object oriented programming.

I don't want to dwell too much into the discussions of what parts of OOP is bad and good especially in an introduction tutorial to TwinCAT3, but I just want to mention that I've seen inheritance being so extremely overused and where it has been used in so many places where it doesn't make any sense at all and where there are better options available.

I'll discuss this a little bit in the next part of this tutorial, but most likely I will leave these type of discussions to a future advanced PLC programming tutorial.


C) TUTORIAL 13 - Event logger FB //FILE -->TimeProject (part2)

Alright, it's time for a little bit of programming! Fun fun fun!


C.1) Event logger FB functionality (1st iteration)

In this part we will continue to develop the event logger functionality that we did in the last part.

What we're going to do now is to create an event logger function block and what we want this function block to do is to store all our created events.
|320

Note: With a few more detail, the FB would look like this

Once an event is added the current system time will be added to this event just as in the previous part of this tutorial.
The amount of events that we can store is a fixed size and the amount will just grow until our buffer is full. Once the buffer is full we can't more events.

In this iteration the event logger function block will be very limited in functionality. I just want you to get a little grip of creating a function block, some (local) variables and a method (or a couple of them).

So in this iteration the functionality will be limited and we will continue to develop this function block and the functionality around the function block in the next part of this tutorial, in other words part 6b.


C.2) Creating the Event logger FB

What I've done here is that I've opened the solution from the previous part of this tutorial. Part 5 - Structures and functions (IEC 61131-3)

And what we're going to do first is to create a function block!

|450

|230

And we name it FB_EventLogger (or since this is a modern IDE we can just call it EventLogger).

Note: This convention of adding "FB_" is just a convention I've always had. These conventions of adding FB_ or F_ or whatever it's mostly due to the history PLC programming, it's more of the times of when you didn't have a very good IDE and you didn't immediately see what type of data it was. Nowadays when using a modern development environment like Visual Studio for TwinCAT, it's actually not that useful anymore. So what I'm actually going to do now, to not learn you bad habits, is to remove "FB_" and just call it EventLogger.

So the name of the function block is EventLogger,

|230

Now we have the shell for our function block EventLogger.
|250

You can already immediately see here an "FB" that it is indeed a function block so the development environment already tells us that it's a function block.
|250

Do you remember that we had the structure here in DUTs? This was the structure that defined our event.
|250

So we have:

So this is what defines our event. And what we're going to do now is that we need a structure to store all these events.

We don't need any inputs or outputs, I always remove them until I eventually need them because I just like to keep the code as clean as possible.

|250

We need an array to store this data, so right now I'm just going to create an array

Note: remember to add descriptive code. Because once you start adding comments eventually at some point the comment will not be correct anymore so it will describe something that's not true anymore. Because the code will change, while the comments won't. The only thing that's worse than comments are comments that are not correct. So try to use comments as little as possible.

In this case I would just name the array EventBuffer.
|350

You have 100 which is a number, which I know is the maximum size (of the buffer).

But here is also an opportunity to add more documentation to the code so I would just create a constant.

Constants are variables that can't be changed (during runtime). I would call the constant MAXIMUM_SIZE_OF_EVENT_BUFFER, make it an integer and assign it the value 100.
|350

And then use that instead,
|450


C.3) Creating the AddEvent method (part1 - PUBLIC method)

What we're going to do now is to add our method that will add our event to this function block.
|320

Note: With a few more detail, the PUBLIC Method would look like this:

Now what I'm going to do is to right-click on the EventLogger FB, click "Add" and then "Method"

And here we have to give the method a name so I will just call it "AddEvent"
|250

We can actually give a return type. For example we could indicate whether the addition was successful or not (it would fail to add the event if our buffer is full). So we can return a BOOL.

Then we need to select "Structured Text" and add an access specifier. And access specifiers is something I haven't mentioned so I guess now is a good opportunity for that.

Now I'll briefly talk about access modifiers.


C.3.1) < Concept > - What are Access modifiers? (PUBLIC, PRIVATE, PROTECTED, INTERNAL)

When you declare a method inside a function block you have to set one of four access modifiers.


1. PUBLIC:
With PUBLIC everyone has access to the method. This means that the function block itself has access to it, as well as anyone else outside of the instance of the function block.

So if you would instantiate a function block X with method Y, and instantiate the function block Z, then function block Z could call Y.
|345

PROGRAM MAIN
VAR
	// Declare intances of the FBs
	fbX : FB_X; 
	fbZ : FB_Z; 
END_VAR
-------------------------------------------------------------
// using Methods
fbX.MTHD_Y();
fbZ.MTHD_Y();


2. PRIVATE:
With PRIVATE only the function block of where the method is declared has access to the method.

Access Modifier Parent Class Child Class Unrelated Class
Public Accessible Accessible Accessible
Private Accessible Not Accessible Not Accessible

3. PROTECTED:
With PROTECTED only the function block of where the method is declared and any of its children have access to the method
With children I mean any function block that EXTENDS the function block that has the PROTECTED method.

Access Modifier Parent Class Child Class Unrelated Class
Public Accessible Accessible Accessible
Private Accessible Not Accessible Not Accessible
Protected Accessible Accessible Not Accessible

4. INTERNAL:
With the access specifier of INTERNAL only the function block of where the method is declared and any function blocks that reside in the same library have access to the method.

Note: We are not going to talk too much about libraries right here and right now, we will come back to libraries in part 11 of this tutorial.

Access Modifier Parent Class Child Class Unrelated Class
Public Accessible Accessible Accessible
Private Accessible Not Accessible Not Accessible
Protected Accessible Accessible Not Accessible
Internal Accessible within the same module or assembly Accessible within the same module or assembly Not Accessible

Summary:

|400


C.3) Creating the AddEvent method (part2 - continue)

We select an access specifier of PUBLIC because we want this method to be able to call it outside of the function block... so we want to be able to instantiate the EventLogger FB from another FB or from the MAIN program or whatever and then be able to call this method "AddEvent" so it has to be PUBLIC
|250

Now we have our AddEvent() which returns a BOOL. Now for someone that's calling this method might not know why this method returns a BOOL. So here this is an API type of call so now it actually makes sense to add a comment.
|450

Now the user of this function block will know what this boolean is.


- Variables for the AddEvent method:
What we're going to add now is some input variables.

Note: We could just use this whole structure,
|300

But that's a little confusing because then the caller has to provide the date and timestamp, which we don't want because we want the date and timestamp to be added automatically in the function block. So once the user adds this event we just want the FB to automatically create this system timestamp and add it internally. We want to add everything except the date and timestamp, so we can just copy and paste these variables and add them as input variables.
|400

So the user needs to provide:


C.4) Creating the IsEventBufferFull method (PRIVATE Method)

If we are going to add an event we need to know whether our event buffer is full or not. We need to keep track of how many events we already have in the system. In our FB EventLogger we need to add a variable that keeps track of this so we call it "CurrentEventBufferIndex". It's just going to be an integer. This starts at zero, so it means that when we add our first event this will go up to one and then we are going to store the first event at location one in this array all the way up too 100.

|450

Copy the PLC Code:

FUNCTION_BLOCK EventLogger
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.
END_VAR


VAR_OUTPUT
	// outputs from the function block. 
	// The function block can modify the values of these 
END_VAR


VAR
	// local to the function block and to its methods
	// internal (instance) variables
	
	aEventBuffer : ARRAY[1..MAXIMUM_SIZE_OF_EVENT_BUFFER] OF ST_Event;
	nCurrentEventBufferIndex : INT := 0;
	
END_VAR

VAR CONSTANT
	// local to the function block and to its methods
	// these variables cannot be changed during program execution.
	
	MAXIMUM_SIZE_OF_EVENT_BUFFER : INT := 100;
	
END_VAR

What we need to do now is to check whether the event buffer is full. We have to check whether the current location of where we are is above 100. We can create a private method that check if the buffer is full or not.
|330

Note: With a few more detail, the PRIVATE Method would look like this:
|400

This method can be private because there is no one else outside of this function block that needs to access it.

The AddEvent() needs to be PUBLIC because that's how someone adds an event by calling it. But this new method we only need to do internally in this FB.

We call this IsEventBufferFull() which is a very good and descriptive name. The method tells everything we need to know.
Is the EventBuffer full or not? so it just returns a boolean and it can be private. It doesn't have any input and it only returns a boolean.
|300

|230

The only thing that this method has to do is to return whether the buffer is full or not.
If the current index plus the next one (the one we are going to add) is larger than the maximum size then the event buffer is full.
|350

Copy the PLC Code:

METHOD PRIVATE IsEventBufferFull : BOOL


-------------------------------------------------------------
//Check if the event buffer is full
IF (nCurrentEventBufferIndex + 1) > MAXIMUM_SIZE_OF_EVENT_BUFFER THEN
	IsEventBufferFull := TRUE; //method returns TRUE, it is full
ELSE 
	IsEventBufferFull := FALSE; //method returns FALSE, it is not full
END_IF


C.4) Using our PRIVATE Method and our Custom Function

- Using IsEventBufferFull():
Now we have a neat little function that we can call in our AddEvent()
|350

This is really neat. Now our code is much more descriptive.

If the event buffer is not full we can add our event... here we will write some code soon...
But if it is full then we just want to return AddEvent = FALSE,
|300

because we return FALSE if the buffer is full.

If it's not full, then we will return TRUE
|300

Now we are going to add our event, so in the event buffer we are going to add our event in the location of where we are currently plus one.

Note: remember that CurrentEventBufferIndex starts at 0
|400

So we already increment this CurrentEventIndexBuffer by one because we are going to go to the new location, so if we start at zero we want to go to number one which is the first one, all the way up to 100
|400

Then we want to store the Event in this location, so we write this code for the first 4 variables in the structure.

|450

- Using the UpdateEventTimestampWithSystemTime(stEvent):
Then we have to deal with the Time Stamp. In this event structure, our timestamp is the last one. So we need to store it as well.
To add our date and timestamp we are going to use our UpdateEventTimestampWithSystemTime(stEvent) function that we wrote in the last part.
Part 5 - Structures and functions (IEC 61131-3)

What this function does is that it takes the whole event structure as input.

Note: You could argue that you could have another function here that would just return the system time as an date_and_time timestamp instead, but this is how we did it in the last part of this tutorial and we will re-use this function which makes a lot of sense.

We need to make a call of this UpdateEventTimestampWithSystemTime function. (It's a very long name...).
And we need to provide it an stEvent,
|350

and the stEvent here that is the current event buffer structure of where we are right now.
The event buffer is an array of events. So we only need to provide the current location of where we are right now. Like this:
|400

Let's recap, how does this work?
The function will take a reference to this buffer location and only update the date_and_time timestamp of this event. That's pretty much everything we need to do.

There are lots of limitations (of this FB). For example, when the buffer is full we are not going to do anything with the event.
|400

You could argue that for example maybe we want the oldest event to be removed from the queue. Maybe we want it to be a linked list so we can remove an event in the middle. We could spend lots of time showing you this but again I want to save a little bit of the fun for the next part of this tutorial.


C.5) Testing our FB EventLogger and its methods

What we need to do now is to use this FB and see that it's actually working.

In MAIN, we can remove the code from the previous part of this tutorial
|350

What we're going to do now is to create an instance of this function block event logger. And you create an instance just like any other variable.
So you create an instance of a FB just like if you would create an INT, a BOOL or whatever. So it will reserve the memory in the PLC for this instance of this FB, and we can just call the instance the same name as the FB.
(Note: We can also call it EventLogger1 just to show that it is an instance and not the FB).
|250

We have an instance of EventLogger and now we can also try to add an event to it just to see that it works.
Here in MAIN we can just create some variables that are necessary to add an event. That is these guys, and we can just set some values to them,
|260

We can have a boolean, and if this boolean is TRUE then we add this to the event logger.
|260

Now we can call the FB to Add the Event in one PLC Cylce,
|350

Note: we could have many event loggers, we can have Event logger2, EventLogger3 and so forth...
Again, this is a FB and it's just like any other variable. We can create as many instances of it as we want.
|350

For the event logger it typically makes sense to only have one because you want to have a global event logger for the whole system. This is ONE example of a function block, you can have as I mentioned before you could have motors, maybe temperature sensors or whatever. In an automation project you would normally have many instances

- Running the MAIN PRG:
Ok I guess it's time to run this, so we will activate configuration,
|300

|200

Yes, we want to go into RUN mode...
|200

We click "yes"
|300

And Execute,
|200

So now we are running the runtime, and the binary is being executed!

But we haven't added the event because the boolean is FALSE

and what you can actually do in this online view is that you can login to the event logger and here you can actually see:

Note: All of them just have the default values because they are empty, they don't have any events.

What we're going to do now is to change this boolean to TRUE and do an online-write,

and then we will get an event added with the values that we prepared in MAIN, which are these.
|500

Now we go to the EventLogger, now the index incremented to one,
|330

which means that the first one should hopefully have our event...
|350

Which it does!
And it also has the system time timestamp in it. So it worked.

That's about it for now, In the next part where we will add some more functionality around this function block.

In regards with this thing called interfaces...

And that's it for this part! We have only scraped the surface of the capabilities of function blocks and object oriented programming in TwinCAT 3

See you in the next video!


F) Extra (UML Diagrams in Object Oriented Programming)

In programming, there are various ways to graphically represent a function and its inputs and outputs. Here are a few common methods:

  1. Flowchart: A flowchart is a graphical representation of a function's logic using different shapes to represent actions, decisions, and input/output operations. You can use rectangles for processes, diamonds for decisions, and arrows to show the flow of control.

  2. Input-Output Table: Create a table that lists the inputs on one side and their corresponding outputs on the other side. This method is simple and can be easily visualized in a text editor or on paper.

  3. XY Plot: For functions with numerical inputs and outputs, you can create a simple XY plot. Plot the inputs on the X-axis and their respective outputs on the Y-axis. This method is especially useful for visualizing mathematical functions.

  4. Bar Chart: If your function involves categories or discrete data, a bar chart can be useful. Each input can be represented by a bar, and the height of the bar can represent the corresponding output.

  5. Graph Visualization Tools: You can use graph visualization tools like Graphviz or networkx (for Python) to create visual representations of functions with complex data structures.

  6. UML Diagrams: For object-oriented programming, Unified Modeling Language (UML) diagrams can help represent classes and their methods, including input and output parameters.

  7. Sankey Diagram: A Sankey diagram is a type of flow diagram where the width of the arrows is proportional to the flow quantity. It can be useful for visualizing data transformation between inputs and outputs in a function.

Remember that the choice of graphical representation depends on the complexity and nature of the function you want to visualize. For simpler functions, a flowchart or an input-output table might suffice, while for more complex functions, a graph visualization tool or UML diagrams might be more appropriate.


E) Summary

E.1) OOP concepts

|300

  1. Abstraction: Abstraction simplifies complex objects by focusing on their essential characteristics while hiding unnecessary details. It provides a high-level view through abstract classes and interfaces, serving as a blueprint for other classes to follow.

  2. Inheritance: Inheritance allows one class (subclass) to inherit properties and behaviors from another class (superclass). This promotes code reusability and establishes a hierarchical relationship among classes, with specialized subclasses extending the functionality of the base class.

  3. Polymorphism: Polymorphism enables objects to take on multiple forms or behaviors. It includes compile-time polymorphism (method overloading) and runtime polymorphism (method overriding). Objects can be treated uniformly, regardless of their specific subclass type, providing flexibility and extensibility in the code. We will learn more about it in the next part

  4. Encapsulation: Encapsulation is the practice of bundling data (attributes) and methods (behaviors) together within a class and controlling access to them through access modifiers (public, private, protected). It helps protect the internal state of an object and ensures that data is accessed and modified only through defined interfaces (methods).

How they are related:

In summary, abstraction simplifies objects, inheritance promotes code reuse and hierarchy, polymorphism enables objects to have multiple forms, and encapsulation protects data and behaviors within a class. Together, these four concepts form the core principles of object-oriented programming, leading to well-structured, flexible, and secure software development.
|300

Learn more here: Introducción a la POO por EDteam


E.2) TimeProject (part2) with Event logger FB

E.2.1) DUTs

  1. Enum
TYPE E_EventType :
(
	Alarm := 0,
	Message := 1
);
END_TYPE
  1. Structure
TYPE ST_Event :
STRUCT
	eEventType : E_EventType;
	eEventSeverity : TcEventSeverity;
	nEventIdentity : UDINT;
	sEvenText : STRING(255);
	dtTimestamp : DATE_AND_TIME;
END_STRUCT
END_TYPE

Remember:
|500

Diagram:


E.2.2) POUs

E.2.2.1) FUN
  1. Function
FUNCTION UpdateEventTimestampWithSystemTime
VAR_INPUT
	stEvent : REFERENCE TO ST_Event;
END_VAR
VAR
	fbGETSYSTEMTIME : GETSYSTEMTIME;
	stFileTime : T_FILETIME; // requires Tc2_Utilities
END_VAR

------------------------------------------------------------------
// The function block writes on stFileTime (VAR_OUTPUT) in T_FILETIME format 
fbGETSYSTEMTIME(timeLoDW => stFileTime.dwLowDateTime,
				timeHiDW => stFileTime.dwHighDateTime);

// Overwrites the Timestamp of stEvent using the FILETIME_TO_DT function
stEvent.dtTimestamp := FILETIME_TO_DT(fileTime := stFileTime);

Remember:
A) GETSYSTEMTIME FB
|300

B) T_FILETIME Structure as output of the FB
|500

C) Function to convert FILETIME to DATE_AND_TIME (DT)
|250

Diagram:

E.2.2.2) FB
  1. Function block
FUNCTION_BLOCK EventLogger
VAR_INPUT
	// inputs to the function block. 
	// These are read-only, meaning you cannot modify their values within the block.
END_VAR


VAR_OUTPUT
	// outputs from the function block. 
	// The function block can modify the values of these 
END_VAR


VAR
	// local to the function block and to its methods
	// internal (instance) variables
	
	aEventBuffer : ARRAY[1..MAXIMUM_SIZE_OF_EVENT_BUFFER] OF ST_Event;
	nCurrentEventBufferIndex : INT := 0;
	
END_VAR

VAR CONSTANT
	// local to the function block and to its methods
	// these variables cannot be changed during program execution.
	
	MAXIMUM_SIZE_OF_EVENT_BUFFER : INT := 100;
	
END_VAR
----------------------------------------------------------------
// code

4.1 Private Method

METHOD PRIVATE IsEventBufferFull : BOOL


------------------------------------------------------------------
//Check if the event buffer is full
IF (nCurrentEventBufferIndex + 1) > MAXIMUM_SIZE_OF_EVENT_BUFFER THEN
	IsEventBufferFull := TRUE; //method returns TRUE, it is full
ELSE 
	IsEventBufferFull := FALSE; //method returns FALSE, it is not full
END_IF

4.2 Public Method

(* Returns TRUE if add was succesful and FALSE if buffer is full *)
METHOD PUBLIC AddEvent : BOOL
VAR_INPUT
	// user input
	eEventType : E_EventType; //user defined ENUM
	eEventSeverity : TcEventSeverity; // library ENUM
	nEventIdentity : UDINT;
	sEvenText : STRING(255);
	
END_VAR

------------------------------------------------------------------
//Check if the event buffer is full using a private method
IF NOT IsEventBufferFull () THEN
	
	nCurrentEventBufferIndex := nCurrentEventBufferIndex + 1;
	
	// from user input, fill buffer
	aEventBuffer[nCurrentEventBufferIndex].eEventType := eEventType;
	aEventBuffer[nCurrentEventBufferIndex].eEventSeverity := eEventSeverity;
	aEventBuffer[nCurrentEventBufferIndex].nEventIdentity := nEventIdentity;
	aEventBuffer[nCurrentEventBufferIndex].sEvenText := sEvenText;
	
	// using our function, fill the date and time stamp
	// input current event to the function
	// updates only date and type stamp of the input event
	UpdateEventTimestampWithSystemTime(stEvent := aEventBuffer[nCurrentEventBufferIndex]);
	
	AddEvent := TRUE; // add was succesful
ELSE
	AddEvent := FALSE; // buffer is full
END_IF

Diagram:


E.2.2.3) MAIN
  1. Main
PROGRAM MAIN
VAR
	(* 
	  --------------- PART 1 ---------------------
	stThisIsOurFirstInstanceOfAStructure : ST_Event;
	bBoolean : BOOL := FALSE; 
	  --------------------------------------------
	*)
	
	bBoolean : BOOL := FALSE; 
	
	// Define sample values to the variables needed to add an Event
	eEventType1 : E_EventType := E_EventType.Alarm; //user defined ENUM
	eEventSeverity1 : TcEventSeverity := TcEventSeverity.Error; // library ENUM
	nEventIdentity1 : UDINT := 42;
	sEvenText1 : STRING(255) := 'This is a sample event';
	
	// Create one instance of our FB EventLogger
	EventLogger1 : EventLogger; 
	
	
END_VAR

------------------------------------------------------------------
(* 
  ----------------- PART 1 ---------------------
IF NOT bBoolean THEN
	UpdateEventTimestampWithSystemTime (stEvent := stThisIsOurFirstInstanceOfAStructure);
	bBoolean := TRUE;
END_IF
  ----------------------------------------------
*)

IF bBoolean THEN
	// Add an event (in one cycle of the PLC)
	EventLogger1.AddEvent(eEventType1, eEventSeverity1, nEventIdentity1, sEvenText1); 
	bBoolean := FALSE;
END_IF

Full Diagram:


🔙 Previous Part |Next Part 🔜

↩️ Go Back


Z) Glossary

File Definition