📜 ⬆️ ⬇️

CODESYS timers and triggers. Another step Arduino to the classic PLC


It happens to program controllers (PLC) in the environment CODESYS. Everyone who dealt with this system knows that Standard.lib library is present in any project, in which basic timers, triggers, counters and some other functions and functional blocks are implemented. Many of these units are constantly used in PLC programs. And the library itself, like the programming languages ​​CODESYS, is the embodiment of the IEC 61131-3 standard, i.e. It is designed to help with programming classic PLC tasks.

One of the features of PLC programs is that the main program loop should be executed without significant delays; it should not have internal loops with indefinite exit times or synchronous calls for “thoughtful” functions, especially with regard to communication over slow channels. Updating the input and output process images 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 real state of the control object, eventually the watchdog overflow of the runtime will work. Many may object to me, saying that modern PLCs are multivalued, with the support of hardware interrupts. I agree, but talking about such systems is not part of my plans, but I want to talk about (quasi, pseudo - choose) PLC single-tasking implementation (without interruptions) based on the Arduino microprocessor platform, which has only one main loop. By the way, it would not be superfluous to say that the article by the Arduino-compatible PLC CONTROLLINO, part 1 about the attempt of the hardware implementation of the Arduino in the prom. PLC.

A few words about the Arduino. From the point of view of the PLC programmer, the Arduino is a typical controller with one very fast or, conversely, a very slow loop loop (). There are no restrictions on the cycle execution time, and it can work both once and for an infinite number of times - according to the programmer’s intention. When a program is simple and comes down to performing sequential operations, regulators, without parallel events, it is enough to alternate operations with infinite nested loop test conditions and synchronous delays such as delay (). The successive steps of such a program will be performed literally line by line, simple and logical. But, as soon as the need arises for programming parallel operations, it is necessary to change the program paradigm.

In a single-task system, visible parallelism can be achieved only by very fast sequential scanning of parallel states, without lingering for a long time on each function call or condition check. There are no problems with physical inputs and outputs, functions are worked out fairly quickly, but delay () becomes an unjustified brake. And this is where non-blocking timers, the ones that are classics in PLC programming, come to replace them. The bottom line is that a millisecond time counter is used for their work, and all actions are tied to the values ​​of this global counter.
')
And now let's remember that Standard.lib from CODESYS. It just implemented the IEC non-blocking timers. I took it as a basis and ported the functions of timers and triggers into the Arduino library code (C ++). Those. tried to bring the Arduino closer to the classic PLC.

Below I will give a brief description of the ported CODESYS function blocks (FB) and their analogues in my plcStandardLib library; all timing diagrams are correct for the new Arduino library. Detailed description of the source blocks can be found, for example, in the Russian CODESYS help.

TON - Timer Delay On Functional Block


TON(IN, PT, Q, ET) 

IN and PT inputs are BOOL and TIME types, respectively. Outputs Q and ET are similar to the BOOL and TIME types. While IN is FALSE, the output is Q = FALSE, the output is ET = 0. As soon as IN becomes TRUE, the time begins (in milliseconds) at output ET 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
Thus, the output Q is set with a delay PT from the front of the input IN.

In the Arduino IDE:


Ad Variations:

 TON TON1(); TON TON1(unsigned long PT); //     PT 

Use options:

 Q = TON1.Run(boolean IN); //  "  " TON1.IN = IN; TON1.Run(); Q = TON1.Q; 

TON Timeline:


TOF - function block "timer with shutdown delay"


 TOF(IN, PT, Q, ET) 

IN and PT inputs are BOOL and TIME types, respectively. Outputs Q and ET are similar to the BOOL and TIME types. If IN is TRUE, then the output is Q = TRUE and the output is ET = 0. As soon as IN goes to FALSE, the time begins (in milliseconds) at the output ET. When the set duration is reached, the countdown stops. 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 decline of the input IN.

In the Arduino IDE:


Very similar to TON, for short:

 TOF TOF1(unsigned long PT); //     PT Q = TOF1.Run(boolean IN); //  "  " 

TOF timeline:


TP - functional unit "pulse-timer"


 TP(IN, PT, Q, ET) 

IN and PT inputs are BOOL and TIME types, respectively. Outputs Q and ET are similar to the BOOL and TIME types. While IN is FALSE, the output is Q = FALSE, the output is ET = 0. When IN goes TRUE, the output Q is set to TRUE and the timer starts counting the time (in milliseconds) at output ET until the duration 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 input IN.

In the Arduino IDE:


Very similar to TON, for short:

 TP TP1(unsigned long PT); //     PT Q = TP1.Run(boolean IN); //  "  " 

TP Timeline:


R_TRIG - front-end detector function block


The function block R_TRIG generates a pulse on the leading edge of the input signal. Output Q is FALSE as long as the CLK input is FALSE. Once CLK is TRUE, Q is set to TRUE. The next time the function block is called, the output is reset to FALSE. Thus, the block produces a single pulse at each CLK transition from FALSE to TRUE.

