TwinCAT 3 Tutorial: Structured Text

This chapter is part of the TwinCAT 3 Tutorial.

TwinCAT 3 includes all five IEC-61131-3 languages: Ladder Diagram, Structured Text, Function Block Diagram, Sequential Function Chart, and Instruction List. If you’re coming from the Allen-Bradley world then obviously Ladder Diagram is going to be your most comfortable language, but I expect you’ll also want to make use of Structured Text. In fact, Beckhoff themselves typically present Structured Text as the go-to language for programming in TwinCAT 3.

I prefer writing most of my programs in Ladder Diagram for the obvious reasons: ease of troubleshooting, and the ability of electricians to go online with the program and debug it. However, we can’t forget that old adage, “use the right tool for the job,” and there are times when Structured Text is the right tool, and Ladder Diagram is not.

Structured Text has similarities to Pascal or BASIC (at least after they removed the concept of line numbers from BASIC). The most applicable feature of Structured Text for us are LOOPs.

The FOR Loop

Imagine for a moment that you have an array of a thousand REAL data values and you want to compute the average of those values. The formula is pretty simple: just add them up and divide by 1000. Obviously this presents some difficulty in Ladder Diagram, but in Structured Text, we can just use a FOR loop.

Start by creating a new function. Call it AverageOf1000 and make sure you select a function with the return type of REAL, and Structured Text (ST) in the Implementation Language drop-down box:

01 Add POU AverageOf1000

Click Open. Now you’ll have an empty Structured Text function:

02 Empty AverageOf1000 Function

We could pass the array in as an input, but if you remember from the last section, that would mean copying the entire array every time this function is called, which could negatively impact the scan time. It’s better to pass large data structures like this by reference, which means we declare it as a VAR_IN_OUT variable:

03 Declare Array as VAR_IN_OUT

Next declare some local variables: one to store the sum of the values, and another to be an index to hold where we’re pointing to in the array.

04 Declare Sum and Index as VAR

Now we can write our logic, which consists of a FOR loop and a division operation:

05 AverageOf1000 Function Body

On line 1 it initializes the value of variable Sum to 0. Note that the := operator means assignment. It computes the expression on the right (the RValue) and stores it in the variable on the left (the LValue). Also note that each statement ends with a semi-colon. This is important and you’ll get a syntax error if you don’t include it (the exception is the semi-colon at the end of line 4, which is optional, but frequently included in many Structured Text examples).

Lines 2 and 4 define the FOR loop. Line 2 defines a loop index variable (called Index in this case), followed by an assignment symbol (:=). This means the Index variable will take on the values from 1 to 1000 and BY 1 means it will count by 1. The lines between 2 and 4 are what will be executed with each value of Index.

If you were to watch the runtime execute this logic, what you’d see is (roughly):

  • Set Index to 1
  • Execute line 3
  • Set Index to 2
  • Execute line 3
  • Set Index to 3
  • Execute line 3
  • Set Index to 4
  • Execute line 3
  • Set Index to 5
  • Execute line 3
  • Set Index to 999
  • Execute line 3
  • Set Index to 1000
  • Execute line 3

As you can see, loops can have a significant impact on scan time, especially as the number of iterations becomes high. If you’re running TwinCAT 3 on a modern PC, then 1000 iterations isn’t too bad, but executing a million iterations on a 2 GHz PC is likely going to take a minimum of 0.5 milliseconds, and that’s without doing anything in the loop. You have to be aware of this and program accordingly. If you’re averaging the list of the last 100 sensor readings, don’t even worry about it, but if you’re doing math-heavy computation on thousands of data points, be aware that it might be too much work to do in one scan time.

Line 5 takes the Sum and divides by 1000, assigning the result to the return value of the function. Note that I added a decimal point to the value 1000.0 and I did this to remind the reader that I’m dealing with floating point numbers here. This is a style choice. You don’t have to do it.

The WHILE Loop (and IF/THEN/ELSE Blocks)

Another type of loop is the WHILE loop. Instead of executing a fixed number of times like a FOR loop, it can execute as long as some condition is true. For instance, let’s say we want to find the first index in an array where the value is greater than some value:

06 WHILE Loop and IF THEN Block Example

