ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PLC状态机编程实践:Sysmac Studio自动送料Demo详解

PLC状态机编程实践:Sysmac Studio自动送料Demo详解 1. 为什么PLC程序里要用状态机先说个普遍现象很多做非标设备、小型自动化项目的工程师一开始写的PLC程序都是“一锅烩”。什么叫一锅烩就是整个程序里到处都是M继电器和置位复位指令气缸伸出去一个SET缩回来一个RST电机启动一个线圈停止又是另一个线圈。程序写的多了气缸多了工位多了你会发现M继电器根本不够用而且最要命的是——你根本不知道某个M继电器在哪些地方被置位了又在哪些地方被复位了。排查问题的时候只能一个程序段一个程序段地翻翻到头晕。这个问题的本质在于程序里的“状态”是隐性的、分散的。每个M继电器都代表一个隐含的中间状态但没有任何地方能总览整个设备当前处于什么阶段。一旦设备动作逻辑复杂一些比如有自动、手动、回原点、暂停、复位、报警恢复这些模式来回切换或者有多个工位的节拍互相配合程序逻辑就会迅速膨胀变成一团乱麻。状态机就是用来解决这个问题的。状态机的核心思想就是把设备的运行过程拆解成若干个明确的“状态”然后规定状态与状态之间的跳转条件。按照这种方式写出来的程序在任何时候你都清清楚楚地知道设备在干什么、下一步能去哪一步、什么条件触发这个跳转。好处有三点第一是逻辑清晰代码的可读性大大提升换一个人来维护也不会骂娘第二是排查故障方便现场出了问题你只需要看PLC当前处于哪个状态结合跳转条件就能很快定位问题第三是结构稳定不会出现那种“这个M继电器不知道被谁复位了”的莫名其妙的偶发故障。Sysmac Studio是欧姆龙NJ/NX系列PLC的编程软件它支持IEC 61131-3标准可以写ST语言结构化文本也支持FB功能块、面向对象编程局部类等这些高级特性。相比传统CX-Programmer的梯形图Sysmac Studio里做状态机是非常顺手的因为它有ENUM枚举类型有STRUCT结构体还有完整的ST文本编辑环境。你可以用非常接近高级语言的写法去组织状态逻辑而不是像用梯形图那样靠辅助继电器堆砌。这篇文章就是我自己的一个实践记录。我用欧姆龙NX系列PLC在Sysmac Studio里写了一个小的状态机Demo用这个例子来讲清楚状态机的写法思路、具体代码实现、调试方法和踩坑心得。如果你以前用梯形图写逻辑写够了想换个思路这篇文章应该能给你不少参考。2. 在Sysmac Studio里搭建状态机的基础环境2.1 创建工程与选择PLC型号在动手写状态机之前先把软件环境和工程建好。Sysmac Studio的界面和传统CX-Programmer差别很大整体风格更接近Visual Studio那种IDE的感觉。左侧是“多视图资源浏览器”变量表、程序、任务、设备配置都在这里管理不是像老旧软件那样一上来就是一段一段的梯形图。打开Sysmac Studio后新建工程选择PLC系列。以我手头的NX102-1200为例这个型号属于NX1P2之后的进阶版支持EtherCAT总线最多带4轴价格和性能都比较适合中小型设备。在“创建工程”对话框里选择“NJ/NX系列”然后选择对应的CPU型号再设置好单元版本。这里有个细节单元版本会影响指令集和功能块支持如果你的Sysmac Studio版本比较新建议直接选最新的单元版本功能更全。建完工程之后Sysmac Studio会自动生成一个“程序”区默认有一个Program0。在这个Program0下面会有一个“梯形图/ST”的目录里面默认建了一个名为“Program0”的程序体。你可以右键重命名比如把主程序命名为“Main”。这个MainProgram体可以切换语言——右键程序体在属性里可以选择“梯形图/Ladder”或“ST”。我建议状态机的主逻辑用ST全局变量和I/O映射用梯形图或者其他结构化方式。原因很简单状态跳转逻辑用ST的CASE语句写起来非常顺手梯形图写状态跳转会给你带来无穷无尽的烦恼。2.2 任务周期配置与看门狗设置状态机的运行离不开任务周期的设定。在Sysmac Studio里所有程序都是在任务Task里周期执行的。打开“配置和设置”→“任务设置”可以看到系统默认有“主任务”和“定期任务”两类。主任务是最高优先级的EtherCAT刷新、运动控制指令都在主任务里执行定期任务则是按设定的周期来执行比如1ms、2ms、4ms、10ms等。状态机程序一般放在主任务或者优先级较高的定期任务里任务周期的选择取决于你的设备对响应速度的要求。如果是气缸、电机启停这种毫秒级的控制1ms到4ms都可以如果涉及EtherCAT轴的运动控制最好放在主任务里跟运动控制刷新频率保持一致。还有一点要特别注意Sysmac Studio默认开启了看门狗Watchdog功能。看门狗的目的是监测程序是否在设定时间内完成了扫描。如果你的状态机逻辑里写了Wait循环、或者用了FOR循环等待什么条件很容易触发看门狗超时导致CPU报错停机。这个坑我踩过后面会详细说。所以在开始写状态机之前先把任务周期和看门狗时间设定合理免得调试过程中频繁报警。2.3 变量规划与I/O映射写状态机之前还有一件事要做规划好输入输出变量。我习惯的做法是把所有的物理输入输出点单独做一层映射程序内部不直接引用物理地址而是用专门的全局变量去对应。比如物理输入_IO_StartButton启动按钮接在X0上物理输出_IO_CylinderA_Out气缸A伸出接在Y0上程序里所有状态判断都用这些带前缀的映射变量而不是直接写X0和Y0。这么做的目的是方便以后改接线。现场改一个传感器的常开常闭很多时候只需要改映射不用动程序逻辑。在“全局变量表”里把I/O映射、中间变量、状态变量都定义好。状态机相关的变量也要在这里提前规划好比如当前状态变量跳转条件用到的输入信号输出控制变量我建议把状态变量搞成枚举类型这样在ST里看起来非常直观调试的时候状态值也是可读的符号而不是一堆数字。枚举类型怎么定义下一节详细说。3. 状态机建模先把设备流程拆清楚再动手写3.1 用枚举类型定义设备状态状态机的第一步不是写代码而是做状态建模。什么叫做状态建模就是把设备从启动到停止甚至从断电到上电这一整段过程里可能处于的每一个阶段都罗列出来然后思考清楚每个阶段的进入条件、驻留条件、退出条件。我习惯用一个具体的设备来练习。假设现在有一个简单的自动送料机构它由两个气缸和一个传送带组成。设备的工作流程是这样的启动按钮按下后传送带开始运转把工件送到挡停位置挡停位置的光电传感器检测到工件后传送带停止气缸A伸出把工件夹紧夹紧到位后气缸B伸出把工件推入下一个工位推料到位后气缸B先缩回然后气缸A松开最后传送带重新启动开始下一轮的送料。整个过程中如果按下急停所有动作立即停止急停复位后设备需要回到初始状态然后重新启动。就这么一个简单的流程如果按照传统的梯形图写法你可能需要七八个M继电器来记住“现在走到哪里了”。而用状态机来建模只需要把流程中的几个稳定阶段提取出来S_IDLE待机状态传送带停止气缸都在原位等待启动信号。S_RUN_CONVEYOR传送带运转等待工件到位。S_CLAMP_CYL_A工件到位后传送带停止气缸A夹紧。S_PUSH_CYL_B气缸A到位后气缸B推出。S_RETRACT_CYL_B气缸B到位后气缸B缩回。S_RELEASE_CYL_A气缸B缩回到位后气缸A松开。S_WAIT_NEXT气缸A松开到位后这里是给操作员或下一道工序留一个缓冲然后再回到待机状态或直接进入下一轮循环。你可能会问S_WAIT_NEXT和S_IDLE有什么区别在功能上可能有时候是一样的但是在语义上它们是有区别的。S_WAIT_NEXT意思是“本周期喂料流程已经完成等待允许下一轮的条件”S_IDLE是“设备处于上电后的初始待机等待启动按钮”。把这两个状态分开以后想加“连续自动循环”功能就非常方便——在S_WAIT_NEXT里直接跳回S_RUN_CONVEYOR就是连续模式跳回S_IDLE就是单次模式。这个区分很关键。状态机的好处之一就是状态的划分越贴近设备的客观流程后续扩展功能就越容易。把这些状态定义为枚举类型在Sysmac Studio里可以这样写在程序的“变量”区域里新建一个枚举类型或者在全局变量表里右键“新建数据类型”选择“枚举”。枚举类型定义如下TYPE ST_AutoFeedState : ( S_IDLE : 0, S_RUN_CONVEYOR : 1, S_CLAMP_CYL_A : 2, S_PUSH_CYL_B : 3, S_RETRACT_CYL_B : 4, S_RELEASE_CYL_A : 5, S_WAIT_NEXT : 6 ); END_TYPE注意枚举类型的底层本质是整数这里我显式地给每个枚举值赋了初始编号从0开始。这样做的目的是以后如果要在HMI上显示状态或者用数值做诊断你可以明确知道整数0到6分别对应什么状态不至于因为编译器自动编号的规则变化而乱了。而且以后如果要在状态序列中间插入一个新的状态显式编号也能避免后面的枚举值因为插入而整体偏移这个习惯建议一开始就养成。3.2 状态机的大脑CASE语句结构有了枚举类型状态机的主程序结构就很清晰了。Sysmac Studio支持IEC 61131-3标准所以可以用CASE语句对当前状态做分支处理。一个最基础的状态机主循环长这样// 状态机主程序每个扫描周期执行一次 CASE stateFeed OF S_IDLE: // 待机状态输出全部复位等待启动条件 bConveyorRun : FALSE; bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; IF bStartButton THEN stateFeed : S_RUN_CONVEYOR; END_IF; S_RUN_CONVEYOR: // 运转传送带等待工件到位信号 bConveyorRun : TRUE; IF bWorkpieceDetect THEN bConveyorRun : FALSE; stateFeed : S_CLAMP_CYL_A; END_IF; S_CLAMP_CYL_A: // 夹紧气缸A bCylinderA_Out : TRUE; IF bCylinderA_OutDetect THEN stateFeed : S_PUSH_CYL_B; END_IF; // ……其余状态按此模式填充 END_CASE;这个结构非常直观。每个状态下面第一件事是设定这个状态下各个输出的值第二件事是判断是否满足跳转条件、应该跳转到哪个状态。这种写法的好处是你打开程序一眼就能看到“当前状态接下来能去哪些状态”整个状态跳转的逻辑都在一个CASE块里不需要到处找M继电器。可能有人会担心CASE语句这种集中式写法会导致代码行数暴涨。确实如果一个设备有三十个状态这个CASE块会非常长。但长不是问题可读性差才是问题。状态多的情况下你可以按功能把CASE块拆分成多个比如一个“自动流程状态机”一个“手动模式状态机”一个“报警处理状态机”然后用一个更上层的“运行模式状态机”来管理它们。这种嵌套式状态机是后面的大杀器先按住不表。3.3 状态机的时间基准什么时候用定时器什么时候别用在状态机里经常遇到需要等待某个动作完成、或者某个信号稳定维持一段时间的情况。这里有两个选择用定时器或者用计数累加。Sysmac Studio里用TON接通延时定时器是常规操作但它有一个特点TON的定时器实例需要保持前面的条件持续为TRUE。在状态机里使用TON需要注册一个TON实例的变量FB实例然后在每个状态里调用。比如在S_RUN_CONVEYOR状态里启动一个TON延时2秒后如果工件还没到位就判定为缺料报警跳转到S_ALARM。在Sysmac Studio的ST里TON是这样的// 全局或局部声明 TON_bWorkpieceMissing : TON; // 在状态S_RUN_CONVEYOR中 S_RUN_CONVEYOR: bConveyorRun : TRUE; TON_bWorkpieceMissing( In : TRUE, PT : T#2S, Q bWorkpieceMissingAlarmStart ); IF bWorkpieceDetect THEN bConveyorRun : FALSE; stateFeed : S_CLAMP_CYL_A; // 复位计时器 TON_bWorkpieceMissing( In : FALSE, PT : T#2S ); ELSIF bWorkpieceMissingAlarmStart THEN stateFeed : S_ALARM; END_IF;这里有一个关键细节TON的In信号必须在离开这个状态之前给到FALSE否则定时器会保持记忆。很多人第一次用会踩坑状态都已经跳走了定时器还在那跑定时到点之后莫名其妙触发了一个跳转。所以每次在状态内部使用定时器离开状态之前一定要把定时器条件切断。我用得更多的其实是另一种方式利用扫描周期的累加来实现延时。状态机程序有自己的任务周期比如1ms你可以用一个整数变量来做计数在状态内部每周期加1达到设定阈值就执行跳转。这样做的好处是延时的精确度不受定时器实例管理的限制而且占用的变量就是一个INT或者DINT不会因为同时开了很多定时器而搞不清哪个定时器是干什么用的。如果你对定时精度有要求那么用任务周期累加的方式完全可以保证精度。举个例子在MODE1ms的任务周期下一个从0累加到5000的DINT变量就是5000个扫描周期对应5秒延时。需要注意用任务周期累加要求这个状态必须每一次扫描周期都被执行到如果状态机里存在某种跳转把这段逻辑跳过那计时就不准了。这一点要心里有数。4. 状态机的几种典型写法对比与演进4.1 用IF嵌套写状态机最简单但容易失控很多从梯形图转过来的工程师第一次用ST写状态机最容易写出来的其实是IF嵌套的写法类似于IF stateFeed S_IDLE THEN // 待机处理 ELSIF stateFeed S_RUN_CONVEYOR THEN // 传送带处理 ELSIF stateFeed S_CLAMP_CYL_A THEN // 气缸A处理 END_IF;这种写法思路很直接用一连串的IF...ELSIF...来区分不同状态。在小程序里比如只有两三个状态这样写也没什么问题。但如果状态超过十个、十几个这种写法会变得非常难维护。主要问题在于IF...ELSIF的结构本质上是一个线性判断链每次扫描周期从第一个IF开始一路判断到最后效率不是问题问题是阅读程序的人需要从第一个IF开始一路看到最后才能看到某个状态的处理逻辑不方便快速定位。而且IF嵌套写状态机的时候人很容易手滑把ELSIF写错或者遗忘某个ELSE分支导致状态落到真空区。这里说的真空区就是没有任何一个IF分支匹配当前状态值程序就静默地什么都不执行输出全部保持前一个状态的值设备就可能卡在那里不动。排查起来还特别麻烦因为你看状态变量值既不是状态0也不是状态1而是一个什么都不是的数。CASE语句从编译器的语义上就保证了“当前值匹配哪一个分支就执行哪一段”如果值不在枚举范围内编译器会提示或者你可以通过ELSE分支兜底。所以在Sysmac Studio的ST环境里我建议一律用CASE语句作为状态机的骨架不要用IF嵌套。4.2 用BOOL触点组表示状态梯形图思维的最后倔强还有一种在梯形图里常见的“状态机”就是定义一组BOOL变量每个BOOL代表一个状态通过置位复位来实现状态转移。比如说定义变量M0 待机M1 传送带运行M2 气缸夹紧等等然后在梯形图里写待机条件下SET M1RST M0下一条件下SET M2RST M1这种方式本质上是状态机的“标志位法”在我之前用CX-Programmer的时候也常这么干。它的问题在哪里最突出的问题是状态数目一多M标志的置位和复位逻辑就会交叉引用程序里出现“M100在任何地方都可能被复位”的情况。到了这时候就回到了文章开头说的那个问题——一锅烩越维护越乱。它的好处也有就是梯形图的直观性很多电工出身的朋友对置位复位非常熟悉改用这种方式上手快。但如果你的项目是用Sysmac Studio做的我强烈建议直接用ST ENUM的方式来做状态机步子迈大一点用习惯之后你会发现回不去了梯形图写状态跳转实在是太费劲了。4.3 函数块封装让状态机变成可复用的功能块状态机写到后面你会发现其实很多设备的状态流程是有共性的。比如送料机构的状态机、夹紧机构的状态机、出料机构的状态机它们的逻辑骨架都一样只是输入输出不同、状态数量不同。这个时候你就可以把状态机封装成一个功能块FB在多个设备上复用这就是Sysmac Studio面向对象思想的用武之地。封装一个状态机FB需要注意的点和写普通状态机不太一样。FB的内部变量要区分好输入变量VAR_INPUT、输出变量VAR_OUTPUT、内部变量VAR。输入变量主要是跳转条件输出变量主要是控制指令内部变量就放当前状态。类的实例化之后你需要选择是单实例还是数组实例。在Sysmac Studio里FB可以定义为数组这在多工位设备上是神器——一个工位一个FB实例程序写一次所有工位通用。第一次封装状态机FB的时候我建议拿一个最简单设备先练手把FB的输入输出接口想清楚再逐步增加复杂度。这个过程对后续的大型项目非常有帮助因为你会发现其实很多设备的动作逻辑都是状态机的排列组合。4.4 嵌套状态机如何管理模式和子流程设备往往不止有自动流程还有手动操作、回原点、报警处理、故障复位这些流程。如果你把所有流程都塞进一个大状态机里状态数会爆炸程序会变得极其臃肿。更合理的思路是用嵌套状态机顶层的“运行模式状态机”只管运行模式比如自动模式、手动模式、报警模式而在“自动模式”内部再嵌套一个“自动流程状态机”管具体的走料流程。这在Sysmac Studio里实现起来并不复杂。顶层状态机用一个变量比如stateMode来区分模式内部自动流程用stateFeed来区分流程步骤。在顶层状态机的AUTO模式下才执行自动流程状态机的CASE在手动模式下自动流程状态机不参与输出。嵌套状态机的好处是层次清晰每个状态机只负责自己那层逻辑修改任何一个层级的逻辑都不会影响其他层。排查问题的时候先看顶层状态机当前处于什么模式再看底层自动流程状态机当前处于哪个状态问题定位一下子就缩小了。关于嵌套状态机的层级深度我的建议是不要超过三层。层次太深会让程序的可读性下降除非你对整个结构非常熟悉否则三层以上自己都会被绕晕。三层对于绝大多数自动化设备已经足够了。5. 实操过程一个自动送料Demo的完整实现5.1 全局变量与I/O映射接下来我把前面说的自动送料机构从头到尾写一遍记录完整的实操过程。这里是完整变量表规划变量名类型注释bStartButtonBOOL启动按钮物理输入映射bStopButtonBOOL停止按钮物理输入映射bWorkpieceDetectBOOL工件到位光电传感器物理输入映射bCylinderA_InDetectBOOL气缸A缩回到位传感器物理输入映射bCylinderA_OutDetectBOOL气缸A伸出到位传感器物理输入映射bCylinderB_InDetectBOOL气缸B缩回到位传感器物理输入映射bCylinderB_OutDetectBOOL气缸B伸出到位传感器物理输入映射bConveyorRunBOOL传送带运行输出物理输出映射bCylinderA_OutBOOL气缸A伸出电磁阀物理输出映射bCylinderB_OutBOOL气缸B伸出电磁阀物理输出映射stateFeedST_AutoFeedState自动流程状态eStopResetBOOL急停复位信号bRunEnableBOOL自动运行允许变量表在Sysmac Studio的“全局变量表”里定义。我习惯加前缀b开头是布尔量st开头是状态枚举变量i开头的整数r开头的实数。这样在ST程序里扫一眼变量名就能知道数据类型。5.2 初始化逻辑程序上电后第一个扫描周期需要做一些初始化操作把所有输出复位、状态机回到初始状态、定时器清零。在ST里可以利用Sysmac Studio的“初始步进”或“事件任务”来做。更简单的做法是在状态机主程序开头用一个首次扫描标志// 首次扫描初始化 IF NOT bInitComplete THEN bConveyorRun : FALSE; bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; stateFeed : S_IDLE; bInitComplete : TRUE; END_IF;bInitComplete是全局变量默认FALSE程序第一次扫描时进入初始化块执行完之后置为TRUE之后再也不会进入。这个方式在Sysmac Studio里完全可行需要注意的是如果用其他程序段也想在首次扫描时初始化这个变量要共享避免各段重复初始化覆盖彼此的变量。5.3 自动流程状态机完整代码以下是我在Sysmac Studio里实际写的自动流程状态机完整代码。这个代码在ST语言里可以直接编译通过// // 自动送料机构状态机 // CASE stateFeed OF S_IDLE: // 待机状态输出全部复位 bConveyorRun : FALSE; bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; // 启动条件启动按钮按下 所有气缸在原位 IF bStartButton AND bCylinderA_InDetect AND bCylinderB_InDetect THEN stateFeed : S_RUN_CONVEYOR; END_IF; S_RUN_CONVEYOR: // 传送带运行等待工件到位 bConveyorRun : TRUE; bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; // 工件到位后停止传送带进入夹紧 IF bWorkpieceDetect THEN bConveyorRun : FALSE; stateFeed : S_CLAMP_CYL_A; END_IF; S_CLAMP_CYL_A: // 气缸A夹紧工件 bCylinderA_Out : TRUE; bCylinderB_Out : FALSE; // 夹紧到位后进行推料 IF bCylinderA_OutDetect THEN stateFeed : S_PUSH_CYL_B; END_IF; S_PUSH_CYL_B: // 气缸B推出把工件推到下一工位 bCylinderA_Out : TRUE; // A保持夹紧状态 bCylinderB_Out : TRUE; // 推料到位后退出推料过程 IF bCylinderB_OutDetect THEN stateFeed : S_RETRACT_CYL_B; END_IF; S_RETRACT_CYL_B: // 气缸B缩回 bCylinderA_Out : TRUE; // A保持夹紧 bCylinderB_Out : FALSE; // B缩回到位后松开A IF bCylinderB_InDetect THEN stateFeed : S_RELEASE_CYL_A; END_IF; S_RELEASE_CYL_A: // 气缸A松开 bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; // A松开到位后进入下一轮等待 IF bCylinderA_InDetect THEN stateFeed : S_WAIT_NEXT; END_IF; S_WAIT_NEXT: // 一个送料循环完成在这里选择单次模式还是连续模式 bConveyorRun : FALSE; bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; // 如果启动按钮还按着或者一个允许循环的信号存在直接进入传送带运转 IF bStartButton OR bRunEnable THEN stateFeed : S_RUN_CONVEYOR; ELSE stateFeed : S_IDLE; END_IF; ELSE: // 兜底分支如果状态值异常回到待机 stateFeed : S_IDLE; END_CASE;这段代码里有两个细节值得注意。一个是状态内对输出变量的赋值。我在每个状态里都对三个输出变量做了统一赋值这样即使状态之间跳转发生误差输出也不会产生意料之外的保持。比如在S_RUN_CONVEYOR里bCylinderA_Out和bCylinderB_Out必须显式写FALSE否则如果上一个状态是S_CLAMP_CYL_AB的输出会一直保持TRUE。这就是在状态机里最需要注意的“输出泄漏”问题。另一个细节是ELSE分支。CASE语句加ELSE兜底如果因为某种原因状态变量变成了一个不在枚举范围内的值比如被通讯或者HMI强行写入了一个非法值程序不会卡在真空区而是自动回到S_IDLE。这对于设备安全非常重要。5.4 状态跳转的“同周期二次跳转”陷阱在仔细看上面的代码时你可能已经注意到一个问题在S_WAIT_NEXT状态里如果条件满足我直接跳转到了S_RUN_CONVEYOR。但是在同一个扫描周期里跳转到S_RUN_CONVEYOR之后这个CASE语句已经执行完了S_RUN_CONVEYOR里的动作不会被处理需要等到下一个扫描周期才执行。这种跳转方式叫“状态末尾跳转”通常没问题因为PLC处理的速度很快差一个扫描周期1ms级别对现场来说完全无感。但如果你有这样的需求从状态A跳转到状态B的同一个周期内B状态的处理逻辑也要立刻执行这就需要在编程方法上做一些处理。Sysmac Studio的ST语言里CASE语句本身的特点就是一个周期只能执行一个分支。如果你需要立即执行跳转后的状态可以引入“中间变量NextState”的方式在每一个状态的末尾不直接修改stateFeed而是赋值给NextState在整个CASE语句结束后统一把NextState写入stateFeed然后再做一次轻量级的二次分发或者直接循环处理。这种做法的复杂度比较高大多数设备根本不需要。我自己的经验是除非你的设备有那种“传感器信号持续时间极短、必须在信号变化的同时完成两段状态的动作”这种苛刻要求否则直接在每个周期末尾跳转就够了。PLC的扫描周期足够快绝大多数动作差一个扫描周期机械上根本感知不到。但有一个情况需要特别注意跳转条件如果是瞬时脉冲信号比如光电传感器只遮挡了一下那么你在这个扫描周期检测到信号、跳转到下一个状态但是下一个扫描周期传感器信号已经消失这会影响你在新状态里的逻辑判断吗答案是要看情况。如果新状态的开始动作不依赖这个瞬时信号就没事如果新状态的第一件事是检测“工件还在不在”那传感器信号消失可能会导致逻辑误判。解决办法很简单就是给脉冲信号加保持。在Sysmac Studio里可以用一个置位锁存的方式把瞬时信号锁存为“本次流程需要处理的信号”等状态机走到合适的位置再复位掉。这个手法在实际工程中非常常用我称它为“事件锁存”。5.5 用事件锁存处理瞬时信号回到自动送料的例子里启动按钮如果用的是普通按钮而不是带锁按钮按下去的瞬间最多也就是几十毫秒PLC扫描周期如果是1ms几十个周期肯定能捕捉到。但如果设备比较大扫描周期被延长到10ms甚至更长按钮按下去的脉冲可能在两个扫描周期之间就消失了程序可能会漏掉这次启动请求。一个稳妥的做法是在程序里对启动按钮做锁存// 在程序开头的地方检测启动按钮上升沿 IF bStartButton AND NOT bStartButton_Latched THEN bStartButton_Latched : TRUE; END_IF;然后在状态机的S_IDLE状态里判断条件从IF bStartButton改成IF bStartButton_Latched进入S_RUN_CONVEYOR之后在合适的时机复位bStartButton_Latched。Sysmac Studio里也支持上升沿检测指令用法是R_TRIG功能块。R_TRIG实例变量的使用方式和TON类似需要声明一个实例每次扫描周期调用一次然后访问Q输出。在实际的工程代码里用R_TRIG比手动锁存更易于阅读但它要求“每一个扫描周期都要调用一次”如果某个状态下忘记调用上升沿就检测不到了。如果你觉得麻烦用我上面那种手动锁存方式反而更可控。6. 状态机里的防呆设计与安全互锁6.1 气缸动作的安全互锁写状态机代码的时候很多人容易陷入一个思维误区觉得状态跳转正常了设备就能正常运行。但现场设备不是这么回事机械设备有惯性、有卡滞、有传感器失灵这些意外情况都需要在程序里考虑。最典型的是气缸互锁。还拿自动送料机构举例气缸A夹紧和气缸B推出这两个动作在物理上不允许同时进行否则会撞坏工件或者损坏机械结构。在状态机里S_CLAMP_CYL_A状态下B输出是FALSES_PUSH_CYL_B状态下A输出是TRUE看起来正常流程中不会出现“B伸出时A没有夹紧”的情况。但代码逻辑是代码逻辑物理世界是物理世界。万一出现程序跑飞、状态变量被HMI写入异常值、或者中间继电器卡死电磁阀可能会出问题。所以我在实际项目里除了状态机逻辑还会在输出控制的最末端加一道“硬互锁”逻辑。在Sysmac Studio的梯形图里加一个互锁网络或者在ST程序最后加一行// 最终输出控制状态机逻辑之外的安全互锁 bCylinderA_Out_Final : bCylinderA_Out AND NOT bCylinderB_Out; bCylinderB_Out_Final : bCylinderB_Out AND NOT bCylinderA_Out;然后把物理输出映射到bCylinderA_Out_Final而不是直接映射bCylinderA_Out。这算是一道非常可靠的软件防线即使状态机内部逻辑出现任何异常两个气缸的电磁阀也不可能同时得电。更进一步我建议在电气回路里也做硬互锁通过中间继电器常闭触点串联。软件互锁和电气互锁双保险才是设备防护的正解。软件永远只是最后一道防线而不是唯一防线。6.2 传感器异常与超时保护状态机里经常出现的情况是跳转条件一直不满足。比如气缸A已经伸出了但伸出到位传感器一直没有信号。这时候如果程序只是傻傻地等设备就会停在S_CLAMP_CYL_A状态不走了如果操作员没有盯着屏幕可能半天都发现不了。所以每一个需要等待传感器信号的状态都应该加上超时保护。超时保护的做法不复杂就是在进入状态的第一个扫描周期启动一个定时器或者开始计数如果超过设定时间比如5秒说明该动作没有在预期时间内完成设备应该进入报警或停止状态。在Sysmac Studio里实现超时保护我的做法是在每个需要等待的状态里用类似下面的逻辑S_CLAMP_CYL_A: bCylinderA_Out : TRUE; bCylinderB_Out : FALSE; // 进入本状态后开始计时 IF stateFeedPrev S_CLAMP_CYL_A THEN iTimeCount : 0; ELSE iTimeCount : iTimeCount 1; END_IF; // 正常跳转条件 IF bCylinderA_OutDetect THEN stateFeed : S_PUSH_CYL_B; // 超时保护默认任务周期1ms5000个周期5秒 ELSIF iTimeCount 5000 THEN stateFeed : S_ALARM; bAlarmCode : 16#A001; // 气缸A伸出超时 END_IF;这种写法用到了stateFeedPrev上一个周期的状态值来判断“是否刚刚进入本状态”如果刚进入计数清零否则累加。Sysmac Studio的ST里没有内置的这个变量需要自己在程序开头保存一下stateFeedPrev : stateFeed;需要说明的是这里的iTimeCount的累加依赖于状态机每次扫描周期都执行到S_CLAMP_CYL_A分支。如果有一个更高优先级的任务把主程序抢占了或者主任务本身发生了延迟那么iTimeCount累加的实际时间会大于理论时间。对于气缸动作超时这种秒级判断这个误差可以接受。如果你需要非常精确的超时判断建议用操作系统的时间函数或者硬件定时器来做。6.3 急停与报警状态的处理很多工程师写状态机会把“急停”当成一个普通的输入信号来处理这在逻辑上是行不通的。急停不是输入它是最优先级的“停机命令”它的优先级高于一切状态包括自动、手动、回原点。任何状态下按下急停所有输出必须立即失电设备必须马上停止运动。这不是状态机逻辑能解决的问题它需要的是硬件安全回路。在实际项目中急停按钮应该接在安全继电器回路里物理断开设备的主电源或伺服使能同时在PLC里也接入急停信号的常闭触点用于程序逻辑感知急停状态。在状态机程序里急停信号作为最高优先级条件必须在CASE语句之前做判断// 急停处理优先级高于状态机 IF NOT bEStopOK THEN // 急停触发所有输出立即复位 bConveyorRun : FALSE; bCylinderA_Out : FALSE; bCylinderB_Out : FALSE; // 状态机回到一个特殊的急停状态等待复位 stateFeed : S_ESTOP; END_IF;注意这里的bEStopOK是急停未触发时的一个软信号可以取反急停输入。在状态机外部先做急停判断如果急停触发直接把所有输出复位并进入S_ESTOP状态。在S_ESTOP状态下除了急停复位信号之外不接受任何其他跳转条件。这个设计的思路是急停触发后即使操作员按了启动按钮设备也不会自动重新运行必须先完成急停复位和故障清除然后回到待机状态再重新启动。这样做的好处是防止“急停复位后设备立即自行启动”这种致命危险。很多设备的安全生产规范里明确要求急停复位后必须人工重新按下启动按钮才能恢复自动运行。这个逻辑用状态机来实现非常自然但是用梯形图的M继电器方式实现就很容易出现漏考虑的情况。7. 调试工具与常见问题排查7.1 Sysmac Studio的数据监视与在线修改程序写完之后接下来是调试阶段。Sysmac Studio提供了一套比较完整的调试工具。最常用的是“数据监视窗口”可以在线监控所有全局变量、局部变量的实时值。调试状态机的时候可以直接把stateFeed这个枚举变量拖到监视窗口在线看到它当前的值。在Sysmac Studio里枚举类型在监视窗口会直接显示符号名比如S_RUN_CONVEYOR这对于调试来说非常友好不用自己对着编号猜状态。除了普通的监视Sysmac Studio还支持“程序在线修改”和“变更程序运行”。也就是说程序下载到PLC运行之后你可以随时在开发环境里改ST代码修改完成后选择“变更程序运行”而不是“传送并运行”PLC不会停机新的代码会立即生效。这个功能在调状态机时太有用了每次改完跳转条件不需要重新下载程序、不需要复位PLC点一下“变更程序运行”就好了。不过要注意“变更程序运行”不是所有的改动都支持比如新增变量、修改任务设置这些还是需要完整传送。另外频繁在线修改代码会把CPU的存储区重新分配如果项目很大改得很频繁可能会遇到内存碎片的情况这时候做一次全量下载就能解决。7.2 常见问题速查表下面的表格是我在状态机调试中遇到过的典型问题、原因和解决办法整理出来供参考问题现象最可能的原因排查与解决办法某个状态一直不跳转跳转条件中的传感器信号没有到位或信号极性反了用数据监视窗口查看对应的BOOL变量确认物理信号已进入PLC状态机跳转但输出没有变化输出可能在状态机之外被其他程序段覆盖了检查输出变量是否有多个程序段在赋值这是最常见的冲突原因状态从正常值变成非法值有通讯写入或HMI写入覆盖了状态变量在HMI/上位机程序里禁止对状态变量写入或者加密保护动作执行一两步就停了且状态很正常跳转条件中某一步的传感器信号是瞬时脉冲在下一个扫描周期消失用信号锁存或沿检测来处理瞬时信号看门狗超时报警状态机里写了等待循环如WHILE/等待占用了太长的扫描时间不要用等待循环改用状态停留定时器/计数的模式TON定时器在状态跳走后还在计时离开状态之前没有把TON的In信号置FALSE检查所有使用TON的状态确保离开前切断In信号气缸在状态跳跃时出现瞬间误动作状态中未显式给所有输出赋默认值导致输出保持上一个状态的值每个状态都要对每个输出统一赋值不要只赋值变化的量枚举变量在监视窗口不显示符号名监视窗口变量类型可能被强制转换成了整数删除监视项重新添加或检查变量类型是否正确引用了该枚举类型7.3 利用Sysmac Studio的“程序联锁”与仿真调试Sysmac Studio还有一个非常有用的功能模拟器。在没有实物PLC的情况下可以在软件里启动模拟运行PC会模拟PLC的指令执行你可以用模拟器完成大部分状态机的逻辑验证。我自己经常在写状态机的时候先在模拟器上把跳转条件用变量强制Force的方式模拟一遍比如强制bWorkpieceDetect为TRUE看状态机是否正确从S_RUN_CONVEYOR跳到S_CLAMP_CYL_A。这样在去现场之前就能消灭大部分逻辑Bug到了现场只需要调试传感器信号和机械动作的配合效率提升非常明显。模拟器有一点要注意它不能模拟物理I/O的响应时间也不能模拟通讯总线比如EtherCAT的真实刷新周期。所以涉及运动控制的程序在模拟器上运行的结果只能作为逻辑参考不能作为最终性能依据。但是单就状态机逻辑来说模拟器已经足够强大。调试状态机时还有一个实用技巧把状态跳转的历史记录下来。Sysmac Studio本身没有内置的“状态历史记录”功能但你可以用一个数组变量作为环形缓冲区每次状态变化时把当前状态值和变化时刻存入数组。这样设备出现故障的时候打开数据监视窗口看看这个数组就能复盘出设备在故障前经历了哪些状态、每一步停留了多久。这是排查偶发故障的神器。再分享一个小细节。Sysmac Studio的“变更程序运行”在状态机调试中非常快但是每次修改之后最好做一次“程序检查”看有没有改了跳转条件导致变量未使用的警告。我遇到过好几次删掉了一个传感器变量的引用但是忘了删除全局变量表里的定义结果程序下载到现场PLC之后那个变量一直显示为TRUE导致状态机逻辑异常。这种低级错误浪费了很多时间现在每次修改完都会先检查一遍变量引用。8. 从Demo到工程化状态机写法的演进心法写到这里自动送料的状态机Demo已经完整了。但我想再多说一点自己的心得体会因为光会写一个Demo和能在工程中灵活使用状态机之间还是有一段距离的。第一个体会是状态机的难点不在写代码而在分状态。什么是分状态就是你能不能把设备的整个动作流程拆成一个一个互斥的、边界清晰的稳定状态。拆得好代码自然顺滑拆得不好就容易出现两个状态之间的边界模糊或者一个状态需要等两个几乎同时到达的信号逻辑就开始别扭。拆状态有一个行之有效的技巧不要按“动作”去分状态要按“稳定条件”去分状态。比如“气缸A正在伸出”不是一个状态因为气缸伸出的过程是不稳定的你应该拆成“气缸A缩回在原位等待伸出指令”和“气缸A已伸出到位保持夹紧”这两个状态。以稳定条件为状态设备的每个阶段都有明确的物理含义跳转条件也会变得很清晰。第二个体会是状态机的输出赋值要追求“完全确定”。每一个状态对每一个输出变量都要有确定的值不能有“这个输出在这个状态下保持原样”的模糊地带。你可能会问有些状态不需要改变某个输出那直接不赋值不就行了吗是可以不赋值但我不建议这么做。原因前面也提到了状态机在运行过程中状态是不断切换的如果某个输出只在部分状态里赋值在另一些状态里不赋值那它的值就取决于“上一个赋了这个输出的状态”这种隐式依赖是Bug的温床。所以我的习惯是每个状态的CASE分支第一件事就是把所有输出先按该状态的默认值全部赋值一遍再写需要变动的那些。代码会变得冗长一些但可读性和健壮性提升不止一个档次。第三个体会是状态机的扩展性远好于梯形图。传统梯形图程序如果要增加一个新的工作模式往往需要在原来的置位复位逻辑里小心翼翼地加触点。而状态机天然支持“加状态”的扩展模式你只需要在枚举里加一个新的状态在CASE里加一个分支再把跳转条件接上原来的代码完全不用动。这一点对我们做非标设备的来说非常重要因为客户改需求是家常便饭状态机结构让改需求不再是一场噩梦。第四个体会也是最后一个不要为了用状态机而用状态机。状态机适合逻辑复杂、状态多的场景但如果你就是一个水泵启停启保停三句话的事硬套状态机反而是脱裤子放屁。工具是为人服务的不要被方法论绑架。我的判断标准很简单如果设备有超过两三个连续动作需要按顺序执行或者有自动/手动/报警多种模式需要切换那状态机就是值得的如果只是简单的泵阀控制按常规方式写就好。Sysmac Studio本身提供了非常强大的IEC结构化编程环境用状态机这种思想来组织的程序无论是可读性、可维护性还是可扩展性都远胜于传统的梯形图堆继电器。如果你以前没用过这种方式建议从一个小的Demo开始练手把状态划分、CASE结构、超时保护、信号锁存这些基本功练扎实再慢慢过渡到多状态、多模式、嵌套状态机的复杂项目。我在第一次用Sysmac Studio写状态机的时候其实写了改、改了删折腾了不少时间。但现在回头看那些折腾都很值得因为用状态机写出来的程序一次调试通过率真的高很多现场排查问题也快很多。这大概就是结构化思维带来的红利吧。
返回列表