- Structured Text
- Project Examples
After playing around with ladder logic (LL) in OpenPLC, I wanted to get a basic grip of structured text (ST). While LL is a visual method of programming PLCs, ST is a C-like language for programming PLCs, featuring well-know coding functions such as IF and WHILE.
Fortunately, OpenPLC also allows you to program PLCs using ST! There is one minor difference; with Ladder Logic, OpenPLC can simulate the circuit and get a pretty visualisation based on the LL design itself - see the images in my previous post. With ST, because there is no design, there is no pretty visualisation. However, the Debugger does give a timeline of the states of the various variables, and it can be used to force a value, so you can still simulate a circuit.
While studying ST, I noticed many of the tutorials used a piece of software called CODESYS for their programming. This is much more feature-rich than OpenPLC, and can do visualisations - it can do full HMI, with buttons and lights etc. So you can guess what a future post will be about ;-)
You’ll see the code below, but a few things about the language first:
- A mandatory semi-colon
;ends each statement
IFis closed with
THEN, and brackets are not used
:=is used for variable assignment
Let’s recreate the basic designs I created with LL before in OpenPLC and get them running on the Arduino.
Two-Button Latching Circuit
The variables are the same as before:
The code is:
IF PB1 THEN LED := TRUE; END_IF; IF PB2 THEN LED := FALSE; END_IF;
Even if you’ve never done any coding before, I’m sure this will make sense. If
PB1 is pressed, set
TRUE (on). If
PB2 is pressed, turn it off.
PB1 doesn’t have to be physically held on to keep
LED on; once it is pressed,
LED is set to
TRUE, and this stays until it is changed. With LL we needed a contact linked to the LED in parallel with the push button to latch it; in ST, it’s effectively self-latching.
To simulate this in OpenPLC, click the Simulate button and let it compile and start etc:
Next, click Debug on the left panel:
A new tab will appear, and the person will become a STOP sign:
On right panel, it will change to the Debugger tab, but currently it’s empty:
To Debug (view) the individual variables, click the glasses icon for each in the left panel:
They will appear in the right panel:
If you hover over then double-click, we get visuals!
Repeat for all, and then if you hover over the visual, you can change the size. I like the middle-size one:
Now, if you hover over the value (in this case, False), you get a new menu:
If you click the padlock, you can set the value in a new popup:
Click Toggle value then OK, and the chart will change:
And note how this has turned the LED on!
You can force all the values all the values this way to see the behaviour. Note you’ll have to Toggle the
PB1 both on and off (
FALSE) to simulate pressing and releasing the button.
If the speed is too fast, you can change the Duration setting in the Debugger. I found 30s good.
Here’s the full “routine” -
Interestingly, if you hold
TRUE (i.e. keep the button pressed),
FALSE even if you press
PB1. This is because, in the ST, the code for
PB2 comes after the code for
Changing the code around:
IF PB2 = TRUE THEN LED := FALSE; END_IF; IF PB1 = TRUE THEN LED := TRUE; END_IF;
Creates the opposite effect:
Also, if you decrease the Duration enough, you can see the ramp for the change:
Uploading to the Arduino works in the same way as with LL and creates the same result as the Debugger. Unsurprising, really.
One-Button Latching Circuit with Emergency Stop
Next one! The initial thought is this:
IF PB1 AND NOT PB2 THEN LED := NOT LED; END_IF; IF PB2 THEN LED := FALSE; END_IF;
The second line, quite simply, toggles the value of
LED to the opposite of what it was.
PB2 (emergency stop) is pressed, this will not happen. And whatever value
LED is, if
PB2 is pressed, the
LED goes off.
However, this creates a strange effect:
Let’s zoom in by changing the Duration:
On the actual Arduino, this is the LED flashing continuously.
Why is this? Well, PLC code loops continously. This means, every loop, it sees
PB1 high, and toggles
LED. Not quite what we want.
The solution is using states; in particular, using a variable to log the previous state of
PB1, and only do something if the previous state has changed. So, we add a new variable,
PB1_PREV (note this does not relate to anything physical; it is purely a variable):
And the code looks like this:
IF PB1 AND NOT PB1_PREV AND NOT PB2 THEN LED := NOT LED; END_IF; PB1_PREV := PB1; IF PB2 THEN LED := FALSE; END_IF;
This is the same as before, but each loop,
PB1_PREV is set to be the same as
PB1, and the
IF only functions if
PB1 is not equal to
Here is the full functionality, both turned on and off by
PB1, and an emergency stop caused by
PB2 even though
PB1 was still
TRUE (and, if
PB1 has no effect):
Playing with Timers
my_ton_in := PB1; my_ton( IN := my_ton_in, PT := my_pt, Q => my_ton_q); my_tof( IN := my_ton_q, PT := my_pt, Q => my_tof_q); LED := my_tof_q;
TON (timer on)
my_ton_in is set to be the same as
PT is for the time variable,
my_pt, set to
T#2000ms (I’ve used the same variable, two seconds, for each timer, but of course different variables could be used). The output of
my_ton is assigned to
my_ton_q, which is used as the input for the
TOF (timer off)
my_tof. The output of this,
my_tof_q, is then assigned to
The functionality. Note the delay between
Steady(ish) State (e.g Temperature)
The variables are the same as LL (I’ve explained the strange numbers here), except the temperatures are also included up here, and I’ve used initial values to make the simulation look better:
The code is actually simpler than using LL, as each comparison can change multiple variables. It’s easy to read what it does:
IF sensor >= temp_max THEN cooler_on := TRUE; heater_on := FALSE; ideal_temp := FALSE; END_IF; IF sensor <= temp_min THEN heater_on := TRUE; cooler_on := FALSE; ideal_temp := FALSE; END_IF; IF sensor <= temp_max AND sensor >= temp_min THEN ideal_temp := TRUE; END_IF;
As a reminder, here is the same functionality in LL:
Note how, in this case,
heater_on are de-energized using a NC contactor linked to
ideal_temp, which would be similar to
IF (sensor >= temp_max) AND NOT ideal_temp THEN. However, not only does this not seem to work as well in ST, it also isn’t really what we want if we think logically about the functionality. A better way, which we did in ST above, is to directly define the values of
heater_on depending on the comparison.
As for the Debug chart:
With ST, it’s very easy to take this a step further. We can keep the heater or cooler on until it hits the perfect temperature, and then stop. So, instead of potentially yo-yoing between the
temp_min, it will only increase or decrease to the perfect temperature. This involves setting a new variable,
perfect_temp, and let’s say set to 42598 (the mid-point). The new code looks like:
IF sensor >= temp_max THEN cooler_on := TRUE; heater_on := FALSE; ideal_temp := FALSE; END_IF; IF sensor <= temp_min THEN heater_on := TRUE; cooler_on := FALSE; ideal_temp := FALSE; END_IF; IF sensor <= temp_max AND sensor >= temp_min THEN ideal_temp := TRUE; IF sensor < perfect_temp THEN heater_on := TRUE; cooler_on := FALSE; END_IF; IF sensor > perfect_temp THEN cooler_on := TRUE; heater_on := FALSE; END_IF; IF sensor = perfect_temp THEN cooler_on := FALSE; heater_on := FALSE; END_IF; END_IF;
And the output:
Feel free to comment on my LinkedIn post.