本文是CSDN上看到的一篇质量比较高的文章,自己仔细读了几遍收获很大,值得一起学习。我在作者的基础上重新排版添加了一些备注和修改了一些错误的地方。由于字数比较多,因此分成了上下篇。
本文将会讲解高度抽象的设计模式,设计模式与案例无关,并不以案例讲题。反正学校按这种方式教了那么久也没人听得懂。我将主要以理论为主,避免与具体案例绑定,以防止读者先入为主,以为某一设计模式只能适用于某案例,或某一设计模式只能单独使用,不能结合其它设计模式,又或者某一案例只适合某一设计模式。
快捷键:
简单程序
Y0 := TRUE;
K1Y1 := HF;//指从Y1开始的4个连续端口设置为二进制1111,也就是四路导通。
为什么要用K开头呢? K在PLC符号中代表后面的文本将作为十进制数字使用,然而数字不能用于标识符的第一个字母,这是所有编程语言的通例。 K1是指4个连续端口。为什么是4个? 1位16进制数代表4个端口。这个K1是指“1位16进制数”的意思,也就是4个开关量。如果改为K2,就是“2位16进制数”,代表8个开关量。Y1表示从Y1端口开始计数,实际上就是C语言里的起始指针,数组指针之类的含义。
三菱ST编程相对西门子更简单,因为它可以直接使用X、Y、M、D这几个系统标识,而西门子变量和端口全部都要声明,然后定义到指定地址才能使用。
PLC的经典用法就是串并联逻辑电路,ST语言的串联用AND操作符,并联用OR操作符,动断触点(常闭)用NOT操作符,此外还支持奇校验XOR操作符。
M1 := X0 AND NOT M0;(* 上升沿检测,如果X0是通,M0是断,则M1是通 *)
Y0 := Y0 XOR M1;(* 上升沿的时候转换状态,如果Y0和M1中通的个数为单数,则Y0为通,否则为断 *)
M0 := X0;(* 记忆X0的上一个状态 *)
X0为通时Y0设置为通,X1为通时Y0设置为断,自锁电路。
Y0 := NOT X1 AND (X0 OR Y0);
在梯形图中,有一种专业的自锁电路的替代方法,就是SET和RST指令。它们在ST中被封装成系统宏函数。
SET(X0, Y0);(* 按下X0时,Y0置位 *)
RST(X1, Y0);(* 按下X1时,Y0复位 *)
这是推荐使用的方法,简单易懂。ST宏函数和系统功能块非常多,我们能够记住一些指令就很不容易了,何况它们还有不同的参数。GX Work2提供了一个快捷键,Ctrl + F1,把插入点定位在写好的函数名上,按Ctrl + F1,IDE会自动插入参数提示。如果连函数名也不记得了,可以在右边的窗口双击插入函数名或功能块名。西门子Portal则是从左边的自定义程序和右边的系统程序中拖拽到工作区。系统函数的第一个形参都是使能标志,相当于每一个系统函数内置了一个条件表达式,只有当第一个形参传入TRUE(通)的时候,才执行指令的功能。
Y0 := X0 XOR X1 XOR X2;//做一个三联开关,X0,X1,X2的状态变化都会引起Y0翻转。
这个程序在X0,X1,X2之中任意一个改变状态时,都会使Y0状态变化,也就是任意一个开关都可以自由地操作灯的亮灭。这个表达式如果要用标准的串并联梯形图来做,会非常麻烦。
我们做逻辑运算多用的是高电平奇校验公式,很少用到低电平奇校验公式。当奇校验公式中有奇数个常量目标电平时,根据奇校验的算法,可以证明它等价于偶校验,其实也等于在中间的某个位置加入一个NOT运算符或对最终结果取反。
了解了奇校验的特点后,我们也可以用一个计数器来代替奇校验公式。
ST中的上升沿检测使用LDP,下降沿使用LDF。升级版的单按钮控制:
Y0 := Y0 XOR LDP(TRUE, X0);
LDP的第一个形参还是使能的意思,几乎所有的指令都有很大概率在第一个形参写TRUE,因为三菱PLC所有的指令都需要使能,而多数情况下,这些指令是需要永久使能的。LDP的第二个形参是上升沿检测的变量,这里检测的是X0的上升沿。实际上对应单片机内部电路,X0的引脚可能是收到了下降沿,这个LDP是指的从断到通的状态,与单片机电平方向无关。当收到上升沿的时候,LDP返回一个扫描周期的TRUE,TRUE与XOR组合,就等价于一个NOT,就将Y0翻转了,而LDP只返回一个扫描周期的TRUE,所以也只有这一个扫描周期的XOR等价于NOT,其余扫描周期则为XOR FALSE,也就是不起作用。这样就实现了X0在从断到通时Y0会发生状态翻转,也就是记忆按钮的效果。
定时器
输出自变量,ENO:执行结果:位。定时器触点:TS普通,STS累计。定时器线圈:TC普通,STC累计。定时器当前值:TN普通,STN累计。
计数器
输出自变量,ENO:执行结果:位。计数器触点:CS。计数器线圈:CC。计数器当前值:CN
扩展阅读https://blog.csdn.net/rediculous/article/details/102656817 注意: ST语言不能直接写C251
Coin
线圈。Switch
开关的意思。Number
数字的意思。ST编写AB相编码器
OUT_C_32(TRUE,CC251,0);
第一个参数是使能C251,当它为TRUE时,X0和X1被指定用于C251,这是PLC内部系统的设计,无法更改。第二个参数如果是CC251,表示使用C251,如果是CC252就使用C252。
C252比C251多使用一个X2作为复位。C254比C252多一个X6做外部使能。C253和C252功能一样,但绑定的是X3,X4,X5。C255和C254功能一样,但绑定的是X3,X4,X5,X7。
第三个参数是CS251的阀值。即CS251 := CN251 >= [第三个参数]
。与普通计数器不同,第三个参数不会限制计数器的增长。如果不需要使用CS251,则第三个参数可以随便写。计数器复位当然,C251也可以使用RST(X2, CN251);来复位,C252也可以用OUT_C_32(X6, CC252, 0);RST(X2, CN252);达到与C254一样的效果。但指令表的响应时间为0.3ms,比直接使用内置的功能慢。
梯形图剩下的功能就只有定时器和计数器了。下面演示一下定时器和计数器的用法。这个程序功能为按下X0两次后,Y0输出1秒的通,然后变回断。
OUT_C(X0, CC0, 2);(* 对X0计数,它是边沿有效的,计数器C0,CC0是C0的线圈,第2个C代表"Coin" *)
SET(CS0, M0);(* 达到2的时候,CS0变为TRUE,对M0置位,将M0也变为TRUE,CS0是C0的触点,第2个S代表"Switch"开关的意思 *)
RST(M0, CN0);(* M0为TRUE时,复位计数器C0,M0为TRUE时,按X0不会计数,因为它一直被复位,CN0是C0的数值,第2个N是"Number"数字的意思 *)
OUT_T(M0, TC0, 10);(* M0为TRUE时,定时器T0开始计时,三菱FX3U定时器单位是100ms,第3个参数10表示1秒 *)
RST(TS0, M0);(* 时间到的时候TS0为TRUE,这一步就会复位M0,将系统状态变回起始值 *)
Y0 := M0;(* 将M0的值从Y0输出 *)
我们再来试试自锁电路的一种标准化写法,它同样具有很好的可读性。
IF X0 THEN
Y0 := TRUE;
ELSE_IF X1 THEN
Y0 := FALSE;
END_IF;(* 注意:这个END_IF是要加分号结尾的,ST语言坑死人,多少新手被坑在这里好久才发现突然需要一个分号 *)
比起SET和RST,使用了条件分支使程序看起来更像是说明文档。它的大意是:“如果X0通了,则让Y0也通,如果X0没通,但是X1通了,就让Y0断开,如果以上条件都不满足,就什么也不做,保持之前的状态。” 再来一个自锁(X0,X1)+强制点动(X2)+翻转(X3)的综合型开关电路,动力输出设计为Y4,并且每当按下按钮时,会有对应的指示灯亮起,指示灯设计Yn对应Xn:
K1Y0 := K1X0;(* 4路指示灯直接赋值 *)
IF X0 THEN
Y4 := TRUE;(* 启动 *)
ELSE_IF X1 THEN
Y4 := FALSE;(* 停止 *)
ELSE_IF LDP(TRUE, X3) THEN
Y4 := NOT Y4;(* 翻转 *)
ELSE_IF LDP(TRUE, X2) OR LDF(TRUE, X2) THEN
Y4 := X2;(* X2动作时,取消记忆,强制变为手动 *)
END_IF;(* 再次强调:这个END_IF是要加分号结尾的,ST语言坑死人,多少新手被坑在这里好久才发现突然需要一个分号 *)
读者可以自己试试用梯形图串并联方式实现这个电路,一个简单的功能做成梯形图也会非常复杂。条件分支的完整模板为:
IF conditions THEN
tasks;(* 语法上IF语句任何一段分支的tasks都可以是空的。如果是空的,分号也可以不要 *)
ELSE_IF conditions THEN (* 如果没有其它条件,ELSE_IF必须省略,如果有多个,ELSE_IF应当有很多,语法上没有上限,可以写到程序存储器不够了为止 *)
tasks;
ELSE_IF conditions THEN
tasks;
...
ELSE (* 如果没有默认操作,ELSE可以省略,也可以不省略 *)
tasks;
END_IF;(* 这个地方破天荒地有一个分号 *)
这种分支结构是专门用于状态变量的,需要使用数字作为状态变量,但代码中不一定要按大小顺序排版,也不一定要连续。未显式定义的数字会转接到ELSE段中。做一个演示用的红绿灯,有红亮(Y0)、黄亮(Y1)、绿亮(Y2)三种状态,用一个按钮X0来控制。这个程序并不十分适合这一场景,只是做为介绍语法之用,具体工程中只需要5~6行代码就搞定了。
IF LDP(TRUE, X0) THEN
D0 := D0 + 1;(* 状态变量如果无止境地加上去会怎么样?事实上不会,因为在CASE里有做限制 *)
END_IF;
CASE D0 OF
0:(* 状态0的时候Y0通。不一定要换行,只是为了写注释方便我在冒号后面直接换行了*)
Y0 := TRUE;
Y1 := FALSE;
Y2 := FALSE;
1:(* 状态1时Y1通 *)
Y0 := FALSE;
Y1 := TRUE;
Y2 := FALSE;
2:(* 状态2时Y2通*)
Y0 := FALSE;
Y1 := FALSE;
Y2 := TRUE;
ELSE(* 其它状态时,跳到状态0 *)
D0 := 0;
END_CASE;
与C语言家族不同,结构化文本不需要break,它不会从一个状态直接就执行到下一个状态。case语句的结构很简单,开头CASE和OF中间只能写一个数值变量,后面是常量标签加个冒号,一个ELSE代表未定义的数值,最后以END_CASE;结尾,注意那个分号。简化的写法,通过条件判断来赋值:
IF LDP(TRUE, X0) THEN
D0 := (D0 + 1) MOD 3;
END_IF;
Y0 := D0 = 0;
Y1 := D0 = 1;
Y2 := D0 = 2;
还有依次传递状态 (不推荐)
IF LDP(TRUE, X0) THEN
Y2 := Y1;
Y1 := Y0;
END_IF;
Y0 := NOT (Y1 OR Y2);
个人比较推荐6行写法,比较正规,5行写法太作了,可读性不好。目前看来CASE语句好像没什么用。事实上CASE语句是IF语句的定制化版本,适合范围更窄,但能少写一些代码。接下来的设计模式中如果条件允许,用CASE会有更好的可读性,因为不重要的部分(标签数字)不会太显眼。
FOR temp := 起始值 TO 完成值 BY 增量 DO
业务代码
END_FOR;
while x0 then
y0 := 1;
end_while;
REPEAT
D0 := D0 + 1;
UNTIL D0 == 0 END_REPEAT;
提前终止当前循环,进入尾部的代码,相当于C的 break。
提前跳过本次循环,进入首部的代码,与C的continue类似。但三菱GX Works2中没有这个关键字。
返回,相当于X86汇编的RET指令,RETURN是无操作数指令。ST的函数返回值直接函数名接受值。三菱FX3U的函数是一次性的,建议不要将自定义函数用于FX3U。造成三菱FX3U函数为一次性的原因是FX3U没有动态栈。能够函数复用的X86软件的编译器会给每一个CALL指令前面加上硬件压栈指令PUSH,在RET之后会加上硬件弹栈指令POP。硬件栈是由CPU直接操作的连续内存空间,不是软件模拟的数据结构。它需要CPU内置有栈指针寄存器。而FX3U的PLC程序是虚拟的处理器,没有设计栈指针的功能。虽然可以用软件栈代替,但他们也没有这么做。这就导致了所有CALL命令都绑定在一块固定的寄存器上,无法嵌套。而且寄存器绑定函数后就会导致数据耦合,产生难以预料的后果。
三菱FX3U的功能块可以产生多个实例,每一个实例绑定独立的寄存器,就可以安全地做到代码复用。对于纯计算,即没有状态存储功能的函数和功能块,可以放心地复用,对于有状态存储功能的功能块需要创建多个实例。所有支持梯形图的PLC都难以实现动态栈,因为动态栈和梯形图的程序理念是相背离的,动态栈希望程序尽可能使用动态变量,而梯形图依赖于静态变量。动与静的矛盾之下,PLC设计者必须进行取舍。FX3U的内存量是足够动静共存的,问题在于动态栈很难兼容梯形图,而梯形图才是PLC的主要卖点。
跳转到指定标签位置,和X86指令同名,可以用于代码混淆,不建议手写。但三菱GX Works2中没有这个关键字。不确定是不是我的资料的问题。三菱Q系列有JMP指令(比FX系列可以节省一个LD M8000,其实没什么很大的意义),但不能作为ST的函数或关键字。
全部0条评论
快来发表一下你的评论吧 !