The purpose of this function is to search an array of 1000 values and return the first index where the value is greater than some Threshold. If it doesn’t find any values greater than the Threshold then it returns 0, which is an invalid index.

Line 1 initializes a boolean flag, Found, to FALSE. Since this is a function, it’s not really necessary because the value would be initialized to false every time you call the function, but if this was a function block, then you’d want to include that line because the value would be retained from call to call.

Line 2 initializes the Index variable to the first array index (1). Lines 3 and 9 define the WHILE loop. Lines 4 through 8 will be executed repeatedly as long as the expression in line 3 returns true. As you can see, we loop until either we find it, or the Index passes the upper bound of the array.

Lines 4 through 8 comprises an IF/THEN/ELSE block. If the expression in line 4 is true, then it executes line 5. If the expression on line 4 is false, then it executes line 7 instead.

To demonstrate how this works, assume the values in the array are 25, 50, 75, 100, 125, etc. Also assume Threshold is 80. We would expect the function to return a value of 4. Here’s how the function executes:

  • Line 2 sets Index to 1
  • Line 3 evaluates to true because Found is false and Index is 1
  • Line 4 evaluates to false (25 is not greater than 80)
  • Line 7 sets Index to 2
  • Line 3 evaluates to true because Found is false and Index is 2
  • Line 4 evaluates to false (50 is not greater than 80)
  • Line 7 sets Index to 3
  • Line 3 evaluates to true because Found is false and Index is 3
  • Line 4 evaluates to false (75 is not greater than 80)
  • Line 7 sets Index to 4
  • Line 3 evaluates to true because Found is false and Index is 4
  • Line 4 evaluates to true (100 is greater than 80)
  • Line 5 sets Found to true
  • Line 3 evaluates to false because Found is true
  • Line 11 evaluates to true
  • Line 12 sets the return value of the function to 4 (because Index has the value 4)

While this is a perfectly reasonable function, there are also some problems with it.

First of all, the scan time is quite variable. The worst case scan time is when the value isn’t found, and it returns 0. In that case it iterates through the entire array. In the best case it returns 1. Variable scan times can lead to problems if the worst case is never tested, or if you have a lot of functions like this and there’s some diabolical case where all of them have to execute the worst case on the same scan, and you exceed your allowable scan time.

Secondly, the logic is complex. Some of you might be laughing at me for saying that. If you’re a PC programmer writing code in C or BASIC then the function above is actually quite simple, yet in PLC programming we have an abnormal emphasis on simplicity. We want logic that is obviously correct when we look at it, and the above function isn’t obviously correct unless you give it a significant amount of analysis. To analyse it you really have to “play computer” and walk through at least 2 different scenarios: one where the value is found, and one where it’s not found.

Earlier in this section I talked about expecting electricians to go online with our programs and do troubleshooting. An electrician can understand Ladder Diagram, and with a little bit of work they can probably understand the FOR loop example above, but there are going to be a lot of people who won’t be able to understand this example of a WHILE loop with IF/THEN/ELSE blocks. If you believe these people don’t have any business going online with a PLC, then suggest you should change your attitude. Automation is a team sport and we have no room on the team for big egos.

Use the simplest logic you possibly can (not the shortest). If the machine you’re programming has 10 motors, don’t try to write the motor start/stop logic in Structured Text with a FOR loop. Don’t even make a function block and re-use it 10 times. Just write 10 different programs in Ladder Diagram and copy the logic. Sure they might share some common logic, like an OkToRunMotors coil that gets set in another program. Remember that these are 10 physically different motors and the conditions for starting and stopping them are likely to change over time. Recognize that and keep the logic separate.

On the other hand, Structured Text is the right tool for the event-logging and recipe-handling logic of a program. An electrician logging into the PLC to understand why a motor isn’t starting isn’t going to be concerned with the event-logging module. Structured Text is also the right tool for manipulating data, such as a scan received from a barcode scanner or an RFID reader. Complex math is also more easily expressed in Structured Text.

Using the right tool for the job means taking more than the problem itself into account. Make sure you take the capabilities of your team and the customer’s capabilities into account too.

Don’t Loop on an Input

A novice programmer will write this:

07 Loop Forever

