它发生在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);
用例:
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);
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);
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);
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 #ifndef PLCSTANDARDLIB_1_H_ #define PLCSTANDARDLIB_1_H_ #if ARDUINO >= 100 #include <Arduino.h> #else #include <WProgram.h> #endif 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; }; 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; }; 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; }; class R_TRIG // { public: R_TRIG(); boolean Run(boolean CLK); boolean CLK; // boolean Q; // private: boolean _M; // }; class F_TRIG // { public: F_TRIG(); boolean Run(boolean CLK); boolean CLK; // boolean Q; // private: boolean _M; // }; class RS_TRIG // { public: RS_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; // boolean RESET; // boolean Q; // //private: }; class SR_TRIG // { public: SR_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; // boolean RESET; // boolean Q; // //private: }; #endif
plcStandardLib_1.cpp #include "plcStandardLib_1.h" 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() { 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() { 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() { 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() { 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() { 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);
例如,要过滤按钮的联系人的颤动(也要同时打开!),此代码就足够了:
FiltredButtonIn = TON1.Run(digitalRead(ButtonIn))
总结:这就是CODESYS看起来像是基于TON和TP计时器链的脉冲发生器。 最初,TON被带反演的反馈所覆盖,并从中生成单个脉冲发生器,从而开始TP脉冲发生器的操作。 在我的Arduino示例中,类似的代码如下:
digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q)))