计时器和触发器CODESYS。 Arduino向经典PLC迈出的又一步


它发生在CODESYS环境中的程序控制器(PLC)中。 每个处理过该系统的人都知道,在任何项目中都有一个Standard.lib库,该库中实现了基本的计时器,触发器,计数器以及许多其他功能和功能块。 这些单元中的许多单元都在PLC程序中不断使用。 并且库本身像CODESYS编程语言一样,是IEC 61131-3标准的一个实施例,即 设计用于帮助经典PLC任务的编程。

PLC程序的功能之一是,程序的主循环应在没有明显延迟的情况下运行,它的内部循环不应具有未定义的退出时间或“有思想的”功能的同步调用,尤其是在慢速通道上的通信方面。 该过程的输入和输出图像仅在主循环的边界处更新,并且我们在循环的一次迭代中“坐下”的时间越长,对控制对象的真实状态的了解就越少,最终,循环溢出的看门狗将起作用。 许多人可能会反对我,说现代PLC模棱两可,支持硬件中断。 我同意,但是我的计划中未包括谈论此类系统,但我想谈论基于Arduino微处理器平台的单任务实现(无中断)的(准伪选择)PLC,其中只有一个主循环。 顺便说一句,说与Arduino兼容的PLC CONTROLLINO(关于在硬件中实现Arduino的尝试的第1部分)的这篇文章促使我撰写此说明并不是没有错。 可编程逻辑控制器

关于Arduino的几句话。 从PLC程序员的角度来看,Arduino是具有一个非常快或相反的一个非常慢的loop()循环的典型控制器。 循环的执行时间没有限制,根据程序员的计划,它可以计算一次和无数次。 当程序很简单并且简化为执行顺序操作,控制器而没有并行事件时,只需使用无穷的嵌套条件检查周期和类型为delay()的同步延迟来替换操作即可。 这样的程序的顺序步骤将逐字地,简单且逻辑地执行。 但是,一旦需要对并行操作进行编程,就必须更改程序范例。

在单任务系统中,可见并行性只能通过非常快速的并行状态顺序扫描来实现,而不会长时间停留在每个函数调用或条件检查上。 物理输入和输出没有问题,功能运行得足够快,但是delay()变成了不合理的制动。 在这里,非阻塞计时器进入了,这正是PLC编程中的经典计时器。 底线是它们使用毫秒计数器,并且所有操作都与该全局计数器的值相关。

现在,让我们记住来自CODESYS的相同Standard.lib。 它只是实现了IEC-s的非阻塞计时器。 我以此为基础,将计时器和触发器的功能移植到Arduino库代码(C ++)。 即 试图使Arduino更接近经典PLC。

下面,我将在我的plcStandardLib库中简要介绍CODESYS的移植功能块(FB)及其类似物,所有时序图对于新的Arduino库都是正确的。 例如,可以在CODESYS的俄语帮助中找到对源块的更详细描述。

TON-功能块“延迟打开的计时器”


TON(IN, PT, Q, ET) 

分别输入BOOL和TIME类型的IN和PT。 输出Q和ET与BOOL和TIME类型相似。 当IN为FALSE时,输出Q = FALSE,输出ET =0。一旦IN变为TRUE,输出ET的倒计时(以毫秒为单位)就会开始等于PT的值。 此外,计数器不增加。 当IN为TRUE且ET为PT时,Q为TRUE,否则为FALSE。 所以
因此,从输入IN的边沿开始以PT的延迟设置输出Q。

在Arduino IDE中:


广告选项:

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

用例:

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

TON操作时序图:


TOF-功能块“延时关机定时器”


 TOF(IN, PT, Q, ET) 

分别输入BOOL和TIME类型的IN和PT。 输出Q和ET与BOOL和TIME类型相似。 如果IN为TRUE,则输出Q = TRUE,输出ET =0。一旦IN变为FALSE,输出ET的倒计时(以毫秒为单位)开始。 当达到设定的持续时间时,倒计时停止。 如果IN为FALSE而ET为PT,则输出Q为FALSE,否则为TRUE。 因此,输出Q从输入IN的下降开始以延迟PT被复位。

在Arduino IDE中:


简而言之,与TON非常相似:

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

TOF时序图:


TP-脉冲计时器功能块


 TP(IN, PT, Q, ET) 

分别输入BOOL和TIME类型的IN和PT。 输出Q和ET与BOOL和TIME类型相似。 当IN为FALSE时,输出Q = FALSE,输出ET =0。当IN变为TRUE时,输出Q设置为TRUE,并且计时器开始在输出ET上计数(以毫秒为单位),直到达到PT指定的持续时间为止。 此外,计数器不增加。 因此,输出Q沿输入IN的边缘产生持续时间PT的脉冲。

在Arduino IDE中:


简而言之,与TON非常相似:

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

TP操作时序图:


R_TRIG-功能块“前检测器”


R_TRIG功能块在输入信号的上升沿生成一个脉冲。 只要输入CLK为FALSE,则输出Q为FALSE。 CLK变为TRUE时,Q设置为TRUE。 下次调用该功能块时,输出将重置为FALSE。 因此,在从FALSE到TRUE的每个CLK转换中,该模块都会发出单个脉冲。

ST中的示例CODEDESYS:

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

在Arduino IDE中:


公告:

 R_TRIG R_TRIG1; 

用例:

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

F_TRIG-功能单元“下降检测器”


功能块F_TRIG在输入信号的后沿生成一个脉冲。
输出Q为FALSE,直到输入CLK为TRUE。 一旦CLK设置为FALSE,Q设置为TRUE。 下次调用该功能块时,输出将重置为FALSE。 因此,该模块在每次从TRUE到FALSE的CLK转换时发出一个脉冲。

在Arduino IDE中:


 F_TRIG F_TRIG1; Q = F_TRIG1.Run(boolean CLK); //  "  " 

RS_TRIG-功能块RS触发/ SR_TRIG-功能块SR触发


断开主开关,RS触发:

 Q1 = RS (SET, RESET1) 

切换至主导:

 Q1 = SR (SET1, RESET) 

输入变量SET和RESET1与BOOL类型的输出变量Q1相同。

在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); //  "  " 

源代码和示例


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)))); //     } 


例如,要过滤按钮的联系人的颤动(也要同时打开!),此代码就足够了:

 FiltredButtonIn = TON1.Run(digitalRead(ButtonIn)) 

总结:这就是CODESYS看起来像是基于TON和TP计时器链的脉冲发生器。 最初,TON被带反演的反馈所覆盖,并从中生成单个脉冲发生器,从而开始TP脉冲发生器的操作。 在我的Arduino示例中,类似的代码如下:

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

Source: https://habr.com/ru/post/zh-CN402315/


All Articles