Notice how we’re looping on an input. An input is a real-world physical input. It only changes when an I/O scan happens. When the runtime executes this logic, it will enter the loop and potentially never exit, and none of the rest of your program will execute again. The machine will appear to freeze, outputs will stay in their last state, and bad things will happen. Simply, if you’re using an input as the conditional in a WHILE loop, then you don’t have a good understanding of how the PLC runtime works, and you need to stop and go back to the beginning.

Most PLCs work by reading the physical inputs into memory, running the program logic, and copying the new values of the outputs to the actual physical outputs (that’s a simplification and not true of all PLCs, but it’s a good model to start with), and then doing it again and again. The amount of time it takes to do all that is your scan time, and we want the scan time to be as short as possible. Causing the program to enter a loop that waits for an input to turn on will essentially stop the program. In some cases it will also prevent the I/O scan from happening, so it’s impossible for that input to change state again. The machine will freeze forever.

Ladder Diagram doesn’t give you the option of shooting yourself in the foot like this, but Structured Text does. Stay away from infinite loops.

Mixing Ladder Diagram and Structured Text

I’ve shown you how you can write programs, functions, and function blocks in Structured Text, but sometimes it’s nice to add a little Structured Text in the middle of your Ladder Diagram program. It turns out that a program can include something called an Action (which is like a mini local sub-program that you can call from your program) and the Action can be written in a different implementation language than the parent program.

To add an Action, right click on an existing (Ladder Diagram) program and choose Add->Action… from the context menu. All you have to enter is a Name and choose an Implementation Language. Choose Structured Text. The new Action will show up in the Solution Explorer under your program.

The Action has access to all the declarations (inputs, outputs, and local variables) of the parent program or POU. You can call the Action like any other program: just add a block and enter the Action name.

String Functions

String functions can be used in both Ladder Diagram and Structured Text, but when you start to do complicated string manipulation then I suggest moving into Structured Text because it can be easier to understand.

Here are your typical string functions and what they do:

  • LEN(s) – returns the number of characters in string s
  • LEFT(s, n) – returns the n left-most characters from string s, or returns s if n > LEN(s)
  • RIGHT(s, n) – returns the n right-most characters from string s, or returns s if n > LEN(s)
  • MID(s, n, p) – returns n characters from string s, starting at position p (first character number is 1, not 0)
  • CONCAT(s1, s2) – returns strings s1 and s2 joined (concatenated) together
  • INSERT(s1, s2, p) – returns a new string formed by inserting s2 into s1 at position p
  • DELETE(s, n, p) – the opposite of MID, returns string s with the n characters starting at position p removed
  • REPLACE(s1, s2, n, p) – combines DELETE and INSERT – removes n characters from s1 starting at position p, and replaces them with s2
  • FIND(s1, s2) – returns the position of string s2 in string s1, or 0 if not found, and is case sensitive

You can, of course, create your own string functions. For instance, it might be useful to have a different replace function that takes a 3 strings: a string to search, a string to find, a string to replace all instances of the found string with:

08 ImprovedReplace Function

Notice how the variables are declared as T_MaxString instead of STRING

STRING Limitations

Since variables are statically allocated in TwinCAT 3, when you define a STRING variable you have to declare the length. Implicitly this is 80 characters, and it uses up 81 bytes of memory (80 for the data and one byte for a null terminator). Strings are limited to a length of 255 characters. There is a specific type called T_MaxString which is an alias for STRING(255).

Be careful because TwinCAT 3 will silently truncate a string to the maximum defined length of the destination string when you do an assignment.

When you make your own string functions, you should use T_MaxString as the variable type to make sure they work with any string passed to them. If you don’t, the input and output variables will be silently truncated to the length you specify.

Conclusions

Structured Text is a powerful tool. In some PLCs, like the Allen-Bradley ControlLogix line, you have to pay extra for the Structured Text editor, but with TwinCAT 3 you get it for free. (Actually, you get the Ladder Diagram editor for free too…)

With great power comes great responsibility. Use your new powers wisely and sparingly. When programming PLCs, the first priority is correctness and the second priority is readability. Nobody gets points for writing fewer lines of code. Remember that.




This chapter is part of the TwinCAT 3 Tutorial. Continue to the next chapter: Building an HMI in .NET.