ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PLC状态机编程实战:用Sysmac Studio告别梯形图困扰

PLC状态机编程实战:用Sysmac Studio告别梯形图困扰 1. 为什么我劝你别再用梯形图硬啃复杂流程做了几年PLC项目我越来越觉得大多数工程师都卡在同一个坎上程序写到最后梯形图堆得自己都看不下去了。尤其是欧姆龙NJ/NX系列这种支持ST、支持结构化编程的控制器如果你还用老一套思维在那里画触点线圈其实是在拿自己的项目周期和脑细胞去做代价。我接手过一个分拣线项目十几个工位协同每个工位内部还有四五个动作阶段加上异常复位、暂停续跑、急停恢复。用梯形图硬撸了一个月截图给朋友看他回了句这玩意儿连原作者都维护不了吧。确实逻辑变量互相缠绕互锁条件堆成山改一个工位的时序另外三个工位跟着出bug。那段时间我每天都在翻自己两周前写的程序问我当时的自己这个M100到底是干嘛的后来我把整个控制逻辑推倒重来改用状态机。同样一套动作流程代码量没减少多少但结构清晰到我自己都感动。每个工位就是一个独立的状态机什么时候该干什么、什么条件下允许切换全部收敛在各自的CASE语句里。调试的时候看当前状态值配合Sysmac Studio的在线监控一秒钟定位问题出在哪个环节。这不是什么高深理论就是嵌入式软件里再普通不过的编程范式只是PLC圈子里用的人还不够多。写这篇文章我想把欧姆龙PLC上用Sysmac Studio实现状态机的完整思路、代码结构、踩坑经历全部摊开来讲。适合正在做复杂流程控制、被梯形图逻辑折磨得够呛、又对ST语言和结构化编程不太熟悉的工程师。内容不涉及高大上的算法只有能直接抄作业的写法。2. 状态机到底解决了梯形图的什么问题2.1 梯形图的本质缺陷是全图同时扫描要理解状态机的价值得先明白梯形图为什么容易失控。PLC的扫描机制是自顶向下、从左到右把所有网络全部执行一遍然后循环。这本身没问题问题在于程序跑完一整轮之后所有输出、中间变量都已经根据这一轮的输入刷新了一遍下一轮又从头开始执行。如果你的流程控制是先做A再做B再做C你在梯形图里写出来的实际上是满足条件就做A、满足条件就做B、满足条件就做C的并联关系。打个比方梯形图就像一个大开间每个工位、每个动作、每个互锁条件全在一间屋子里谁都能看到谁谁都能干扰谁。程序简单的时候无所谓一旦动作流程超过二十步各种顺序互锁、并行分支、异常复位搅在一起这条线就看不见了——因为你根本没法确定在某一轮扫描里到底哪个网络先执行、哪个中间变量先被改写、会不会同一个周期里既满足了步进切换条件又执行了下个动作。我见过最典型的灾难现场是设备急停复位后气缸该伸的没伸该缩的缩到一半因为复位逻辑和运行逻辑在同一个扫描周期里互相打架。最后查出来是两个网络用了同一个中间变量一个置位一个复位执行顺序刚好反了。2.2 状态机用当前状态把流程变成了一条单行道状态机的核心就一句话在任何时刻程序只执行当前状态对应的那一段逻辑。其他状态的代码就算条件全部满足也不会执行。这种互斥性从机制上保证了流程不会乱。具体到欧姆龙PLC的实现方式就是用ST语言写CASE语句。CASE变量是当前状态值每个状态一个分支分支里写这个状态下要做的动作和跳转条件。扫描周期照常一轮一轮跑但每一轮只进一个分支逻辑上天然就是串行的。还拿分拣线举例。一个上料工位的动作流程是等待启动 - 夹紧工件 - 提升到位 - 横移右行 - 下降放料 - 松开退回 - 回原点。用状态机表示就是7个状态每个状态里写动作输出和切换条件。任何时刻你在HMI上看到的状态值就能直接判断设备正在干哪一步而不是面对几十个中间变量猜这一步该不该动。这就是状态机收敛复杂度的根本逻辑它把全局并发降维成了局部串行。你不需要再关心其他状态在干什么只需要盯住当前状态这一个变量所有逻辑关系都围绕它展开。3. Sysmac Studio里准备一个状态机的标准姿势3.1 用枚举类型给状态起名字别用数字写死先做一个很重要的准备工作在Sysmac Studio里给状态定义成枚举类型。很多人习惯用整数直接代表状态比如0表示待机1表示运行2表示报警。程序里写CASE 1:、CASE 2:看代码的时候还得翻注释才知道1和2分别是什么。状态一多十个二十个状态堆在那里满屏的数字过俩月自己都看不明白。Sysmac Studio的ST语言支持枚举类型定义。在全局变量表或者数据结构里定义TYPE E_STATE : ( ST_IDLE : 0, // 待机 ST_HOME : 10, // 回原点 ST_CLAMP : 20, // 夹紧 ST_LIFT_UP : 30, // 提升 ST_MOVE_R : 40, // 横移右行 ST_DOWN : 50, // 下降 ST_UNCLAMP : 60, // 松开退回 ST_RETURN : 70, // 返回原点 ST_ALARM : 990 // 报警 ) E_STATE; END_TYPE这里的编号我习惯用10的倍数跳着留增量方便以后在中间插入新状态不用改其他状态的编号。枚举变量的声明progState : E_STATE;有了枚举你写CASE的时候就是CASE progState OF E_STATE.ST_IDLE: ... E_STATE.ST_CLAMP: ... END_CASE;Sysmac Studio会自动把枚举显示成名字而不是数字在线监控的时候直接看程序状态是ST_CLAMP还是ST_IDLE不用查对照表。这个习惯越早养成越好我见过太多人因为省这几分钟定义枚举的时间后面每次调试都得把数字背一遍。3.2 程序组织方式状态机跑在任务里传感器和执行器单独封装状态机的魅力在于和外部IO解耦。不要在CASE语句里直接写IN_ClampSensor这种物理输入点的读取而是把IO状态先映射到内部变量再把执行器输出封装成动作函数。这样状态机只关心逻辑上夹紧是否到位不关心实际物理点在哪个通道。具体到Sysmac Studio的程序组织结构我是这么分的设备抽象层IO映射每个程序块里把物理输入点赋值给内部变量物理输出点由内部变量驱动。这样换IO点的时候只需要改这一个地方。状态机层逻辑控制每个工位或者每个控制对象一个状态机FB内部只使用内部变量不直接接触物理IO。HMI/通信层把状态值、故障代码推到全局变量供触摸屏和上位机读取。分层的意义在于状态机代码可以原样复制到其他项目里复用。只要底层变量的命名够规范换一台设备换一套IO状态逻辑一行都不用改。3.3 任务周期和状态扫描别让状态机跳步NJ/NX系列的CPU支持多任务的配置状态机逻辑建议放在固定周期的周期性任务里。这个周期决定了状态切换的时间精度。一般我用2ms到10ms具体看设备工艺要求。太快没必要太慢了动作衔接会卡顿。Sysmac Studio里新建任务的时候把程序块添加到任务下同时设置任务周期。注意如果任务周期设置得过短而逻辑运算量比较大可能造成任务超时报警。状态机这种CASE分支结构运算量不算大但我见过有人把整个项目的梯形图全部塞进1ms任务里结果PLC的扫描周期跑不完直接报任务超时。还有一个容易被忽略的点状态切换条件的上升沿处理。在梯形图里习惯用微分指令DIFU/DIFD在ST里可以用上一周期状态值做判断实现上升沿效果// 上升沿检测上一次不是某状态本次是某状态 EdgeDetect : (progState E_STATE.ST_IDLE) AND (NOT prevStateIsIdle); prevStateIsIdle : (progState E_STATE.ST_IDLE);这种写法在状态机里很常用特别是那种按下按钮之后只触发一次的场景能避免因为扫描周期的原因导致同一条件在一个状态里反复触发。4. 把状态机翻译成ST代码一个气缸工位的完整示例4.1 需求说明与状态划分用一个最典型的单气缸加紧工位来演示完整写法。动作需求待机状态气缸缩回等待启动按钮。夹紧启动后输出夹紧电磁阀伸出气缸。等待位置到位检测到伸出到位传感器后保持夹紧3秒。松开3秒后缩回气缸。缩回到位检测到缩回到位传感器后回到待机。要求任何时候急停信号触发立即切断所有输出并进入急停状态急停复位后回到待机。按这个需求状态可以这么分ST_IDLE待机。ST_CLAMP夹紧伸出等伸出到位信号。ST_HOLD到位保压计时。ST_RELEASE松开缩回等缩回到位信号。ST_E_STOP急停状态。4.2 程序块代码逐段拆解下面这整段就是Sysmac Studio的ST程序块代码。我按功能拆成几块来讲。先看状态机的核心部分用一个CASE语句包住全部状态逻辑CASE progState OF E_STATE.ST_IDLE: // 待机状态气缸缩回输出断开等启动信号 CylinderOut : FALSE; TimerRunning : FALSE; IF StartButton THEN progState : E_STATE.ST_CLAMP; END_IF; E_STATE.ST_CLAMP: // 夹紧输出电磁阀 CylinderOut : TRUE; IF ClampWorkSensor THEN // 到位后开始计时 HoldTimer(IN : FALSE); // 先复位定时器 progState : E_STATE.ST_HOLD; END_IF; E_STATE.ST_HOLD: // 保持夹紧3秒 CylinderOut : TRUE; HoldTimer(IN : TRUE, PT : T#3S); IF HoldTimer.Q THEN HoldTimer(IN : FALSE); progState : E_STATE.ST_RELEASE; END_IF; E_STATE.ST_RELEASE: // 松开 CylinderOut : FALSE; IF NOT ClampWorkSensor THEN progState : E_STATE.ST_IDLE; END_IF; E_STATE.ST_E_STOP: // 急停输出全断 CylinderOut : FALSE; IF NOT EStopSignal THEN progState : E_STATE.ST_IDLE; END_IF; END_CASE;这段代码看着简单但里面有几个细节值得展开讲。4.3 定时器在状态机里的正确用法ST_HOLD 状态里的定时器用法是我踩过坑之后总结出来的。一开始我直接在进入ST_HOLD的时候启动定时器写的是HoldTimer(IN : TRUE, PT : T#3S);但问题是状态机每一轮扫描都会执行这个函数块调用只要还在ST_HOLD状态IN始终是TRUE。3秒到了之后由于程序每个周期还在继续调用定时器Q会一直保持TRUE状态切换是能触发但如果你需要定时器只触发一次就会出问题。更麻烦的是如果你从ST_HOLD跳出去之后另一个地方也用了同一个定时器定时器的状态没复位可能直接导致下一次进ST_HOLD时Q还带着上次的残留。正确做法是先复位再启动。我上面的代码就是先执行一次HoldTimer(IN : FALSE)把定时器内部的计时状态清零然后把IN置TRUE重新计时。这个先复位再置位的顺序很关键少一步都会出莫名其妙的问题。Sysmac Studio的定时器是TON功能块调用格式是// TON实例化之后用结构体变量调用 HoldTimer(IN : condition, PT : T#3S); // 读取输出 IF HoldTimer.Q THEN ...注意TON的Q输出在IN断开时是会被清零的所以如果你在ST_HOLD里直接把CylinderOut设为TRUE同时IN也一直是TRUEQ保持3秒后Q置位这时切换到ST_RELEASEIN条件不再满足Q在下个周期清零。但如果切换不是发生在Q置位的同一个周期可能出现Q已经置位但没被读取到的情况——所以最好的方式还是复位再启动两步走。4.4 传感器输入要先做映射别在CASE里直接读前面提过IO映射的问题这里演示一下。在程序块的开头我先做了输入映射// IO映射物理输入点映射到内部变量 StartButton : IO_Start; EStopSignal : IO_EStop; ClampWorkSensor : IO_ClampWork; ClampBackSensor : IO_ClampBack;然后状态机里只用内部变量。好处在调试的时候尤其明显如果你在Sysmac Studio的在线监控里找不到物理输入点哪里异常可以直接强制内部变量来模拟信号不需要真的去短接传感器。有一点要注意内部变量和IO变量最好用不同的命名前缀区分。我习惯用IO_开头表示物理点映射用普通驼峰命名表示内部逻辑变量。这样一眼扫过去就知道哪个是物理点、哪个是逻辑中间量。4.5 多步动作流程里怎么加并行分支上面那个例子比较简单我再扩充一下如果夹紧之后还要推一个气缸上升两个动作并行执行该怎么处理状态机的处理方式不是把两个动作放在同一个状态里来回纠结而是把并行动作拆成一个状态里同时输出多个动作。假设动作是夹紧的同时提升那状态设计就是E_STATE.ST_CLAMP_AND_LIFT: CylinderOut : TRUE; LiftOut : TRUE; IF ClampWorkSensor AND LiftUpSensor THEN progState : E_STATE.ST_NEXT; END_IF;这表示在一个状态里同时输出两个执行器两个都到位才跳下一个状态。如果两个动作的到位时间差异很大比如夹紧0.5秒到位提升2秒到位那就要考虑拆成两个状态分开等否则夹紧到位之后还得干等着提升效率低。状态机的好处是这种调整只需要增删状态不会影响其他逻辑。5. 状态机的调试心得和那些让你怀疑人生的Bug5.1 用Sysmac Studio的在线监控当行车记录仪状态机代码写完之后调试方式和梯形图很不一样。梯形图调试是看哪个网络通了没通、哪个触点亮了没亮状态机调试只需要盯一个核心变量——progState。Sysmac Studio里可以往监控窗口添加变量把progState和关键传感器的内部变量放一起。我最常用的调试手法是把设备手动打到半自动模式然后一步步触发动作看progState跟着哪个状态走、卡在哪个状态不动。卡住的根本原因只有两个可能要么当前状态的跳转条件不满足要么跳转目标状态里的动作有问题。前者查传感器和定时器后者查输出逻辑和IO映射。配合Sysmac Studio的程序在线编辑功能调整几行ST代码然后重新传输不用整个程序停止效率极高。这里要提醒Sysmac Studio在线编辑之后务必做一些常规的变量一致性检查尤其是函数块实例名、枚举类型引用这些。有一次我改了一行状态切换条件编译通过了结果下载的时候报程序与全局变量不一致排查半天发现是枚举类型里加了一个新成员导致之前用数字初始化的地方出了偏差。这东西编译期不会提醒你但运行时会给你脸色看。5.2 状态卡死和状态乱跳的排查链路很多刚接触状态机的人会碰到两个经典问题状态卡住不动或者状态跳到一个完全意想不到的地方去了。状态卡住的排查链路我一般按这个顺序来先看当前progState停在哪。看这个状态里需要的跳转条件对应的变量在线监控它们的值。常见情况是传感器没到位或者定时器Q没置起来。检查传感器信号是不是抖动。有些传感器在机械震动下会闪断闪通导致状态刚跳过去又跳回来。这时候要么加延时滤波要么把状态切换条件改成持续多少毫秒才认。状态乱跳的排查链路稍微复杂一点常见原因有两个一是多个CASE分支同时满足条件。这在CASE语句里不应该发生因为每个状态只执行自己的分支。但如果你的CASE分支里有个嵌套的IF条件写得过于宽泛比如IF Input1 OR Input2 THEN而Input1在状态A里被置位了、在状态B里没被复位就可能出现跳到你意想不到的状态的假象。排查办法是给每个状态加一个进入时的前置校验条件不满足就不跳。二是状态变量类型不匹配。我踩过一次很离谱的坑我给progState定义的枚举类型但某个assign语句把一个INT类型的临时变量直接给progState赋值编译的时候Sysmac Studio居然没拦住因为某些模式下允许隐式转换运行的时候progState变成了一个不在枚举范围内的值CASE语句没有对应分支整个状态机静默停止输出。从那以后我所有状态切换都严格用枚举赋值不再用裸数字。5.3 模拟调试别等到设备上才第一次跑逻辑Sysmac Studio自带仿真器Simulator支持在没有硬件的情况下运行程序、监控变量。强烈建议状态机逻辑写完之后先用模拟仿真在电脑上把动作流程走一遍。操作方式很简单把PLC设为离线模式然后在工具栏点模拟程序就会被仿真器执行你可以在监控窗口里强制输入信号观察状态切换是否正常。模拟调试最大的价值是可以在2分钟内测完一整条流程不需要等气缸真的动。把所有传感器信号按照时序强制置位/复位看看每个状态切换是否严格按照预期走。如果发现某个状态跳不过去直接改代码重新跑。等仿真跑通、逻辑确认无误再上真机调试你会发现调试时间能压缩到原来的三分之一。6. 状态机写法的进阶技巧从会用到用得舒服6.1 用状态转换表管理复杂流程状态一多比如超过15个状态光靠看CASE代码已经不太直观了。这时候我习惯先画一张状态转换表不用画图软件Excel或者纸上画都行。表格的行是当前状态列是目标状态交叉格写的是触发条件。例如当前状态目标状态触发条件ST_IDLEST_CLAMP启动按钮TRUEST_CLAMPST_HOLD伸出到位传感器TRUEST_HOLDST_RELEASE定时器QTRUEST_RELEASEST_IDLE缩回到位传感器TRUE任意状态ST_E_STOP急停信号TRUEST_E_STOPST_IDLE急停信号FALSE这张表拿去对着写CASE代码效率和准确率都高得多。我一般把这张表做成项目的设计文档放在程序注释的开头或者是单独的说明文件里。半年后你或者同事回头维护这段代码的时候先看转换表再读CASE理解成本直线下降。6.2 全局状态字与HMI显示状态机内部用枚举但给HMI显示的时候通常需要输出一个整数值。这个很简单直接在程序末尾做一个映射// HMI显示专用状态值 StateToHMI : progState;HMI那边拿到的就是枚举的数值。配合触摸屏的文本显示功能可以给每个数值配置一个文本标签比如0显示待机、10显示回原点、20显示夹紧。这样操作员在触摸屏上看到的就是中文状态名排查设备卡在哪一步的时候不用打开电脑看程序。在Sysmac Studio里也可以把progState直接关联到变量标签HMI数据映射的配置非常灵活。6.3 超时报警状态机必备的自保措施状态机最容易出的事故是机械卡住导致传感器永远等不到信号状态卡死在半中间设备不报警也不动看起来像死机。解决方法是给每个状态都加一个超时监控——在一个状态停留超过设定时间强制进入报警状态。实现方式是在状态机开头统一处理// 每个扫描周期累积超时时间 IF progState prevProgState THEN StateTimer(IN : FALSE); prevProgState : progState; ELSE StateTimer(IN : TRUE, PT : TimeOutValue); IF StateTimer.Q THEN progState : E_STATE.ST_ALARM; AlarmCode : 100; // 状态超时报警 END_IF; END_IF;不要小看这段代码。有了超时保护状态机在异常情况下能自动进入报警状态操作员在HMI上看到报警信息后再去处理。如果没有这层保护状态卡死可能直接导致动作中断、产品报废甚至设备损坏。6.4 子状态机嵌套大型项目如何避免状态爆炸如果整个系统的流程非常长比如有50个状态铺在一个CASE里可读性依然会崩。我的做法是拆分主状态机和子状态机。主状态机管大阶段比如初始化、运行、异常子状态机管每个大阶段内部的细分步骤。在Sysmac Studio里我可以定义两个状态枚举类型两个状态变量主状态机的CASE里调用子状态机的处理函数。举例主状态机的运行阶段对应子状态机里的夹紧、提升、横移、下降等步骤。主状态机判断运行阶段是否结束子状态机判断自己内部的步骤怎么走。两层状态机互相独立每一层都不会超过15个状态整个项目的可维护性比单层50个状态好得多。嵌套状态机的原则是子状态机只负责自己的动作细节不反向影响主状态机的宏观流程主状态机只关心阶段切换不关心子状态机内部每一步在干什么。两者之间通过阶段完成标志来交接。这样设计出来的程序别人接手的时候只要看主状态机就能知道整体流程真要细看某个阶段再钻到子状态机里。7. 关于状态机写法我最想说的几条实战体会状态机不是银弹它不会让你的逻辑变少但会让逻辑变得可维护、可调试、可扩展。我在欧姆龙PLC上用这套写法做了几个项目之后明显感觉到自己写程序的安全感提升了。不再怕改需求改一个状态不影响其他状态不再怕现场调试打开Sysmac Studio看状态值就知道卡在哪也不再怕交接给同事看状态转换表比给他看一百行梯形图省力得多。如果你刚入门建议找一个简单的工位动作比如气缸伸缩、传送带启停用ST写一个10个状态以内的状态机在仿真器里跑通整个流程。先体会一下每轮扫描只执行一个逻辑分支这种思维方式。有了这个基础再去处理复杂的多工位协同你会有一种豁然开朗的感觉。Sysmac Studio的ST语言整体不算难有C语言或者高级语言基础的人基本可以无障碍上手。真正需要花时间的是思维方式的转变从条件满足就执行变成当前状态是什么就只执行这个状态下该做的事。一旦完成了这个转变你再看项目里的复杂流程控制基本都能用状态机这套打法稳稳吃下来。
返回列表