Example CODEDESYS in ST language:

 RTRIGInst : R_TRIG ; RTRIGInst(CLK:= VarBOOL1); VarBOOL2 := RTRIGInst.Q; 

In the Arduino IDE:


Announcement:

 R_TRIG R_TRIG1; 

Use options:

 Q = R_TRIG1.Run(boolean CLK); //  "  " R_TRIG1.CLK = CLK; R_TRIG1.Run(); Q = R_TRIG1.Q; 

F_TRIG - functional block "decay detector"


Functional block F_TRIG generates a pulse on the trailing edge of the input signal.
Q output is FALSE as long as CLK input is TRUE. As soon as the CLK is set to FALSE, Q is set to TRUE. The next time the function block is called, the output is reset to FALSE. Thus, the block generates 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); //  "  " 

RS_TRIG - functional block RS trigger / SR_TRIG - functional block SR trigger


Switch with the dominant off, RS-trigger:

 Q1 = RS (SET, RESET1) 

Switch with the inclusion of the dominant:

 Q1 = SR (SET1, RESET) 

The input variables SET and RESET1 are the same as the output variable Q1 of the BOOL type.

In the Arduino IDE:


 RS_TRIG RS_TRIG1; Q = RS_TRIG1.Run(boolean SET, boolean RESET); //  "  " 

 SR_TRIG SR_TRIG1; Q = SR_TRIG1.Run(boolean SET, boolean RESET); //  "  " 

Source Code and Example


plcStandardLib_1.h
 /* * plcStandardLib_1.h * * Created on: 01.01.2017 * Author: Admin */ #ifndef PLCSTANDARDLIB_1_H_ #define PLCSTANDARDLIB_1_H_ #if ARDUINO >= 100 #include <Arduino.h> #else #include <WProgram.h> #endif /* ------------------- TON ------------------- */ class TON { public: TON(); TON(unsigned long PT); boolean Run(boolean IN); boolean Q; //   boolean IN; //   unsigned long PT; //   unsigned long ET; //   -    private: boolean _M; //   unsigned long _StartTime; }; /* ------------------- TOF ------------------- */ class TOF { public: TOF(); TOF(unsigned long PT); boolean Run(boolean IN); boolean Q; //   boolean IN; //   unsigned long PT; //   unsigned long ET; //   -    private: boolean _M; //   unsigned long _StartTime; }; /* ------------------- TP ------------------- */ class TP { public: TP(); TP(unsigned long PT); boolean Run(boolean IN); boolean Q; //   boolean IN; //   unsigned long PT; //   unsigned long ET; //   -    private: boolean _M; //   unsigned long _StartTime; }; /* ------------------- R_TRIG ------------------- */ class R_TRIG //    { public: R_TRIG(); boolean Run(boolean CLK); boolean CLK; //   boolean Q; //   private: boolean _M; //   }; /* ------------------- F_TRIG ------------------- */ class F_TRIG //    { public: F_TRIG(); boolean Run(boolean CLK); boolean CLK; //   boolean Q; //   private: boolean _M; //   }; /* ------------------- RS_TRIG ------------------- */ class RS_TRIG //    { public: RS_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; //   boolean RESET; //   boolean Q; //   //private: }; /* ------------------- SR_TRIG ------------------- */ class SR_TRIG //    { public: SR_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; //   boolean RESET; //   boolean Q; //   //private: }; #endif /* PLCSTANDARDLIB_H_ */ 


plcStandardLib_1.cpp
 /* * 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; //    _StartTime = millis(); // ET = 0; //  = 0 } else { if (!Q) ET = millis() - _StartTime; //   } 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; //    _StartTime = millis(); // ET = 0; //  = 0 } else { if (Q) ET = millis() - _StartTime; //   } 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; //    _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; } 


plcStandardLib_1_example.ino
 #include "plcStandardLib_1.h" #define LED 13 #define ButtonIn 7 TON TON1(500); //   , 500. TON TON2(1000); //   , 1000. TOF TOF1(500); //   , 500. TP TP1(300); //   , 300. TP TP2(200); //   , 200. R_TRIG R_TRIG1; //      void setup() { pinMode(ButtonIn, INPUT_PULLUP); pinMode(LED, OUTPUT); } void loop() { digitalWrite(LED, TP1.Run(R_TRIG1.Run(TON1.Run(digitalRead(ButtonIn))))); // TON1 -    // R_TRIG1 -    // TP1 -     digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q))); //     TON  TP // TON2.Run(!TON2.Q)) -    // TP2 -     digitalWrite(LED, TOF1.Run(TON1.Run(digitalRead(ButtonIn)))); //     } 


For example, to filter the contact bounce of the button (when opened too!), This code is enough:

 FiltredButtonIn = TON1.Run(digitalRead(ButtonIn)) 

As a conclusion: this is how CODESYS looks at the operation of a pulse generator based on a chain of TON and TP timers. At the beginning, TON is covered by inverted feedback, and it produces a single pulse generator that triggers the operation of the pulse generator TP. In my example, the Arduino analogue of this looks like this:

 digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q))); 

Source: https://habr.com/ru/post/402315/


All Articles