【CODESYS系列PLC定时器使用详解】
CODESYS系列PLC定时器使用详解1. IEC标准定时器
1.1 接通延迟定时器TON1.2 关断延迟定时器TOF1.3 定时脉冲定时器TP1.4 定时器功能块的调用
2. 自定义定时器
2.1 周期脉冲信号2.2 不同占空比脉冲信号2.3 延时接通和断开定时器
1. IEC标准定时器
在IEC61131-3标准中,制定了接通延迟定时器(TON)、关断延迟定时器(TOF)和脉冲定时器(TP)3种标准定时器。其中,使用最多的是接通延迟定时器。
1.1 接通延迟定时器TON
接通延迟定时器是指当输入变量IN为TRUE时,定时器开始计时,ET为当前计时值并作为功能块的输出。当ET等于输入变量PT设置的计时值时,定时器输出变量Q等于TRUE,ET保持为PT设置的计时值。如果输入IN变为FALSE,定时器将被复位,输出Q为FALSE,ET复位为0,也就是说让定时器复位的唯一方法就是将输入IN变为FALSE。TON定时器的梯形图表现形式及时序图如下:
1.2 关断延迟定时器TOF
定时器的输入 IN 为TRUE时,定时器的 Q 输出信号立即为TRUE,定时器的输入为FALSE时,定时器开始计时,只要当定时器在运行,其输出 Q 一直为TRUE,当到达定时时间时,输出端 Q 复位,在到达定时时间之前,如果定时器的输入端重新为TRUE,则定时器复位,输出Q 保持为TRUE。
输出ET提供定时时间,延时从 T#0s 开始到设置的定时时间 PT 结束。当 PT 时间到时,ET将保持定时时间直到输入 IN 返回TRUE为止。如果在达到 PT 定时时间之前,输入 IN 变为TRUE,输出 ET 立即变为 T#0s。为了复位定时器,可以设置将 PT=T#0s。
1.3 定时脉冲定时器TP
定时器的输入 IN 为 TRUE 时,定时器启动,无论定时器输入 IN 如何变化,定时器的实际运行时间都是用户所定义的 PT 时间,在定时器运行时,其输出 Q 等于 TRUE 。输出端 ET为输出端 Q 提供定时时间。定时从 T#0s 开始,到设置的 PT 时间结束。当 PT 时间到时,ET 会保持定时时间直到 IN 变为 FALSE 时。也就是说,计时过程中输入IN变为FALSE,或输入IN又变为TRUE,不影响计时过程的进行,计时仍继续,直到计时时间到,输出Q才变为FALSE。
1.4 定时器功能块的调用
功能块调用是为输入输出引脚赋值的过程,在ST语言中,输入用 := 表示,输出用 => 表示。调用功能块后,可以使用 “.” 操作符访问输入输出变量进行赋值操作。定时时间PT是Time数据类型,T#后面跟需要定时的时间,可以是T#1s、T#100ms、T#1m或者T#1h。根据填入的时间单位确定。所以功能块的调用是非常灵活的,如下:
PROGRAM MAIN
VAR
TON_100ms:TON;
IN,Q:BOOL;
PT,ET:TIME;
END_VAR
TON_100ms(IN:=, PT:=, Q=>, ET=>);
TON_100ms.IN:=IN;
TON_100ms.PT:=T#100MS;
ET:=TON_100ms.ET;
Q:=TON_100ms.Q;功能块调用时可以省略部分或者全部引脚,使用"实例名.输入/输出引脚"的方式进行访问。也可以在功能块内部直接填写需要的参数,如下自动触发定时器,这也是最常用的一种写法,在程序中会非常多的用到。如下:
TON_100ms(IN:=NOT TON_100ms.Q, PT:=T#100MS, Q=>, ET=>);
IF ActTime<100 THEN //ActTime为动作计时值,这里100表示100个100ms=10s
IF TON_100ms.Q THEN ActTime:=ActTime+1;END_IF //每隔100ms加1
ELSE
Timeout:=TRUE;//计时时间到,Timeout可以为动作超时,也可以为某些报警
END_IF
//在程序逻辑的相关地方,不要忘记将ActTime进行复位2. 自定义定时器
2.1 周期脉冲信号
在实际编程中,经常需要用到周期脉冲信号,比如在三色灯柱中,出现某个报警时,需要红灯间隔1s或者0.5s周期闪烁,又或者PLC与HMI通讯时的心跳信号,都需要用到这种方波信号。某些PLC会提供固定几种常用的不同周期的时钟存储器,但是当遇到有的PLC没有这种存储器时,就需要自己来写了。而且自己写可以更方便的设置各种不同周期,PLC程序移植时也会更加方便,下面提供了几种不同的写法:
1.双定时器交替工作
PROGRAM MAIN
VAR
TON_1 : TON;
TON_2 : TON;
OUT : BOOL;// 输出变量
END_VAR
//方法1:双定时器交替工作TON_1(IN:=NOT TON_2.Q, PT:=T#1S, Q=>, ET=>);TON_2(IN:=TON_1.Q, PT:=T#1S, Q=>, ET=>);
OUT:=TON_1.Q;2.单个定时器进行状态切换
PROGRAM MAIN
VAR
TON_1 : TON;
OUT : BOOL;// 输出变量
END_VAR
// 方法2:单个定时器进行状态切换TON_1(IN := TRUE, PT := T#1S);// 定时器持续运行
IF TON_1.Q THEN
OUT := NOT OUT;// 每次定时到切换状态TON_1(IN := FALSE);// 复位定时器
END_IF;3.使用固定扫描周期进行计数(假设20ms周期)
PROGRAM MAIN
VAR
Counter : INT;
OUT : BOOL;// 输出变量
END_VAR
// 方法3:使用固定扫描周期进行计数(假设20ms周期)
Counter := Counter +1;
IF Counter >=50 THEN
Counter :=0;
OUT := NOT OUT;// 每1秒切换输出状态
END_IF;4.使用定时器进行计数
PROGRAM MAIN
VAR
TON_1 : TON;
Counter : INT;
OUT : BOOL;// 输出变量
END_VAR
// 方法4:使用定时器进行计数 TON_1(IN:=NOT TON_1.Q, PT:=T#100MS, Q=>, ET=>);
IF Counter<20 THEN
IF TON_1.Q THEN Counter:=Counter+1;END_IF
ELSE
Counter:=0;
END_IF
OUT:=Counter>0 AND Counter<11;2.2 不同占空比脉冲信号
上述2.1节实现的是50%占空比的周期脉冲信号,它的特点是接通和断开的时间相等,比如说接通1s那么断开也是1s。如果需要接通1s断开2s,或者自定义接通和断开的时间,程序代码应该如何写呢?显然,可以延用上述的几种方法,只需要稍加修改即可。例如方法1双定时器交替工作,修改TON_1的定时时间就是断开的时间,TON_2的定时时间就是接通的时间。程序代码如下:
TON_1(IN:= NOT TON_2.Q, PT:= T#2S, Q=>, ET=>);// 断开2sTON_2(IN:= TON_1.Q, PT:= T#1S, Q=>, ET=>);// 接通1s
OUT:=TON_1.Q;当然,也可以将PT定时时间用TIME型变量替代,这样就能随时修改接通和断开的时间,实现不同占空比的周期脉冲信号。下面还将针对方法4:使用定时器进行计数进行更加详细的说明;其它方法的具体修改这里不再一一展现,可以自己编写代码进行测试。使用方法4只需要修改最后一行代码和计数值,代码如下:
//接通1s 断开2sTON_1(IN:=NOT TON_1.Q, PT:=T#60MS, Q=>, ET=>);
IF Counter<30 THEN //修改计数值为3030个100ms=1个周期3s
IF TON_1.Q THEN Counter:=Counter+1;END_IF
ELSE
Counter:=0;
END_IF
OUT:=Counter>0 AND Counter<11;//计数值为1~10表示接通1s 为11~30表示断开2s也可以将上述代码写成功能块,通过输入变量更加自由的选择接通和断开的时间;我的编程习惯是如果这个功能用的少,而且代码也简单,就不会单独封装成FB,而是在程序需要的地方直接写。FB的代码实现如下:
FUNCTION_BLOCK Cyclic
VAR_INPUT
IN:BOOL;// 100ms定时器的触发信号
TimeON:TIME;// 接通时间
TimeOFF:TIME;// 断开时间
END_VAR
VAR_OUTPUT
OUT:BOOL;// 脉冲输出信号
END_VAR
VAR(*0: 计数值,1: 接通周期数,2: 断开周期数*)
State_1:ARRAY OF UINT;
END_VAR
// 程序主体//输入的TimeON、TimeOFF变量为TIME类型,需将其转化为UINT类型,记录的是多少毫秒数。//SEL是一个选择函数,OUT:=SEL(G,IN0,IN1)中,G为TRUE时,SEL函数返回IN1的值,否则返回IN0的值//输入的TimeON、TimeOFF接通断开时间最小单位为100ms,输入小于100ms时将默认为100ms。//输入的TimeON、TimeOFF只能取100ms的整数倍,如输入为123ms,将默认为100ms。
State_1:=SEL((TIME_TO_UINT(TimeON)/100)>1,1,TIME_TO_UINT(TimeON)/100);
State_1:=SEL((TIME_TO_UINT(TimeOFF)/100)>1,1,TIME_TO_UINT(TimeOFF)/100);
IF State_1<(State_1+State_1) THEN
IF IN THEN
State_1:=State_1+1;
END_IF
ELSE
State_1:=0;
END_IF
OUT:=State_1>0 AND State_1<=State_1;调用功能块如下:
PROGRAM MAIN
VAR
TON_100ms : TON;
OUT : BOOL;// 脉冲输出变量
Cyclic_1 : Cyclic;
END_VAR
TON_100ms(IN:=NOT TON_100ms.Q, PT:=T#60MS, Q=>, ET=>);Cyclic_1(IN:=TON_100ms.Q , TimeON:=T#1S , TimeOFF:= T#2S, OUT=>);
OUT:=Cyclic_1.OUT;这种封装FB的一个好处是功能块内部没有调用系统定时器,当需要定义多个FB变量时,每个FB实例的调用只会占用少量的内存。如果按照方法1使用双定时器封装FB块,每个FB的实例调用都占用了2个TON定时器的资源,而一个TON定时器占用32字节的内存。如果不想自己进行封装,很多PLC中都提供了类似的功能块,例如CODESYS中提供了BLINK功能块。功能块的说明和调用形式如下:
首先需要添加Util库,点击“添加库”,在搜索框中输入Util,点确定进行添加。
输入ENABLE为TRUE时输出OUT开始闪烁,为FALSE时停止闪烁并保持当前值,TIMELOW为OUT断开时间,TIMEHIGH为OUT接通时间。
2.3 延时接通和断开定时器
在实际的项目开发过程中,很多的信号都需要做延时处理,例如外部的数字量输入信号如传感器可能存在误触发的情况,这时就需要对传感器的输入信号延时几十到一百毫秒,程序中的各种逻辑处理,报警条件处理等等都可能需要延时,当一些报警信号需要在PC或者HMI上进行显示或者记录时,有些报警信号可能只会触发一瞬间或者触发时间很短,而PLC和PC的通讯往往又有延迟,这些信号就很难被记录下来,这时就需要做延时断开处理了。
到这里,我们的需求就很明显了,最好有一个FB块,功能是对某个输入信号同时做延时接通和断开处理。不难想到,可以用系统定时器TON编写逻辑封装一个功能块,功能虽然能实现,但是,一个普通小型的项目就需要做几十到一百多个延时处理,如果是中大型的项目,或者一些报警信息,状态显示,数据记录信息考虑非常全面的程序,这种信号延时处理往往都是几百上千个。前面也说过,一个TON定时器占用32字节的内存,忽略定义的其它变量,实例化100次就是3200byte=3.2kb,实例化1000次就是32000byte=32kb,这对PLC的内存是一个比较大的消耗(尽管现代PLC的内存已经足够大),所以,当你需要处理大量的信号延时时,可以尝试写一个自己的功能块。以CODESYS为例,使用循环扫描周期20ms,下面直接通过代码来分析功能块的设计思路:
FUNCTION_BLOCK DualDelay
VAR_INPUT
IN:BYTE;// 0-7:定时器1-8输入
DlyON:ARRAY OF BYTE;// 0-7:定时器1-8延时接通周期数输入
DlyOFF:ARRAY OF BYTE;// 0-7:定时器1-8延时断开周期数输入
END_VAR
VAR_OUTPUT
OUT:BYTE;// 0-7:定时器1-8输出
END_VAR
VAR
Clo:ARRAY OF BYTE;// 8个定时器的扫描周期倒计时计数// 0~7: .0-记录每个定时器在上一个扫描周期的输入状态, .1-目标输出状态, .2-实际输出状态
Res:ARRAY OF BYTE;
i:BYTE;// 位循环
inBit:BOOL;// 记录当前定时器的输入信号
END_VAR
//********************程序代码部分*****************************
OUT:=0;// 初始化输出字节
FOR i:=0 TO 7 DO
inBit:=(IN AND SHL(1,i))<>0;// 获取当前输入位的状态
IF inBit<>Res.0 THEN // 定时器输入信号发生变化
Res.0:=inBit;// 更新上一次扫描周期的状态
IF inBit THEN // 上升沿延时
Res.1:=TRUE;
Clo:=DlyON;
ELSE // 下降沿延时
Res.1:=FALSE;
Clo:=DlyOFF;
END_IF
END_IF
IF Clo>0 THEN
Clo:=Clo-1;// 延时计时周期数递减
ELSE
Res.2:=Res.1;// 输出更新,计时结束或延时周期数为0时立即生效
END_IF
IF Res.2 THEN
OUT:=OUT OR SHL(1,i);// 如果当前输出状态为TRUE,设置输出字节的对应位
END_IF
END_FOR输入信号IN定义为BYTE类型,字节的每个位都单独作为一个定时器,这样,一个FB就有8个定时器可以单独使用,当你在程序的某个逻辑处需要处理好几个信号延时时,只要实例化一次这个FB就拥有了8个定时器,当你需要处理10个或者更多信号延时,还可以用数组的形式进行声明,例如:
Delay:ARRAY OF DualDelay;//以数组的形式声明信号延时功能块现在,数组的每个元素都有8个定时器可以使用,这在书写不同的逻辑代码处将会方便很多,而且,这个8定时器的功能块只占用48byte内存,相当于每个定时器只占用6个字节。
上述代码是通过FOR循环同时处理8个定时器,如果你理解这段代码有任何的困难,可以去掉FOR循环,只看其中一个定时器的代码执行逻辑,也就是将 i 固定为1或者其它值再去阅读,这里假设i=1,也就是第2个定时器IN.1,在第一个扫描周期内,开始由第一行程序从上到下,从左到右执行:
执行第一行,定时器的输出初始化为0,包括OUT.1=0
OUT:=0;// 初始化输出字节i=1时,SHL(1,i)将1左移一位,也就是2#0000 0001左移一位得到2#0000 0010,再与IN进行与运算,0跟1或者0与运算的结果都是0,相当于把该位屏蔽了,1跟1或者0与运算,结果是对方原本的值,相当于拿到对方的值。2#0000 0010 AND IN相当于拿到IN.1这一位的值,其它位全部为0。得到的结果和0进行比较,如果=0,说明IN.1为0,如果<>0,说明IN.1为1,这样inBit就拿到了IN.1的值。
inBit:=(IN AND SHL(1,i))<>0;// 获取当前输入位的状态功能块调用如下,可以根据实际需要使用多个定时器:
trace跟踪结果如下,符合预期。
注意事项:
1.输入Delay延时计数数据类型为byte,最大取值255,按照扫描周期20ms计算,最大延时为5.1s。
2.这个功能块实现的基本原理是依靠循环扫描周期,这里设置的是固定20ms,如果你的程序不是采用固定扫描周期,那这种方法就不适合你。
3.这个功能块的代码实现方法是多种多样的,这里只是提供了一种写法,但本质都是利用循环扫描周期进行计数。
4.不要忘记程序的第一行,OUT:=0;初始化输出信号。
页:
[1]