It is possible to program controllers (PLCs) in the CODESYS environment. All who dealt with this system know that in any project there is a Standard.lib library, which implements basic timers, triggers, counters and some number of other functions and function blocks. Many of these units are constantly used in programs for PLCs. And the library itself, like CODESYS programming languages, is the embodiment of the IEC 61131-3 standard, i.e. Is intended to help with the programming of classical PLC tasks.
One of the peculiarities of the PLC programs is that the main program cycle must be executed without significant delays, it should not have internal loops with an indefinite timeout or synchronous calls to “pensive” functions, especially for slow channel communications. Updating the input and output images of the process occurs only at the boundary of the main loop, and the longer we “sit” inside one iteration of the loop, the less we will know about the actual state of the control object, eventually the watchdog will overflow the cycle time. Many people can object to me, saying that modern PLCs are many-valued, there is support for hardware interrupts. I agree, but talking about such systems is not part of my plans, I want to talk about (quasi, pseudo-select) PLC of a single-task implementation (without interruptions) based on Arduino microprocessor platform, which has only one main loop. By the way, it’s not superfluous to say that I was inspired by the article Arduino-compatible PLC CONTROLLINO, Part 1 about the attempt of the hardware implementation of Arduino in the industry. PLC.
In a single-task system, it is possible to achieve visible parallelism only by very fast sequential scanning of parallel states, without delaying for long on each function call or condition check. With physical inputs and outputs there are no problems, the functions work out quickly enough, but delay () becomes an unjustified brake. And here non-blocking timers come to replace those, the ones that are classics in PLC programming. The bottom line is that they use a millisecond time counter for their work, and all actions are tied to the values of this global counter.
Now let’s remember the same Standard.lib from CODESYS. It just implemented IEC-ovskie non-blocking timers. I took it as a basis and ported the functions of timers and triggers to the library code Arduino (C ++). Those. I tried to bring Arduino closer to the classic PLC.
Below I will give a brief description of the ported CODESYS function blocks (FB) and their counterparts in my library plcStandardLib all timelines are correct for the new Arduino library.
TON – the function block “timer with a delayed start-up”
TON (IN, PT, Q, ET)
Inputs IN and PT types BOOL and TIME respectively. Outputs Q and ET are similar to BOOL and TIME types. While IN is FALSE, the output is Q = FALSE, the output is ET = 0. As soon as IN is TRUE, the time (in milliseconds) on the ET output starts to a value equal to PT. Further the counter does not increase. Q is TRUE when IN is TRUE and ET is PT, otherwise FALSE. So
Way, the output Q is set with a delay PT from the front of the IN input.
In the Arduino IDE:
Variants of declarations:
TON TON1 ();
TON TON1 (unsigned long PT); // with the specification of the time interval PT
Usage options:
Q = TON1.Run (boolean IN); // all-in-one call
TON1.IN = IN;
TON1.Run ();
Q = TON1.Q;
Time diagram of the work of the TON:
TOF – function block “timer with delayed shutdown”
TOF (IN, PT, Q, ET)
Inputs IN and PT types BOOL and TIME respectively. Outputs Q and ET are similar to BOOL and TIME types. If IN is TRUE, then the output is Q = TRUE and the output is ET = 0. As soon as IN is switched to FALSE, the time (in milliseconds) starts at the ET output. When the specified duration is reached, the countdown is stopped. The output Q is FALSE if IN is FALSE and ET is PT, otherwise TRUE. Thus, the output Q is reset with a delay PT from the drop in the IN input.
In the Arduino IDE:
Very similar to TON, for brevity:
TOF TOF1 (unsigned long PT); // with the PT
Q = TOF1.Run (boolean IN); // call "all in one"
The time diagram of the work of TOF:
TP – function block “pulse-timer”
TP (IN, PT, Q, ET)
Inputs IN and PT types BOOL and TIME respectively. Outputs Q and ET are similar to BOOL and TIME types. While IN is FALSE, the output is Q = FALSE, ET = 0. When IN is turned to TRUE, the output Q is set to TRUE and the timer starts counting the time (in milliseconds) at the ET output until the length specified by PT is reached. Further the counter does not increase. Thus, the output Q generates a pulse of duration PT on the front of the IN input.
In the Arduino IDE:
Very similar to TON, for brevity:
TP TP1 (unsigned long PT); // with the PT
Q = TP1.Run (boolean IN); // call "all in one"
The time diagram of the operation of the TP:
R_TRIG – function block “detector front”
The function block R_TRIG generates a pulse on the rising edge of the input signal. The output Q is FALSE until the input CLK is FALSE. As soon as the CLK gets TRUE, Q is set to TRUE. The next time the function block is called, the output is reset to FALSE. Thus, the block issues a single pulse at each CLK transition from FALSE to TRUE.
CODEDESYS example in ST language:
RTRIGInst: R_TRIG;
RTRIGInst (CLK: = VarBOOL1);
VarboOL2: = RTRIGInst.Q;
In the Arduino IDE:
Announcement:
R_TRIG R_TRIG1;
Usage options:
Q = R_TRIG1.Run (boolean CLK); // all-in-one call
R_TRIG1.CLK = CLK;
R_TRIG1.Run ();
Q = R_TRIG1.Q;
F_TRIG – function block “detector of decay”
The function block F_TRIG generates a pulse at the falling edge of the input signal.
The output of Q is FALSE as long as the CLK input is TRUE. As soon as CLK gets FALSE, Q is set to TRUE. The next time the function block is called, the output is reset to FALSE. Thus, the block issues a single pulse at each CLK transition from TRUE to FALSE.
In the Arduino IDE:
F_TRIG F_TRIG1;
Q = F_TRIG1.Run (boolean CLK); // call all-in-one
RS_TRIG – function block RS flip-flop / SR_TRIG – function block SR flip-flop
Switch with dominant off, RS-trigger:
Q1 = RS (SET, RESET1)
Switch with dominant inclusion:
Q1 = SR (SET1, RESET)
The input variables SET and RESET1 are the same as the output variable Q1 of the BOOL type.
In Arduino IDE:
RS_TRIG RS_TRIG1;
Q = RS_TRIG1.Run (boolean SET, boolean RESET); // all-in-one call
SR_TRIG SR_TRIG1;
Q = SR_TRIG1.Run (boolean SET, boolean RESET); // all-in-one call
Source code and example
] / *
* PlcStandardLib_1.h
*
* Created on: 01/01/2017
* Author: Admin
* /
#ifndef PLCSTANDARDLIB_1_H_
#define PLCSTANDARDLIB_1_H_
#if ARDUINO> = 100
#include
#else
#include
#endif
/ * ------------------- TON ------------------- * /
Class TON
{
Public:
TON ();
TON (unsigned long PT);
Boolean Run (boolean IN);
Boolean Q; // output variable
Boolean IN; // input variable
Unsigned long PT; // input variable
Unsigned long ET; // output variable - current timer value
Private:
Boolean _M; // internal flag
Unsigned long _StartTime;
};};
/ * ------------------- TOF ------------------- * /
Class TOF
{
Public:
TOF ();
TOF (unsigned long PT);
Boolean Run (boolean IN);
Boolean Q; // output variable
Boolean IN; // input variable
Unsigned long PT; // input variable
Unsigned long ET; // output variable - current timer value
Private:
Boolean _M; // internal flag
Unsigned long _StartTime;
};};
/ * ------------------- TP ------------------- * /
Class TP
{
Public:
TP ();
TP (unsigned long PT);
Boolean Run (boolean IN);
Boolean Q; // output variable
Boolean IN; // input variable
Unsigned long PT; // input variable
Unsigned long ET; // output variable - current timer value
Private:
Boolean _M; // internal flag
Unsigned long _StartTime;
};};
/ * ------------------- R_TRIG ------------------- * /
Class R_TRIG // signal edge detector
{
Public:
R_TRIG ();
Boolean Run (boolean CLK);
Boolean CLK; // input variable
Boolean Q; // output variable
Private:
Boolean _M; // internal flag
};};
/ * ------------------- F_TRIG ------------------- * /
Class F_TRIG // Signal Decay Detector
{
Public:
F_TRIG ();
Boolean Run (boolean CLK);
Boolean CLK; // input variable
Boolean Q; // output variable
Private:
Boolean _M; // internal flag
};};
/ * ------------------- RS_TRIG ------------------- * /
Class RS_TRIG // Signal Decay Detector
{
Public:
RS_TRIG ();
Boolean Run ();
Boolean Run (boolean SET, boolean RESET);
Boolean SET; // set the trigger
Boolean RESET; // reset the trigger
Boolean Q; // output variable
// private:
};};
/ * ------------------- SR_TRIG ------------------- * /
Class SR_TRIG // Signal Decay Detector
{
Public
SR_TRIG ();
Boolean Run ();
Boolean Run (boolean SET, boolean RESET);
Boolean SET; // set the trigger
Boolean RESET; // reset the trigger
Boolean Q; // output variable
// private:
};};
#endif / * PLCSTANDARDLIB_H_ * /
/ *
* PlcStandardLib_1.h
*
* Created on: 01/01/2017
* Author: Admin
* /
#include "plcStandardLib_1.h"
/ * ------------------- TON ------------------- * /
TON :: TON ()
{
IN = false;
PT = 0;
_M = false;
_StartTime = 0;
Q = false;
ET = 0;
}
TON :: TON (unsigned long PT)
{
IN = false;
TON :: PT = PT;
_M = false;
_StartTime = 0;
Q = false;
ET = 0;
}
Boolean TON :: Run (boolean IN)
{
TON :: IN = IN;
If (! TON :: IN) {
Q = false;
ET = 0;
_M = false;
} Else {
If (! _M) {
_M = true; // we cock the flag M
_StartTime = millis ();
// ET = 0; // immediately = 0
} Else {
If (! Q)
ET = millis () - _StartTime; // calculate the time
}
If (ET> = PT)
Q = true;
}
Return Q;
}
/ * ------------------- TOF ------------------- * /
TOF :: TOF ()
{
IN = false;
PT = 0;
_M = false;
_StartTime = 0;
Q = false;
ET = 0;
}
TOF :: TOF (unsigned long PT)
{
IN = false;
TOF :: PT = PT;
_M = false;
_StartTime = 0;
Q = false;
ET = 0;
}
Boolean TOF :: Run (boolean IN)
{
TOF :: IN = IN;
If (TOF :: IN) {
Q = true;
ET = 0;
_M = true;
} Else {
If (_M) {
_M = false; // reset the M flag
_StartTime = millis ();
// ET = 0; // immediately = 0
} Else {
If (Q)
ET = millis () - _StartTime; // calculate the time
}
If (ET> = PT)
Q = false;
}
Return Q;
}
/ * ------------------- TP ------------------- * /
TP :: TP ()
{
IN = false;
PT = 0;
_M = false;
_StartTime = 0;
Q = false;
ET = 0;
}
TP :: TP (unsigned long PT)
{
IN = false;
TP :: PT = PT;
_M = false;
_StartTime = 0;
Q = false;
ET = 0;
}
Boolean TP :: Run (boolean IN)
{
TP :: IN = IN;
If (! _M) {
If (TP :: IN) {
_M = true; // we cock the flag M
_StartTime = millis ();
If (ET < PT)
Q = true;
}
} else {
if (Q) {
ET = millis() - _StartTime; // вычисляем время
if (ET > = PT)
Q = false;
} Else {
If (! TP :: IN) {
_M = false;
ET = 0;
}
}
}
Return Q;
}
/ * ------------------- R_TRIG ------------------- * /
R_TRIG :: R_TRIG ()
{
CLK = false;
_M = false;
Q = false;
}
Boolean R_TRIG :: Run (boolean CLK)
{
R_TRIG :: CLK = CLK;
Q = R_TRIG :: CLK &&! _M;
_M = R_TRIG :: CLK;
Return Q;
}
F_TRIG :: F_TRIG ()
{
CLK = false;
_M = true;
Q = false;
}
Boolean F_TRIG :: Run (boolean CLK)
{
F_TRIG :: CLK = CLK;
Q =! F_TRIG :: CLK &&! _M;
_M =! F_TRIG :: CLK;
Return Q;
}
/ * ------------------- RS_TRIG ------------------- * /
RS_TRIG :: RS_TRIG ()
{
SET = false;
RESET = false;
Q = false;
}
Boolean RS_TRIG :: Run (boolean SET, boolean RESET)
{
RS_TRIG :: SET = SET;
RS_TRIG :: RESET = RESET;
Q =! RESET and (SET or Q);
Return Q;
}
Boolean RS_TRIG :: Run ()
{
Q =! RESET and (SET or Q);
Return Q;
}
/ * ------------------- SR_TRIG ------------------- * /
SR_TRIG :: SR_TRIG ()
{
SET = false;
RESET = false;
Q = false;
}
Boolean SR_TRIG :: Run (boolean SET, boolean RESET)
{
SR_TRIG :: SET = SET;
SR_TRIG :: RESET = RESET;
Q = SET or (! RESET and Q);
Return Q;
}
Boolean SR_TRIG :: Run ()
{
Q = SET or (! RESET and Q);
Return Q;
}
#include "plcStandardLib_1.h"
#define LED 13
#define ButtonIn 7
TON TON1 (500); // Initialize the on delay, 500ms.
TON TON2 (1000); // Initialize the on delay, 1000ms.
TOF TOF1 (500); // Initialize the off delay, 500ms.
TP TP1 (300); // Initialize a single pulse, 300ms.
TP TP2 (200); // Initialize a single pulse, 200ms.
R_TRIG R_TRIG1; // Initialize the front flip-flop for the button
Void setup () {
PinMode (ButtonIn, INPUT_PULLUP);
PinMode (LED, OUTPUT);
}
Void loop () {
DigitalWrite (LED, TP1.Run (R_TRIG1.Run (TON1.Run (digitalRead (ButtonIn)))));
// TON1 - filters the chatter of the contact
// R_TRIG1 - Detects the edge of the signal
// TP1 - generates a pulse on the front
DigitalWrite (LED, TP2.Run (TON2.Run (! TON2.Q)))); // pulse generator based on TON and TP
// TON2.Run (! TON2.Q)) - the generator of a single pulse
// TP2 - generates a pulse on the front
DigitalWrite (LED, TOF1.Run (TON1.Run (digitalRead (ButtonIn)))); // Delay on and off
}
For example, to filter the bounce of the button’s contacts (when it opens, too!), Here’s the code:
FiltredButtonIn = TON1.Run (digitalRead (ButtonIn))
As a conclusion: this is how CODESYS looks at the work of the pulse generator based on the chain of timers TON and TP. At the beginning, the TON is covered by feedback with inversion, and a single pulse generator is obtained from it, which starts the work of the TP pulse generator. In my example, the Arduino analog of this looks like this:
digitalWrite (LED, TP2.Run (TON2.Run (! TON2.Q)));