首页 / 资讯中心 / 文章详情

MC_Power、MC_Home、MC_MoveRelative三段式运动控制状态机实现与调试

MC_Power、MC_Home、MC_MoveRelative三段式运动控制状态机实现与调试 ★ FEATURED ARTICLE
最近在调一套小型龙门机构的运动控制程序控制器支持标准PLCopen运动功能块程序用ST结构化文本编写。核心逻辑就是标题里这三件套MC_Power负责使能、MC_Home负责回原、MC_MoveRelative负责相对定位。听起来非常简单该使能就Enable该回原就给Execute该运动就发脉冲但真正把三个功能块按顺序、带错误处理地串成一个完整流程时问题全冒出来了MC_Power刚把Enable置TRUEMC_Home能不能立刻触发回原还没结束MC_MoveRelative的Execute要不要一直保持急停之后重新上电状态机应该回到哪一步这些问题不看几个晚上的功能块手册根本答不全。这篇内容适合正在用ST写运动控制、又想把手动自动切换和定位流程做得可靠一些的同学。我会从三个功能块的本质行为讲起再给出一套可直接抄的状态机实现最后分享几个联机调试时特别容易忽略的坑。1. 异步功能块与顺序错觉为什么要给三段调用加状态机1.1 一个“看起来对但跑起来就乱”的典型写法很多第一次写运动控制的工程师包括我自己当年都会下意识写出下面这种顺序逻辑// 错误示范以为功能块像普通函数一样执行完就有结果 MC_Power_Instance(Enable : TRUE, Axis : ADR(axis)); MC_Home_Instance(Execute : TRUE, HomeMode : 2, Position : 0, Axis : ADR(axis)); MC_MoveRelative_Instance(Execute : TRUE, Distance : 10000, Velocity : 200, Axis : ADR(axis));一个周期内把三个功能块全部触发一遍。表面看着没问题先使能、再回原、再运动。但跑起来之后电机要么不动要么报错要么突然冲出去。原因很简单运动控制功能块不是普通函数它们在触发之后并不会立即修改轴位置而是进入“后台执行”状态靠每个扫描周期不断被调用来完成内部状态迁移。MC_Power的Enable置TRUE之后伺服驱动器的使能信号建立、母线电压建立、抱闸抬起这些都需要时间。MC_Home一旦触发轴会先找原点开关、再找编码器零脉冲整个过程可能持续数百毫秒甚至数秒。MC_MoveRelative触发后轴要按加减速曲线走完一段行程才算完成。如果你在一个扫描周期里把三个Execute全部给上功能块之间根本不知道谁该等谁结果就是执行器和命令相互打架。1.2 状态机的本质是把“等待条件”显性化解决这个问题的办法不是引入什么高深理论而是把“先后关系”变成显式的状态判断。状态机的本质是把异步操作拆成一个一个阶段每个阶段只做一件事并且明确写出“进入下一阶段”的条件。比如使能阶段不应该在置Enable后的下一个扫描周期就去回原而应该一直检查MC_Power.Status和MC_Home的Busy状态确认轴已经进入“调节器在线”状态再触发回原。回原阶段也一样只有等到MC_Home.Done出现上升沿才允许发运动指令。我在实际项目中总结过一句话运动控制里的状态机不是在管理逻辑是在管理“等待”。你把等待关系理顺了程序自然就稳了。后面三个功能块的细节全都是围绕这句话展开的。2. MC_Power的真实“完成信号”在哪里使能判定的三个层次2.1 Enable参数是电平不是脉冲MC_Power的Enable输入是一个电平信号不是边沿触发。很多初学者把它当按钮用这个周期置TRUE下个周期就置FALSE以为点一下就使能了。实际上Enable必须持续保持TRUE只要你还想让轴处于可运动状态就得一直保持它为TRUE。一旦Enable掉到FALSE轴会进入“去使能”流程驱动器主回路断开电机会立刻失去保持力矩。这时候如果程序里正好有一条运动指令在跑轻则指令报错中断重则由于惯性导致位置偏差甚至机械碰撞。所以状态机的第一个原则使能命令是电平不是脉冲必须由状态变量来持有。// 推荐写法用状态变量持有使能电平 CASE state OF ST_POWERING: cmdPower : TRUE; // 进入使能阶段后持续置位 ST_IDLE, ST_ERROR: cmdPower : FALSE; // 空闲/错误阶段断开使能 END_CASE powerFB( Enable : cmdPower, bRegulatorOn : cmdPower, Axis : ADR(axis) );用状态变量cmdPower而不是直接用某个按钮信号就是为了避免操作者松开按钮导致使能断开。2.2 使能完成的稳定判定与扫描周期余量不同的控制器对MC_Power的输出定义存在差异但大概率会提供Status、RegulatorOn、Busy这三个输出。Status表示功能块整体无错误RegulatorOn表示驱动器调节器已经打开Busy表示正在切换过程中。我踩过的一个坑是只看Status为TRUE就认为使能完成。但Status为TRUE只能说明功能块没有报错不能说明驱动器调节器已经稳住了。在某些平台上Status会在Enable刚给上去的瞬间就被置TRUE而RegulatorOn还停在FALSE。如果这时候就去触发MC_Home回原命令很可能直接被拒绝或者轴没有任何反应。更可靠的完成判定是满足下面三个条件判定条件含义为什么需要RegulatorOn TRUE驱动器已进入调节状态说明使能信号真正到达驱动器侧Status TRUE功能块无错误说明使能链路没有故障ErrorID 0无底层报错说明驱动器没有报警并且建议在这三个条件全部成立后再额外等一个稳定周期。我的习惯是加一个小的TON定时器比如50毫秒到100毫秒确认使能状态没有来回抖动再触发回原。这个延时在纯逻辑仿真里感觉不到但在真实设备上能极大减少“偶发回原失败”的问题。2.3 掉使能与急停状态下的回退策略使能状态不是一成不变的。运行过程中如果操作者按了急停或者驱动器自身报警MC_Power的Status和RegulatorOn会迅速变为FALSE。这时候状态机如果还停在“运动”或“回原”状态那就会永远等不到Done。我的处理方法是在主状态机的每个周期开头加一个全局检查如果当前不在IDLE和ERROR状态并且MC_Power的调节器状态丢失立即把状态拉回IDLE或者进入专门的错误处理状态。// 全局掉使能检测 IF state ST_IDLE AND state ST_ERROR THEN IF NOT powerFB.RegulatorOn OR powerFB.Error THEN // 使能意外丢失终止一切运动相关命令 cmdMove : FALSE; cmdHome : FALSE; state : ST_IDLE; END_IF END_IF注意这里不是一掉使能就让状态机报故障而是回到IDLE重新等待启动信号。因为急停复位后操作者通常希望重新走一遍“使能—回原—就绪”流程。如果直接进ERROR反而需要额外一次复位操作。3. MC_Home回原的完成判据、超时监视与失败重试3.1 回原过程为什么不能按“固定延时”处理回原Homing是一个典型的时长不确定流程。轴从哪里启动、原点开关安装在什么位置、寻零速度设定多少、碰到开关后是否要再找编码器零脉冲都会影响总耗时。如果写死在程序里“延时3秒算完成”换一台设备、改一次速度后立刻失效而且轴可能还没停下来就收到运动指令相当危险。正确做法是始终以MC_Home的输出信号作为完成依据Busy为TRUE代表回原进行中Busy变为FALSE且Done为TRUE代表成功完成Error为TRUE代表失败。我用一张表把这几个输出的组合关系说清楚BusyDoneError意义状态机动作FALSEFALSEFALSE回原未启动等待触发TRUEFALSEFALSE回原执行中继续等待FALSETRUEFALSE回原成功完成跳转就绪FALSEFALSETRUE回原失败进入错误处理TRUEFALSETRUE执行中出现错误等待Error稳定后处理3.2 Done信号可能只有一拍边沿采样写法MC_Home的Done输出在成功完成后通常会维持一个扫描周期随后恢复FALSE。如果你在状态机里写IF homeFB.Done THEN看起来够用但一旦功能块在某个品牌平台上有特殊时序比如Done和Busy同时翻转你的状态机就可能漏掉这一拍永远卡在回原状态。我习惯把所有“完成类”信号都先做一次上升沿捕捉再用捕捉结果做状态跳转// 上升沿捕捉 homeDoneRise : homeFB.Done AND NOT homeDonePrev; homeDonePrev : homeFB.Done; // 使用上升沿结果 IF homeDoneRise THEN state : ST_READY; END_IF这样即使Done只出现一个扫描周期也能被可靠记录。运动控制里的“边沿捕捉”不是可选项是所有命令触发和完成判断的基础。3.3 回原超时和重复回原的复位条件回原失败的原因非常多原点开关没接对、寻零速度过低导致一直碰不到开关、负向限位先被触发、驱动器报警导致回原中途停止。所以状态机里必须给回原加一个超时监视。超时时间怎么定我一般按“轴从行程最远端走到原点所需时间的1.5倍”来估算。比如某轴行程1米寻零速度设为50毫米每秒那最坏情况大约20秒超时设30秒比较稳妥。如果设备希望快速报警可以再收紧到20秒左右。超时和Error都要进同一个错误处理状态但复位条件不一样。Error可以由MC_Home的ErrorID清除来复位超时则需要先把回原命令撤掉再让MC_Home进入空闲状态然后重新触发。最简单的方式是一旦进入错误处理状态就把execHome置FALSE等至少两个扫描周期再允许下一次启动。CASE state OF ST_HOMING: homeTimer(IN : TRUE, PT : T#30S); IF homeFB.Done THEN execHome : FALSE; state : ST_READY; ELSIF homeFB.Error OR homeTimer.Q THEN execHome : FALSE; state : ST_ERROR_HOME; END_IF END_CASE这里还有一个容易踩的坑执行MC_Home时Execute需要保持TRUE直到完成还是只要上升沿就够了不同平台行为不一致。保守做法是在回原执行期间持续把Execute保持TRUE等Done或Error后再把它撤掉。这样既能兼容“边沿触发”型平台也能兼容“电平保持”型平台。4. MC_MoveRelative的触发纪律相对运动的边沿、参数与连续性4.1 Execute边沿、保持时长与运动中断的关系MC_MoveRelative的Execute是一个上升沿触发的命令。按照标准PLCopen的定义Execute从FALSE变为TRUE时启动一次运动执行期间可以保持TRUE也可以变为FALSE。但某些厂家的实现中如果Execute在运动过程中被拉低会导致当前运动直接中止并置CommandAborted。为了兼容所有平台我采用的规矩是运动启动后将Execute一直保持TRUE直到Done或Error出现才拉低。这样既不会因突然撤掉命令而误触发中断也不会让命令在完成之前丢失。IF moveRequested AND state ST_READY THEN execMove : TRUE; state : ST_MOVING; END_IF // 功能块调用 moveFB( Execute : execMove, Distance : targetDistance, Velocity : moveVelocity, Acceleration : accel, Deceleration : decel, Axis : ADR(axis) ); // 完成后再撤命令 IF moveFB.Done THEN execMove : FALSE; state : ST_READY; END_IF4.2 运动参数的更新窗口与Distance复用很多PLCopen运动功能块都支持在Busy期间修改Distance、Velocity等参数。但修改时机不同效果完全不同有的平台立即生效并中断当前运动曲线有的平台则忽略当前运动、等下一次触发才生效。这个行为差异在标准里没有统一规定所以最安全的做法是只在运动“未启动”的时候更新参数。具体到MC_MoveRelativeDistance代表的是“从当前实际位置开始的偏移量”。如果上一段运动还没结束你就把Distance改成下一个目标偏移功能块可能把目标位置和当前运动叠加导致位置完全不对。我见过一个真实案例某设备想把一个工件从A点移动100毫米如果检测到工件不到位再补10毫米。工程师在第一次运动进行中就把Distance从100改成10结果轴迅速掉头往回走。原因就是新参数在Busy期间被部分平台接受直接覆盖了原目标。修复方法就是加一个“参数更新窗口”// 只有READY状态才允许写入运动参数 IF state ST_READY THEN targetDistance : nextOffset; moveVelocity : currentSpeed; END_IF简单说运动参数绑定到状态机而不是直接绑到功能块输入才能真正控制“什么时候改”和“改给谁用”。4.3 连续完成多个相对位移的正确衔接自动流程里经常需要连续执行多段相对运动比如先走50毫米再走30毫米再走20毫米。如果这三段是独立的MC_MoveRelative那就必须严格等上一段的Done上升沿出现才能把下一段的Execute置TRUE。一个常见错误是把三段运动命令在同一个扫描周期全部触发。结果MC_MoveRelative作为一个功能块实例只保留最后一个Execute有效前两段完全被淹没。正确的衔接写法是使用“待执行队列”或“顺序索引”步骤动作状态1触发第一段距离50毫米等待DoneST_MOVING2第一段Done后清除execMove切换到第二段参数ST_MOVING3触发第二段距离30毫米等待DoneST_MOVING4按类似逻辑执行第三段ST_MOVING每段运动共享同一个MC_MoveRelative实例所以关键是给功能块输入一个“当前段参数”的变量集合每完成一段就更新一次CASE moveStep OF 1: targetDistance : 50; moveVelocity : 100; IF execMoveRise THEN moveStep : 2; END_IF 2: // 等待Done再更新下一段 IF moveDoneRise THEN execMove : FALSE; targetDistance : 30; moveStep : 3; END_IF END_CASE这个设计特别适合做固定节拍的搬运流程又能避免写一堆重复功能块实例。5. 三段串联的完整状态机实现从状态定义到ST代码5.1 状态定义与转移条件表把所有逻辑串起来我推荐分成六个状态IDLE、POWERING、HOMING、READY、MOVING、ERROR。这套状态定义既覆盖了标题里的三个功能块又留出了错误和掉使能的安全出口。当前状态转移条件下一状态IDLE启动信号上升沿POWERINGPOWERINGRegulatorOn、Status、ErrorID全部满足且稳定HOMINGPOWERINGErrorERRORHOMINGDone上升沿READYHOMINGError或超时ERRORREADY运动命令MOVINGREADY停止信号IDLEMOVINGDone上升沿READYMOVINGError或CommandAbortedERROR任意非IDLE/ERROR态使能丢失或急停IDLEERROR复位信号且所有FB ErrorID清除IDLE5.2 主循环代码的结构与关键写法接着给出一个可以套用的简化版ST代码。每个周期调用一次这个程序块外部通过startSignal和resetSignal控制启动与复位。// 状态常量 CONSTANT ST_IDLE : 0; ST_POWERING : 1; ST_HOMING : 2; ST_READY : 3; ST_MOVING : 4; ST_ERROR : 5; END_CONSTANT // 上升沿捕捉 startRise : startSignal AND NOT startPrev; startPrev : startSignal; resetRise : resetSignal AND NOT resetPrev; resetPrev : resetSignal; homeDoneRise : homeFB.Done AND NOT homeDonePrev; homeDonePrev : homeFB.Done; moveDoneRise : moveFB.Done AND NOT moveDonePrev; moveDonePrev : moveFB.Done; // 功能块调用所有周期都会执行 powerFB( Enable : cmdPower, bRegulatorOn : cmdPower, Axis : ADR(axis) ); homeFB( Execute : execHome, HomeMode : 2, Position : 0, Axis : ADR(axis) ); moveFB( Execute : execMove, Distance : targetDistance, Velocity : moveVelocity, Acceleration : accel, Deceleration : decel, Axis : ADR(axis) ); // 全局掉使能检测 IF state ST_POWERING AND state ST_MOVING THEN IF NOT powerFB.RegulatorOn OR powerFB.Error THEN execHome : FALSE; execMove : FALSE; state : ST_IDLE; END_IF END_IF // 超时监视 homeTimer(IN : (state ST_HOMING), PT : homeTimeout); moveTimer(IN : (state ST_MOVING), PT : moveTimeout); // 状态转移 CASE state OF ST_IDLE: IF resetRise THEN cmdPower : FALSE; execHome : FALSE; execMove : FALSE; END_IF IF startRise THEN cmdPower : TRUE; state : ST_POWERING; END_IF ST_POWERING: IF powerFB.Status AND powerFB.RegulatorOn THEN // 稳定后再回原 execHome : TRUE; state : ST_HOMING; ELSIF powerFB.Error THEN cmdPower : FALSE; state : ST_ERROR; END_IF ST_HOMING: IF homeDoneRise THEN execHome : FALSE; state : ST_READY; ELSIF homeFB.Error OR homeTimer.Q THEN execHome : FALSE; state : ST_ERROR; END_IF ST_READY: IF moveCommand THEN execMove : TRUE; state : ST_MOVING; ELSIF stopCommand THEN cmdPower : FALSE; state : ST_IDLE; END_IF ST_MOVING: IF moveDoneRise THEN execMove : FALSE; moveCommand : FALSE; state : ST_READY; ELSIF moveFB.Error OR moveFB.CommandAborted OR moveTimer.Q THEN execMove : FALSE; moveCommand : FALSE; state : ST_ERROR; END_IF ST_ERROR: IF resetRise THEN cmdPower : FALSE; execHome : FALSE; execMove : FALSE; state : ST_IDLE; END_IF END_CASE5.3 超时与公共错误分支的处理超时定时器的PT值需要根据每台设备的实际情况调。上面代码里我写了homeTimeout和moveTimeout两个变量建议在初始化里赋值而不是写死在代码中这样现场调试时可以随时修改。错误分支的核心原则是错误处理不是直接清ErrorID而是先把所有命令撤掉让功能块回到空闲状态再进行复位。比如MC_Home报错后如果execHome还是TRUE有些平台不允许清除ErrorID。必须先把它置FALSE等一个扫描周期然后再允许复位。另外公共错误处理里要记录ErrorID和出错状态方便现场排查。我一般会给每个状态机配一个错误结构体TYPE ST_ErrorInfo : STRUCT errorState : INT; // 出错时所在状态 errorFB : INT; // 出错功能块编号1Power, 2Home, 3Move errorID : DINT; // 功能块ErrorID timestamp : UDINT; // 出错时间用于趋势分析 END_STRUCT END_TYPE6. 联机调试中的三个高频翻车点与排查思路6.1 用状态字而不是功能块输出判断轴状态现场调试时只看ST程序里的FB输出会漏掉很多信息。我强烈建议把轴的状态字StatusWord也纳入监控。驱动器状态字能反映“急停未复位”“抱闸未打开”“母线未充电”“限位触发”等等底层状态这些状态往往不会直接映射到MC_Power的Error输出但会让使能或者回原失败。比如有些伺服驱动器在上电后默认处于“伺服待机”状态必须先把控制字置位进入“运行使能”状态MC_Power的RegulatorOn才会变TRUE。如果程序只盯着MC_Power.Status看会以为功能块有问题实际上使能链路早就断在驱动器侧了。联机调试时我习惯同时开三列监控状态机当前状态、FB输出状态、驱动器状态字。三者对照绝大多数运动控制问题都能快速定位。6.2 “回原偶尔失败”与“使能后立即回原”的时序干扰之前有一台设备出现过“第一次启动回原正常第二次启动偶发失败”的问题。后来排查发现故障都发生在急停复位后立刻重新启动的场景下。电机刚重新使能驱动器内部的抱闸还没完全打开MC_Home就被触发了。抱闸的机械释放时间大约是几十毫秒但程序在使能完成的同一个扫描周期就发了回原命令导致驱动器认为轴还在异常状态拒绝对原命令。修复办法就是我在第二章里提到的“稳定延时”。在RegulatorOn变TRUE之后额外等待100毫秒左右再去触发MC_Home。这个延时不是功能块要求而是为抱闸、母线电容等物理环节留下的余量。工业调试里这种“电气已经通但机械没到位”的时序问题比逻辑错误更难发现因为它不是每次都会出现。6.3 功能块实例重复调用与扫描周期不一致的隐患最后一个高频坑同一个功能块实例在程序里被调用两次。比如轴回原逻辑写在任务A里运动控制写在任务B里两个任务扫描周期不同MC_Home实例同时被两个地方调用。轻则功能块行为异常重则导致底层轴状态错乱。在ST运动控制里一个轴对应的功能块实例必须在一个确定的扫描周期内、只调用一次。如果项目里确实需要在多个任务中使用轴状态正确做法是让其中一个任务拥有这个功能块实例其他任务通过全局变量读取结果。还有人会把功能块放在一个扫描周期非常快的任务里而状态机放在另一个慢速任务里。这也会引起问题因为功能块的Ready/Active输出可能在不同任务间出现相位偏差。我的建议是整个轴控制逻辑从功能块调用到状态机全部放到同一个任务中周期一致才能保证信号的可判定性。如果一定要跨任务那就使用统一的“命令缓存”结构在慢任务中生成命令在快任务中消费命令二者之间用边沿量做握手。调试到这一步功能块之间的衔接问题基本就都暴露出来了。轴能不能动起来很多时候不取决于你熟悉多少功能块指令而取决于你能不能把“等待什么、跳过什么、出错回到哪一步”写清楚。MC_Power、MC_Home、MC_MoveRelative只是三块积木状态机才是让它们变成一台可靠机器的粘合剂。希望这篇能帮你少走一些弯路。
阅读完成 · 觉得有帮助?
咨询建站