Timers and Triggers CODESYS. 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, which implements basic timers, triggers, counters, and some other functions and functional blocks. 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 takes place 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 loop execution time will work. Many may object to me, saying that modern PLCs are multivalued, they are with support for hardware interrupts. I agree, but talking about such systems is not in my plans, but I want to talk about (quasi, pseudo - choose) PLC single-tasking implementation (without interruptions) based on the Arduino microprocessor platform, in which there is only one main loop. By the way, it would not be superfluous to say that the article led me to write this note.Arduino-compatible PLC CONTROLLINO, Part 1 on the attempt of the hardware implementation of Arduino in 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 execution time of the cycle, and it can work both once and for an infinite number of times - as planned by the programmer. When the 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 point 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-s 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 - function block "timer with on-delay"


    TON(IN, PT, Q, ET)

    The IN and PT inputs are BOOL and TIME, 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 the 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. 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(unsignedlong PT); // с заданием интервала времени PT

    Use options:

    Q = TON1.Run(booleanIN); // вызов "все в одном"
    TON1.IN = IN;
    TON1.Run();
    Q = TON1.Q;

    TON Timeline:


    TOF - function block "timer with shutdown delay"


    TOF(IN, PT, Q, ET)

    The IN and PT inputs are of the 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)

    The IN and PT inputs are BOOL and TIME, 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 switches to TRUE, 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 work 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. As soon as 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 generates 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.
    Output Q is FALSE as long as input CLK is TRUE. As soon as 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 produces 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 - function block RS-trigger / SR_TRIG - function 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(booleanSET, booleanRESET); // вызов "все в одном"

    SR_TRIG SR_TRIG1;
    Q = SR_TRIG1.Run(booleanSET, booleanRESET); // вызов "все в одном"

    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 ------------------- */classTON{
    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 ------------------- */classTOF{
    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 ------------------- */classTP{
    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 ------------------- */classR_TRIG // детектор фронта сигнала
    {
    public:
    	R_TRIG();
    	boolean Run(boolean CLK);
    	boolean CLK; // входная переменнаяboolean Q; // выходная переменнаяprivate:
    	boolean _M; // внутренний флаг
    };
    /* ------------------- F_TRIG ------------------- */classF_TRIG // детектор спада сигнала
    {
    public:
    	F_TRIG();
    	boolean Run(boolean CLK);
    	boolean CLK; // входная переменнаяboolean Q; // выходная переменнаяprivate:
    	boolean _M; // внутренний флаг
    };
    /* ------------------- RS_TRIG ------------------- */classRS_TRIG // детектор спада сигнала
    {
    public:
    	RS_TRIG();
    	boolean Run();
    	boolean Run(boolean SET, boolean RESET);
    	boolean SET; // установка триггераboolean RESET; // сброс триггераboolean Q; // выходная переменная//private:
    };
    /* ------------------- SR_TRIG ------------------- */classSR_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(booleanIN)
    {
    	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(booleanIN)
    {
    	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(booleanIN)
    {
    	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(booleanSET, booleanRESET)
    {
    	RS_TRIG::SET = SET;
    	RS_TRIG::RESET = RESET;
    	Q = !RESETand (SETor Q);
    	return Q;
    }
    boolean RS_TRIG::Run()
    {
    	Q = !RESETand (SETor Q);
    	return Q;
    }
    /* ------------------- SR_TRIG ------------------- */
    SR_TRIG::SR_TRIG()
    {
    	SET = false;
    	RESET = false;
    	Q = false;
    }
    boolean SR_TRIG::Run(booleanSET, booleanRESET)
    {
    	SR_TRIG::SET = SET;
    	SR_TRIG::RESET = RESET;
    	Q = SETor (!RESETand Q);
    	return Q;
    }
    boolean SR_TRIG::Run()
    {
    	Q = SETor (!RESETand Q);
    	return Q;
    }
    


    plcStandardLib_1_example.ino
    #include"plcStandardLib_1.h"#define LED 13#define ButtonIn 7TON TON1(500); // Инициализация задержки включения, 500мс.TON TON2(1000); // Инициализация задержки включения, 1000мс.TOF TOF1(500); // Инициализация задержки выключения, 500мс.TP TP1(300); // Инициализация единичного импульса, 300мс.TP TP2(200); // Инициализация единичного импульса, 200мс.
    R_TRIG R_TRIG1; // Инициализация триггера фронта для кнопкиvoidsetup(){
      pinMode(ButtonIn, INPUT_PULLUP);
      pinMode(LED, OUTPUT);
    }
    voidloop(){
      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: the operation of the pulse generator on the basis of the TON and TP timers chain looks like this in CODESYS. At the beginning, TON is covered by feedback with inversion, and a single pulse generator is obtained from it, which 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)));


    Also popular now: