Since recently I started to develop interest in PLCs. Reason: I found an open source project called OpenPLC which is capable of running a PLC on the Raspberry Pi.
So, seen those are my first steps in the magical world of PLC I still have lots to learn.
Below is a mixed bag of terminology, abbreviations used and so on.
Mostly, PLC diagrams are drawn using the ladder method because it reflects the real world in the most close way, but there are 5 methods that can be used in the PLC world to draw PLC schemes:
Ladder diagram (LD): most popular
Instruction List (IL)
Structured Text (ST): Pascal-alike programming language
Function Block Diagram (FBD): used by Siemens Logo PLCs
Sequential Function Charts (SFC)
When using OpenPLC there are quite a few of function blocks available. They are divided in categories: from standard function blocks to mathematical blocks, all is present.
In many of the functional blocks in OpenPLC - and in PLC in general - the following shortcuts are used (list is not limited to what is written below, will be updated if needed):
TP: Timer Pulse
PT: Preset Time / Programmed time (some articles use one or another)
ET: Elapsed Time
TON: Timer ON - with possibility to delay the ON time
TOF: Timer Off - with possibility to delay the OFF time
RLO: Logic Operation Result (mainly used by Siemens S7 PLC)
POU: Program Organisation Unit
An example where this is used can be found below. Note that this is a PLC program written with functional blocks, this is not a ladder program.
Inputs are represented by %I
Outputs are represented by %Q
Memory variables are represented by %M
Depending on the data type used, the following can follow the items above:
X for bit (1 bit)
B for byte (8 bits)
W for word (16 bits)
D for double word (32 bits)
L for long word (64 bits)
So, to represent an input which is of type bit, the following will be used: %IX0.4, meaning byte 0, bit 4 (starting from 0). For a bit on the output side, the following syntax will be used: %QX3.7, meaning byte 3, bit 7.
We can only have a dot notation for bits, never for all the other datatypes. So, %QB4.2 is wrong, must be %QB4 instead since it's a byte, not a bit.
What happens here? Well, we have a combination of an OR-gate with 4 inputs, a trigger block R_TRIG0 that acts on a rising edge at the input IN, a Timer Pulse module TP0 followed by a Timer ON delayed (TON0) and Timer OFF delayed (TOFF0) functional block.
Whenever I01 or I02 goes high, the output of the OR gate will go high too. The trigger block TP0 will detect a rising edge and will trigger the Timer Pulst module. The time TP0 will keep its output Q high is 10 seconds.
The output of TP0 is connected to TON0. As soon as the IN input of TON0 detects a signal, the timer connected to the PT input of TON0 will kick in. Seen the time set, it means output Q of TON0 will be delayed with 2 seconds before it's put to ON.
So, only after TP0 has been high for 2 seconds, the output Q of TON0 will go high. That means, from the 10s time pulse of TP0 only 8 will be effective (we "lose" 2 seconds because of the on-delay of 2s on TON0).
The output Q of TON0 is connected to the input IN of TOF0. This will immediately trigger the output Q of TOF0 and as such, Q01 will be put high too.
Whenever the input IN of TOF0 drops the delayed switch-off timer connected to the PT input of TOF0 will kick in. Since TOF0 is a delayed switch-off block it will keep the output Q high for another 10s (the preset time given to the PT input of TOF0). Only then the Q output of TOF0 will be put low again.
Overall, the Q01 output will be kept high for 18 seconds despite the fact that the pulse given to the PT input of TP0 was set to 10s.
This is, of course, a theoritical example but it's good to know who all modules are coupled to each other, what their dynamics are and how the whole chain is reacting on a positive edge input pulse from I01 or I02.
As said before, it's possible to use the Raspberry Pi as a PLC device. There has to be an OpenPLC runtime installed on the Rasbperry Pi. How to do this is explained on this page.
Short summary on what to do:
git must be installed. If not: sudo apt install git
Since the OpenPLC runtime needs wiringPi make sure wiringPi is installed upfront. To check if the installation is fine, run gpio -v.
If that command is successful, no further actions are needed. If not, then the following has to be excuted:
git clone https://github.com/WiringPi/WiringPi.git
cd WiringPi
./build
gpio -v: check if all went fine
Note: on a Le Potato SBC that command won't work since they're not identified as a Raspberry Pi board and also the pin mapping is different. You can't use wiringPi at all on such board.
Clone the OpenPLC repository: git clone https://github.com/thiagoralves/OpenPLC_v3.git
Go to OpenPLC_v3
Run the command ./install.sh rpi (this tells the installation program the OpenPLC runtime will be installed on a Raspberry Pi)
Installation can take a while, be patient.
The creator of OpenPLC has split the GPIO pins of the Raspberry Pi into two halves when using the Raspberry Pi as OpenPLC hardware layer:
The uneven pins (so, the left side of the RPi 40-pins header) are all input pins
The even pins (so, the right side of the RPi 40-pins header) are all output pins.
See the image at the end of this section where you also can see the way IEC 61131-3 handles byte level addressing (image taken from this website).
As a consequence of this choice we have more input pins than we have output pins.
Input pins go from %IX0.0 to %IX1.5, making a total of 14 input pins (8 + 6).
Output pins go from %QX0.0 to %QX1.2, making a total of 11 output pins (8 + 3)
Note that also the I2c pins (header pins 3 and 5) are claimed by the OpenPLC runtime. That means you can't do I2c anymore if the runtime kicks in, unless you adapt the OpenPLC files and remove the pins 3 and 5 from the array of defined input pins.
This can be done by opening the file custom_layer.h located in ./OpenPLC_v3/webserver/core. There's an array called ignored_bool_inputs[] which is currently initialised to {-1} but should contain the numbers 8 and 9 like so: { 8, 9 }. See next paragraph as to why.
Order doesn't really matter: in the file raspberry.cpp which can be found in the OpenPLC_v3 source tree ./OpenPLC_v3/webserver/core/hardware_layers there's a method initializeHardware() which is called from the PLC framework if the Raspberry Pi has been chosen as PLC platform.
There's a first for loop (line 75 as of this writing) that iterates over the pins that are defined as input like so:
void initializeHardware()
{
wiringPiSetup();
//piHiPri(99);
//set pins as input
for (int i = 0; i < MAX_INPUT; i++)
{
if (pinNotPresent(ignored_bool_inputs, ARRAY_SIZE(ignored_bool_inputs), ii))
{
pinMode(inBufferPinMask[i], INPUT);
if (i != 0 && i != 1) //pull down can't be enabled on the first two pins
{
pullUpDnControl(inBufferPinMask[i], PUD_DOWN); //pull down enabled
}
}
}
.
.
.
To see if a pin is elegable as input, a function pinNotPresent() is called. The purpose of that function is to check if a pin is or is not present in a given array. The function is implemented in main.cpp, located in ./OpenPLC_v3/webserver/core. The pinNotPresent() function is implemented like so:
bool pinNotPresent(int *ignored_vector, int vector_size, int pinNumber)
{
for (int i = 0; i < vector_size; i++)
{
if (ignored_vector[i] == pinNumber)
return false;
}
return true;
}
As one can see, the parameter ignored_vector which is equal to ignored_bool_inputs in this example (see raspberrypi.cpp file) is checked for all items in it (parameter vector_size represents the length of the incoming array igored_bool_inputs)). If the incoming pinNumber is found in the ignored_bool_inputs array the function immediately returns false so that it's ignored by the caller. This way, we can avoid to set the pin mode for the pins added to the ignored_bool_inputs array.
Note that the numbers in the inBufferPinMask correspond to the numbers used by wiringPi and are not the physical header pins!
To know how the wiringPi numbers are associated with the physical header pins, go to the pinout.xyz website and select the wiringPi filter in the upper-right corner.
As an example, take SDA:
Physical header pin: 3
BCM pin: GPIO 2
wiringPi pin: 8
Can't be more simple and less confusing, right????? ;-) (no pun intended...)
So, in the inBufferPinMask array the number 8 will be one of the input pins and not the physical pin header number. If you follow the inBufferPinMask variabel in the code, all will become clear.
https://www.youtube.com/watch?v=ic9crSVVF9Q&ab_channel=JimPytel
https://instrumentationtools.com/plc-program-for-iec-timers-ton-tof-tp-tonr-used-in-s7-1200/
https://instrumentationtools.com/free-download-plc-simulator-using-excel/
https://funprojects.blog/2021/11/18/openplc-on-a-raspberry-pi/ (some pre-OpenPLC knowledge is needed here)
https://www.plcacademy.com/ladder-logic-tutorial/#comment-79393
https://www.solisplc.com/tutorials/how-to-read-ladder-logic