
做自动化项目的工程师应该都有过这种经历设备联调阶段程序里的M继电器和步进指令一多整个逻辑就像一团乱麻。我真正开始在欧姆龙PLC上用Sysmac Studio系统性写状态机是接手一个分拣设备项目之后。那台设备空跑正常但只要中途报警复位动作顺序立刻开始错乱——这边气缸还没到位那边的皮带已经转了调了三天全靠现场拿手去挡限位。后来我把整个自动流程改成了状态机写法用ST语言的CASE分支代替原来几十个M继电器的置位复位从状态划分、枚举定义到FB封装不仅把报警复位的问题解决了调试效率也提升了一大截。这篇文章就围绕“欧姆龙PLC状态机写法”这个话题结合Sysmac Studio环境把状态机怎么拆、ST代码怎么写、现场有哪些坑一次讲透。1. 为什么会在PLC里用上状态机1.1 一锅粥式的顺序逻辑有多痛很多老设备的程序是这么写的用一堆中间继电器M0.0、M0.1、M0.2表示流程步骤每一拍在梯形图里把某个M置位同时把上一个M复位输出线圈再串上这一排M的常开触点。前面二十步还能看清逻辑到五十步以上的时候梯形图纵向拉出一大串横向还有各种互锁、分支别说新来的同事看不下去就是写程序的人自己过两个月再打开也得先画半天逻辑还原图。这种写法的痛点主要集中在三个方面。第一步骤之间没有强互斥关系M0.5置位了不代表M0.4一定复位一旦某条支路漏写复位程序里就会同时存在两个“当前步骤”输出自然就乱套。第二报警复位逻辑极难做设备中途报警后现场工人一按复位程序不知道该回到哪一步只能做一个“全部复位回初始”然后把已经跑了一半的工件丢掉重来。第三排查问题要翻几十行梯形图在线监视时眼前一片绿根本分不清当前到底卡在哪一环。我接手分拣项目时原程序就是这种典型写法。约两百步的顺序控制用了六十多个M继电器报警复位按钮直接跳到第0步。第一次试机时只要气爪夹紧超时报警复位后旋转气缸就偶尔会带着工件转到一半卡住。1.2 状态机解决的核心问题状态机本质上是一个“有限状态自动机”系统在任何时刻只能处于一个状态每个状态中有明确要执行的动作只有满足迁移条件时才跳到下一个状态其余时刻状态保持不变。大理拆解一下就三个要素状态、事件、动作。在PLC里状态用一个变量表示事件就是传感器、按钮、定时器这些BOOL信号动作则是输出控制和内部数据的处理。把顺序逻辑改成状态机之后最大的好处是互斥性天然成立。CASE分支一个周期只会执行一个状态对应的程序段不可能出现两个步骤同时生效的情况。其次状态变量是唯一的判断依据不管从哪里跳进来只要看当前状态值就能知道设备走到哪了。再就是可恢复性好报警复位时把状态变量置回初始值即可整套逻辑从头再走不需要一个个去复位M继电器。调试的时候Watch窗口里盯着一个整数看比盯着一排M的通断要直观得多。1.3 状态机不是银弹先看场合状态机虽好但不能无脑全上。纯联锁逻辑比如安全门、急停和电机启停之间的互锁用梯形图直接写反而更简洁可靠这类逻辑没有阶段顺序硬套状态机属于自找麻烦。还有一种情况是设备同时存在多个相互独立的流程例如一条产线上多个工作站并行工作每个站有自己的动作节拍这种情况如果用单一状态机描述状态数会膨胀得非常夸张更合理的方案是每个工作站独立一个状态机之间只通过握手信号交互。所以我的习惯是有明确阶段划分、有动作顺序、需要中断和恢复的设备流程统统可以用状态机但安全相关的联锁、模式切换、手动操作链路保持在状态机之外独立编写。2. 在Sysmac Studio里搭状态机的三种路2.1 第一种ST加CASE最推荐欧姆龙NJ/NX系列PLC支持完整的IEC 61131-3语言体系Sysmac Studio里可以直接建ST程序。ST语言写状态机的核心就是CASE语句后面跟一个状态变量每个分支处理一个状态的持续动作和迁移条件。这种写法文本化程度高跟Git这类版本管理工具配合非常好两个人改同一个程序合并的时候比梯形图容易得多。ST的另一个好处是可以用枚举类型。定义一组状态名称程序里写的是STATE_IDLE、STATE_RUNNING这种名字而不是裸的0、1、2、3可读性完全不一样。Sysmac Studio的ST编译器对枚举的支持也比较友好CASE直接拿枚举做选择器没问题。2.2 第二种SFC图天生就是可视化状态机Sysmac Studio也支持SFCSequential Function Chart这是IEC 61131-3里专门做顺序控制的语言从视觉上看就是一系列步骤方块加上迁线箭头每一步可以绑定转移条件和动作块。SFC的优点是直观老板和机械工程师开会时把SFC图投到屏幕上谁都能一眼看懂设备动作流程。但SFC也有不好受的地方。分支一多图上的连线就开始绕改一个步骤的位置要拖动一堆对象在线修改逻辑不如文本方便而且状态数量一多上下层细节来回切换反而没有ST一个文件拉到底来得痛快。我见过有同事在NJ上用SFC写了一个三百多步的流程后期光看图找线就找得头晕。所以我个人更倾向于ST写状态机SFC留给那些流程相对固定、需要频繁跟非程序人员沟通的场景。2.3 第三种梯形图加步进辅助老设备的过渡方案有些老工程师对梯形图有天然的习惯又想把逻辑改成状态机的思路。这种情况下可以用一个INT变量作为步进号在梯形图里通过比较触点判断当前步用置位复位指令来切换步进。本质上这跟M继电器的思路类似只是把所有步骤的编号统一到一个变量上至少规避了“多个步骤同时为1”的问题。这种写法在改造老项目时是可行的过渡方案但分支一多梯形图的物理尺寸还是会被撑得很大。我的建议是新项目、新平台直接上ST老设备改造实在没法迁移程序的可以用这种思路先整理逻辑后续再考虑移入ST环境。3. 实战分拣工作站状态机完整实现3.1 工艺动作拆解与状态划分我拿一个实际做过的简易分拣工作站举例。设备组成很简单一条送料皮带、一个双电控气爪、一个旋转气缸、两个光电传感器和一个启动按钮、一个复位按钮。工艺流程是上电复位各机构等待启动按下启动后皮带正转送料工件到达传感器后皮带停止气爪夹紧旋转气缸转90度到放料位放料位传感器确认到位后气爪松开旋转气缸返回原位回到待机状态等待下一次启动。这一步的状态划分很关键直接决定了后面代码的清晰度。我的习惯是先把工艺动作拆成一拍一拍的节点然后把“做了什么”和“等什么条件跳走”写清楚。这个分拣工作站可以拆成七个状态外加一个报警状态。状态名状态行为迁移条件STATE_INIT复位所有输出复位完成信号为真STATE_IDLE待机无输出启动按钮按下且复位完成STATE_CONVEY皮带正转送料工件到位传感器有信号STATE_CLAMP气爪夹紧夹紧到位传感器有信号STATE_ROTATE旋转缸转到放料位旋转到位传感器有信号STATE_RELEASE气爪松开松开到位传感器有信号STATE_RETURN旋转缸返回原位返回到位传感器有信号STATE_ALARM停机并清输出故障复位按钮按下状态划分的时候注意一点不要把两个动作塞进同一个状态里。比如“气爪夹紧并旋转”看起来是连续动作但实际调试时如果夹紧没到位就旋转机构会撞在一起所以一定要拆成两个状态让传感器校验到位之后才能往下走。3.2 枚举定义与全局变量规划在Sysmac Studio里枚举类型可以在“数据类型”里新建也可以直接在ST程序最前面用TYPE块定义。我用的是后者方便直接在POU里引用TYPE ENUM_MACHINE_STATE : ( STATE_INIT : 0, // 初始化 STATE_IDLE, // 待机 STATE_CONVEY, // 皮带送料 STATE_CLAMP, // 气爪夹紧 STATE_ROTATE, // 旋转至放料位 STATE_RELEASE, // 气爪松开 STATE_RETURN, // 旋转返回 STATE_ALARM // 报警 ) : STATE_INIT; END_TYPESysmac Studio的枚举默认从0开始编号我显式把STATE_INIT赋为0其余自动递增。这里有个事项枚举成员的顺序尽量不要在中途打乱否则迁移逻辑里的编号会跟着乱套。全局变量规划方面输入信号用PI前缀表示外接传感器输出信号用PO前缀表示执行机构状态变量用内部变量。我习惯把状态变量放在一个专门的状态机结构体里而不是单独散落一个全局变量这样多实例使用时能直接套结构体。3.3 主迁移逻辑的ST代码主逻辑放在一个ST格式的程序里整个程序体就是一个CASE语句。Sysmac Studio支持CASE的缩排和自动补全写起来比想象中顺畅。核心代码如下CASE #MachineState OF // 初始化复位 STATE_INIT: #PO_BeltRun : FALSE; #PO_Clamp : FALSE; #PO_Rotate : FALSE; #b_HomeDone : TRUE; #MachineState : STATE_IDLE; // 待机 STATE_IDLE: IF #b_Start AND #b_HomeDone THEN #MachineState : STATE_CONVEY; END_IF; // 皮带送料 STATE_CONVEY: #PO_BeltRun : TRUE; IF #PI_WorkpieceArrive THEN #PO_BeltRun : FALSE; #MachineState : STATE_CLAMP; END_IF; // 气爪夹紧 STATE_CLAMP: #PO_Clamp : TRUE; IF #PI_ClampDone THEN #MachineState : STATE_ROTATE; END_IF; // 旋转至放料位 STATE_ROTATE: #PO_Rotate : TRUE; IF #PI_RotateDone THEN #MachineState : STATE_RELEASE; END_IF; // 气爪松开 STATE_RELEASE: #PO_Clamp : FALSE; IF #PI_ClampOpen THEN #MachineState : STATE_RETURN; END_IF; // 旋转返回 STATE_RETURN: #PO_Rotate : FALSE; IF #PI_RotateHome THEN #MachineState : STATE_IDLE; END_IF; // 报警状态 STATE_ALARM: #PO_BeltRun : FALSE; #PO_Clamp : FALSE; #PO_Rotate : FALSE; IF #b_AlarmReset THEN #MachineState : STATE_INIT; END_IF; ELSE #MachineState : STATE_INIT; END_CASE;这里面有一个细节值得注意双电控阀的输出在状态跳走之前就要先复位。比如STATE_CONVEY里检测到工件到位后先执行#PO_BeltRun : FALSE再跳转到STATE_CLAMP。这样做的原因是双电控阀如果断电保持状态下一轮启动时阀门可能还在上一轮的位置设备动作就会错。所以双电控输出严格在退态前复位是状态机安全可靠的关键习惯。3.4 进入动作、持续动作和退出动作怎么分细看上面代码会发现有的输出在每个扫描周期都被刷新例如STATE_CLAMP里#PO_Clamp : TRUE每一圈都执行有的动作只在条件满足的那一刻执行一次例如STATE_CONVEY里的皮带停止。这种写法的本质是把“持续动作”和“退出动作”混合在同一个分支里好处是逻辑紧凑坏处是当状态数量大、动作复杂时容易分不清哪段是进入动作、哪段是退出动作。我后来在项目里越来越倾向于一种规范做法如果某个状态有明确的进入动作和退出动作就在迁移代码块里做标记用空行隔开再加上注释。比如STATE_CONVEY的退出段一定是先把皮带停了再迁走STATE_CLAMP的进入段一进来就置位夹紧。代码行数会多一点但半年后回来看依然能看懂当初的意图。Sysmac Studio的注释支持中文这块不要省状态机代码最怕的就是过段时间连自己都忘了某个状态为什么这样跳。3.5 初始化与复位怎么融入状态机状态机的初始化最好不好依赖上电瞬间的默认值。Sysmac Studio虽然没有传统“首次扫描位”但可以自己定义一个BOOL变量在程序的第一段做初始化逻辑IF NOT #b_InitDone THEN #MachineState : STATE_INIT; #b_InitDone : TRUE; END_IF;关键是报警复位后的处理。设备报警时状态机跳到STATE_ALARM所有输出全部切断。复位按钮按下后不要直接跳回故障前的状态——那样极容易造成二次事故。我的做法是复位后一律回STATE_INIT让设备从复位流程重新走一遍确认所有执行机构都处于初始位置再进入待机。有人会觉得这样浪费节拍但在大多数生产场景里安全比那几秒的节拍重要得多。特别是旋转气缸这类机构如果不回原位就接着干活会撞夹具、撞传感器甚至伤到人。这个原则我在所有状态机项目里都守着。4. 状态机写法的关键细节与避坑指南4.1 状态变量用INT还是枚举类型这个问题我纠结过一阵。早期写CUDA程序时习惯用数字因为索引方便到了PLC状态机我开始改用枚举最大的好处是代码可读性强。在Sysmac Studio里枚举在在线监视时可以显示成员名一看就知道设备在“待机”还是“夹紧”不需要去翻定义表。缺点是有时候枚举和外部设备通信时需要转换成INT比如HMI要显示当前状态或者上位机通过OPC UA读状态值枚举转整数需要额外代码。我的工程习惯是程序内部一律用枚举与HMI、上位机交互的接口用INT映射在状态机程序里维护一个#nCurStateForHMI : TO_INT(#MachineState);。这样既保持了代码可读性又方便外部系统读取。如果设备状态特别多、层级复杂我会把状态变量直接定义为INT再配合常量名但这种情况比较少见。4.2 为什么CASE分支的ELSE必须留状态机的CASE语句网上很多示例都只写了正常状态的分支ELSE被忽略了。但现场环境不是教科书。通信干扰、程序逻辑错误、手动模式下误操作都可能让状态变量跑到一个未定义的值。只要进了未定义的CASE分支状态机就等于“死机”了——输出全部停在上一拍的状态没有任何迁移条件能够把它拉回来唯一的解决办法就是断电重启。哪怕设备再简单我写的CASE语句一定会带上ELSEELSE #EmployeeErrorCode : 16#00FF; // 未知状态 #MachineState : STATE_INIT; END_CASE;这样即使状态真跳飞了也会被拉回到初始化流程而不是原地死掉。Sysmac Studio的ST编译器不会强制要求写ELSE但这一步绝不能省。4.3 定时器与超时看门狗怎么接光有传感器迁移条件还不够设备上传感器也有失灵、线松、信号被挡住的时候。如果一个状态进去之后传感器永远不来设备就会一直卡在那个状态表面上看起来是“停机”了实际上程序还活着只是死等。这种故障最隐蔽因为程序没有任何报警提示。处理办法是给每个状态加看门狗超时。Sysmac Studio的ST里可以直接用TON功能块比如送料状态最长允许5秒#TON_ConveyWatchdog( IN : (#MachineState STATE_CONVEY), PT : T#5S); IF #TON_ConveyWatchdog.Q THEN #nErrorCode : 16#0021; // 送料超时 #MachineState : STATE_ALARM; END_IF;这段看门狗逻辑可以放在CASE语句之外也可以放在状态分支内部。放在外部的优势是统一管理状态机一进某个状态对应的定时器就自动开始计时一旦状态跳走IN变成FALSE定时器自动复位。超时阈值建议每个状态单独配置不要所有状态共用一个大值否则“卡住”了不能及时被发现。4.4 状态跳变的瞬间输出抖动问题状态机写多了遇到的另一个典型问题就是输出抖动。举个例子设备在STATE_CONVEY时皮带一直在转状态跳转到STATE_CLAMP后如果STATE_CLAMP分支里没有对皮带输出做任何处理那皮带的输出会因为“上一拍一直置TRUE”而继续保持接通。这在某些输出模块上不会自动复位结果就是皮带在夹紧状态下继续送料。解决这个问题的思路有两个。第一坚持“退出动作先复位”原则在迁移条件满足时先复位相关输出再跳状态第二如果状态很多输出也很多可以在CASE外部做统一的输出分配逻辑即将所有输出的最终值放在状态机之后重新赋值一遍。第二种做法更干净但也更需要纪律否则会出现几个地方给同一个输出赋值互相打架。我个人的习惯是小规模状态机用第一种代码直白状态超过二十个、输出超过三十个的项目我用第二种把输出分配单独写一个程序段CASE里只做状态迁移不让输出赋值散落在各个分支里。这样即使某个状态写漏了输出外部统一的赋值逻辑也能兜底。5. 进阶把状态机封装成功能块FB5.1 为什么用FB再包一层当设备不止一个工位、比如一条装配线有四个相同的夹紧机构这时候如果每个工位都复制一遍状态机代码程序瞬间就膨胀四倍而且改一个逻辑要同步四处。Sysmac Studio支持功能块FB把状态机封装进FB之后每个工位只要建一个FB实例所有内部状态变量都隔离互不干扰。这是状态机在工程里真正落地的重要一步。FB还有一个好处可以把外部接口收窄。调用方不需要关心状态机内部有几层CASE只需要给几个BOOL输入、读几个输出就行。Sysmac Studio的FB可以通过ST或者梯形图调用在顶层程序里看起来非常干净。5.2 一个通用状态机FB的骨架FB的接口设计我一般这样规划输入变量bEnable总使能、bStart启动指令、bReset复位指令、bSensorA、bSensorB等传感器信号输出变量bBusy工作中、bDone完成、bError故障、nCurState当前状态INT内部变量MachineState枚举、TON定时器、初始化标志FB内部的ST代码结构和前面第三节的CASE基本一样只是部分输入输出改为引用FB的接口变量。在ST程序中调用FB时写法如下#Station1FB( bEnable : #b_Station1Enable, bStart : #b_StartButton, bSensorA : #PI_WorkpieceArrive_1, bBusy #b_Station1Busy, nCurState #n_Station1State );用FB封装后状态机的迁移日志也可以做成一个环形缓冲数组每次状态跳变时把旧状态、新状态、时间戳存进去。这在排查现场问题时是杀手锏——不用去猜设备刚才干了什么直接把记录拉出来看就行。Sysmac Studio的数组操作和FOR循环处理这块很方便值得一试。5.3 状态机与安全逻辑的关系状态机封装成FB之后有一条红线不能碰安全逻辑决不能塞进FB内部。急停、光栅、安全门这些信号必须在状态机外部用独立的逻辑直接切断输出回路而且优先级要高于一切状态机内的输出。Sysmac Studio处理EtherCAT安全通信时安全IO和控制IO是两个域安全功能要在安全程序里做不能用普通ST程序代替。我在设备上见识过把急停写在状态机一个分支里的做法结果是急停按下后设备并没有马上停要等当前状态走完才反应过来。这在自动化设备上是不可接受的。所以状态机FB只管自动流程安全逻辑永远独立哪怕增加硬件成本也不能图省事混在一起。6. 现场调试与常见问题排查实录6.1 状态卡住不动了怎么查状态机最常被问的问题就是设备启动后状态数值停在一个值上不走了。每次遇到这种情况我的排查步骤是固定的。先看当前状态值是多少确认卡在哪个状态然后打开Sysmac Studio的监视窗口拉出该状态的迁移条件对应的所有传感器和按钮信号逐个确认是否满足再看状态内的定时器是否在计时如果定时器早就到了说明超时保护逻辑没写或者超时后跳转路径不对最后看报警状态变量有没有被意外置位。现场最常见的卡住原因是传感器信号一直不满足。比如气爪夹紧到位传感器被工件碎屑挡住信号一直不通状态就永远停在STATE_CLAMP。这种问题跟状态机写法关系不大但有了状态机排查时间至少缩短一半因为你能直接定位到“夹紧没到位”而不是对着几十个M发愁。6.2 状态乱跳大概率是变量竞争状态卡住还好查状态乱跳就麻烦得多。有一次现场反映设备偶尔会跳过夹紧步骤直接旋转我先怀疑是传感器受干扰换了传感器还是复现不了。最后排查下来问题出在另一个程序段里为了让HMI显示某些逻辑我用SET指令给状态变量赋了一个无关的中间值跟状态机的CASE赋值打架了。这种“变量竞争”在旧程序里非常致命。Sysmac Studio的ST程序虽然允许同一个变量在多处赋值但状态变量这种核心数据最好只在状态机里赋值其他地方一律只读。如果确实需要在别的程序里干预状态比如手动模式要强制跳到某个状态也要通过专用的控制字加互锁实现不能直接改状态变量。Sysmac Studio的交叉引用功能可以很快找出所有给状态变量赋值的位置排查时一定要用起来。6.3 Sysmac Studio调试三件套调试状态机有三套工具我用得最多。第一是监视窗口把状态变量、传感器、输出变量拉到一个分组里在线监时一眼看到所有相关的通断状态比满屏的梯形图直观太多。第二是数据追踪Data Trace可以记录状态变量和关键传感器信号的时序波形尤其适合定位那种“偶发跳变”的问题把触发条件设好后等故障复现波形会清晰显示到底是哪个信号先变化导致了乱跳。第三是模拟器Sysmac Studio自带的模拟器可以在没有PLC硬件的情况下跑ST程序搭框架阶段用它验证状态迁移逻辑比反复下载到实体PLC里省太多时间。另外一个小技巧在所有状态迁移都加上日期时间戳记录状态跳变时往数组里写一条日志。设备出现怪问题时上位机或Sysmac Studio直接导出日志就能还原状态机每一步的时间轴。这个方法帮我解决过至少三次“白天好好的半夜开始乱跳”的疑难杂症。写在最后状态机不是新知识嵌入式领域早就在用了但在PLC世界里很多人还是习惯用梯形图一条条排指令最后排出一个几千步的大梯形图。状态机这个写法真正解决的是“程序乱不乱、好不好调”的问题而不是“能不能动”的问题——所有设备最后都能动起来但出问题时你是花三天还是三十分钟差别就在这里。我现在的习惯是接手任何一套需要写顺序逻辑的设备第一件事不是打开Sysmac Studio写代码而是先在纸上把状态迁移图画出来哪怕后面用梯形图实现这张图也能让思路清晰大半。欧姆龙的Sysmac Studio对ST、FB和枚举的支持在各大品牌里算相当舒服的建议你也找个小项目试一遍状态机比如一个气缸一个传感器的小机构跑通之后你会对“每一步都有据可查”这种感觉上瘾。