As all good stories do, this story begins with a comment taken wildly out of proportion!
I had some spare time on my hands and one of my colleagues (I have no idea which one) mentioned Ladder Logic. I hadn’t heard of ladder logic but I was told it was really difficult, a little pointless and probably not worth pursuing.
Therefore, here is a blog on how I managed to code up a set of traffic lights using OpenPLC with ladder logic! I aimed to spend maybe a day on this. Over 4 days later, I had a working(ish) set at a cost of:
- My sanity;
- Any favours anyone ever owed me;
- A lot of sleep
- and about 15 quid!
First off, lets take a look at what a PLC is! The internet defines it as a “Programmable logic controller” but to me a better description is you know when you are tapping away in an Excel spreadsheet, your machine bluescreens and you think “how the fuck does this not cause a nuclear powerplant to meltdown?” Well that is a PLC, not the bluescreen element but the kit that looks after Industrial Control Systems (ICS) which can include nuclear powerplants, water treatment works and all levels of automation in factories.
The key with this technology is not that it’s really old, it’s that it works and it works 100% of the time!
OpenPLC is a PLC program that has two distinct parts. There is a editor, where you can create programs using ladder logic. Then there is a runtime which uses kit such as an ardunio or raspberry pi to run the code and uses the GPIO pins as the switches. So you can build a working circuit and see the logic happening in front of your eyes.
The software is easy to install and has great guides on it’s website so this blog is going to skip over the installation steps. For this project I used a Windows 10 VM for the editor and a raspberry pi for the runtime server.
So as of around 2 weeks ago, I’d never heard of ladder logic, and to be honest, I kinda of wish I still hadn’t! The idea behind ladder logic, is you have 2 rails and rungs where stuff happens in the middle. The electrical circuit runs down the rails doing the tasks in order, however it runs through the rail so quickly (around 300ms per revolution) that you have to think of everything happening in parallel.
This adds in challenges, as if your first rung is to turn on a light and the second rung is to turn off the light. The logic doesn’t wait for the first rung to finish before moving on, it carrys on down the rails. So it would be a never ending flickering of the light and it super quickly goes from off to on.
For our ladder logic project, we need to introduce some key terms:
- Contacts – These are like switches, but they are controlled by coils. If a coil is on the switch is closed. If the coil is off, the switch is open.
- However, there are 2 types of contacts:
- Regular – As described above, a coil is on the contact is made, the circuit runs
- Negated – The opposite. A coil is on, the contact is open and the circuit doesn’t run
- However, there are 2 types of contacts:
- Coils – These were originally coils of wire around a magnet to create an electromagnetic field. These then control the contacts. If the coil is “on” the contact is “on”.
- Timers – These are simple. In our project we only use the “TON” turn on timer. These are counters that keep the output false until a certain amount of time has passed. So a delay timer effectively.
The symbols for each of these are:
The logic behind ladder logic, is what needs to be happening or not happening for your thing to happen.
So can something only happen, is nothing else is happening? Or does something else need to happen then your thing can happen. Or with timers, after 10 seconds of a thing happening my thing can happen.
Makes sense? Not quite. Ok, let’s look at the OpenPLC ladder logic “Hello World” example.
This is nice and simple. There is a negated contact going into a time off timer which then effects the lamp coil.
The button being negated that means it is always “on” unless pressed. So the circuit will always be complete unless the button is pushed.
As long as the button isn’t pushed the circuit will be complete and the coil “lamp” will be on.
If the button does get pressed. The lamp coil will then stay on for 2000ms as the timer does it’s job. After that 2000ms if the button is still pressed, the lamp will go off.
The OpenPLC editor has a nice debug option by clicking the “running man” then “sunglasses”
This provides a view where you can change the state of contacts. When a line, contact or coil are green it means it’s powered. So when we start the button isn’t pressed resulting in full green and the counter at 0.
When the button is pressed, the input to the timer is black, meaning no power and the timer starts counting.
Then after the 2000ms (or 2 seconds to me and you) are over. The output from the timer becomes false, resulting in the lamp coil being turned off.
Hopefully at this stage this all makes sense.
If we wanted to export this from the editor and into the runtime server to use physical buttons and LEDs, we would need to input the GPIO which controls each function.
The pin outs are available on the OpenPLC website.
%IX are inputs (such as buttons)
%QX are outputs (such as LEDs)
These are specified in the top part of the OpenPLC editor. In this example we have the button as %IX0.1 and the LED as %QX0.0
Now that we have the basics of OpenPLC and Ladder Logic. Let’s build a single traffic light!
Building a traffic light!
Let’s think about the logic behind a single traffic light, ignoring any pedestrians.
- Red light is on for 10 seconds
- After 10 seconds the amber comes on and both red and amber stay on for 3 seconds
- After 3 seconds both red and amber go off and green comes on
- After 20 seconds the green goes off and the amber comes on
- After 3 seconds the amber goes off and loop back to the red being on for 10 seconds
Simple right, a couple of timers and a coil for each light.
We also want to add in a start and stop button, just so the controller can shut it down for maintenance and start it up again.
So let’s look at the code in OpenPLC editor:
In here, we have 14 different variables! All doing an important job, if you’d like to code for this is here on my github:
The variables in use are:
So let’s step through each part and try and understand the logic for each step.
The run function works with the following logic:
- When the Start_button is pressed and the stop_button is not pressed then run.
- OR if the run coil is powered and the stop_button is not pressed then run.
The usage here, is an operator will press the start_button turning the run coil on, this then remained on due to the run contact being closed until the stop_button is pressed.
We want the red light to be on for 10 seconds.
- If the Run coil is on, the Green_LED coil and Amber_on coil are off, then the red light is on.
This means, as long as nothing else is happening, the red light will be on. As soon as either Amber on or Green come on, the red LED will turn off.
The red light broke my brain for about a day. I originally had it based on if the start button was pressed, but then it didn’t loop, I then had a multiple of timers to try and get it working one per cycle. The negation of the other lights took me such a long time to understand the theory behind it!
Red and Amber Lights
We want amber to join red after 10 seconds and last for 3 seconds. For this we have to introduce timers, the Amber light is controlled by two different coils, as we want it on during this stage and later on it’s own stage.
The logic here is:
- If Run and Amber_On coils are on, then Amber_LED is on; or
- If Run and RA_On coils are on, then Amber_LED is on
Looking at the RA_On logic we have:
This looks much more confusing but it’s not, it’s ok! We can step through this.
Starting with the second rung we have:
- If run and red_LED are on. The counter will start counting.
- Once the counter has got to 10 seconds. The RA_on coil will be on.
Remember that the RA_on coil completes the circuit for the Amber_LED. So this will light the Amber Light.
Then going up to the top rung we have:
- If run and RA_on are on and Green_LED is off. Keep RA_on turned on
- If Run and RA_on are on. The counter will start counting.
One the counter get’s to 3 seconds, the Green_LED coil will be on. This then breaks the circuit to RA_on via the Green_LED contact (as it’s negated) and the Amber_LED will turn off.
We have already seen this one above. The logic for it is:
- If RA_On is turned on, count to 3 seconds then turn on Green_LED
Amber Light (without red)
The amber light needs to come on 20 seconds after the green light. However this also holds the key in looping the code.
The logic for turning the Amber light on is:
Running through this, to turn the Amber_on coil on we first need to follow the second rung:
- If run and Green_LED are on. Then start counting 20 seconds. After 20 seconds turn on Amber_On.
Remembering from earlier that Amber_On turns on the Amber_LED
The top rail of the Amber_On coil logic states that:
- If Run is on and Amber_off and red_LED are off and Amber_On is on, then the coil Amber_on will remain on.
Effectively, if amber_off or red_led coils turn on, then amber_on coil turns off.
Also it can’t turn on via this rail, as it already needs to be on for the whole rail to be active. If you didn’t have this part of the logic, then the coil would be on whenever amber_off or Red_LED were off, resulting in amber being on for the entire duration of the green light phase.
The last step to this is how we then loop back round to red. Remember how Red only comes on if no other lights are on, this means we can’t turn this coil off by going to the next light like we have previously, we need to specifically turn the Amber_on coil off.
This is quite straightforward, once Amber_On is active, we want the light to stay on for 3 seconds, then turn off (and by turning it off, turns on red as nothing else is active)
Even though this seems simple now, when starting out this was bonkers complicated mostly due to the logic of red only running when nothing else was.
The ladder for this is:
The logic here shows that:
- If Run and Amber_On coils are on. Then the timer starts counting up 3 seconds. After 3 seconds the Amber_Off coil is on.
This coil then stops the Amber_Light, resulting in the loop starting back to the Red_LED and it carries on until the Stop_Button is pushed.
It was required to debug a lot throughout the process of this. Using the running man and sunglasses we can see the cycle running. If it gets stuck anywhere try and explain the logic in words and see which coil is on or off incorrectly for the next step!
Remember the green lines, contacts and coils show what is currently powered. So we can see that Green_LED has power, via the Green_LED contact. we can also see that as the Green_LED is on, there is power to the timer and the time next to it shows 15 seconds have passed. Once that reaches the target of 20 seconds, the Amber_On coil will turn on, which will turn off the Green_LED by the negated contact of Amber_On.
Moving this to OpenPLC RunTime
Wiring up a breadboard and Raspberry Pi
I was so excited when I first got this going! There is a short video of it all working on my twitter.
Now that we have 1 working. It’s rare that a junction only has 1 light. Therefore we need to expand our code out for a 4-way junction.