凌晨两点,车间里只剩设备运行灯还亮着。你在调试台上盯着伺服轴连续往复跑了二十几次,眼看就要收工,结果下一轮动作刚走到行程的一半,安全门被人一碰,急停回路瞬间断开——轴停在半空中,PLC侧的程序还在按部就班地执行。你猜它接下来会干嘛?继续把剩余脉冲发完?还是干脆把输出点复位?如果是前者,伺服会带着“未完成的目标位置”继续狂奔一小段;如果是后者,重新上电后这轴到底该回到哪里,谁也说不清。这就是今天要聊的痛点:急停工况下,伺服轴的脉冲输出如何做到可暂停、可恢复、可移植。
伺服控制里的“脉冲暂停”听着简单,无非是让脉冲停一下,但真正落地时会发现,它牵扯到状态机设计、底层指令差异、驱动器参数配合、硬件急停链路和软件恢复逻辑。更麻烦的是,你不可能每一次换项目都重写一套暂停逻辑——今天用某日系品牌的PLC,明天用某国产主流PLC,后天可能换成IEC 61131-3平台的CODESYS系产品,接口全不一样。这篇文章我直接用一套可复用的思路拆开讲:急停时脉冲为什么要“暂停”而不是“切断”;怎么用状态机把暂停、恢复、报错这段逻辑封装成跨平台功能块;以及我在现场踩过的几个坑。
1. 为什么急停必须“暂停脉冲”而不是“切断脉冲”
1.1 直接停脉冲输出,往往会出大问题
很多刚接触运动控制的人,第一次做急停逻辑时想得很简单:急停信号一触发,把“发脉冲”的M继电器断开,或者直接把高速脉冲输出对应的使能位清零,这不就停了吗?表面上看,轴确实不动了,但这里藏着两个隐患。
第一个隐患是“停止后的位置语义丢了”。脉冲输出指令都有一个当前目标位置寄存器和实际偏差计数器。如果在运行中直接关闭指令,很多PLC的高速脉冲输出会自动把输出点的脉冲数清零,甚至把发送缓冲区的剩余脉冲一并丢弃。这时候伺服驱动器端实际的指令位置,和PLC脑子里记录的“我打算发多少脉冲”,就对不上了。等急停复位后,你按PLC侧的当前值去恢复动作,轴要么冲出去,要么反向回原点,很难优雅地接着跑。
第二个隐患更隐蔽:大多数PLC的脉冲指令是“一次性任务”,触发后它会自己发完一整段脉冲数。急停信号来了,关闭输出管脚,这段脉冲确实断了,但驱动器那里可能还缓存着一部分未执行的脉冲指令,或者驱动器的位置环仍在按之前的速度前馈继续跑。轴在物理上停住,是靠伺服自身的急停响应,而不是靠PLC“停发脉冲”这个动作。这意味着复位后脉冲计数和实际机械位置之间,存在一个随机偏差。
所以,这里说的“暂停”,并不是“让脉冲输出点变成0V”,而是“让运动控制状态机进入一个可恢复的中间态”:轴停住、位置还在、剩余脉冲数还在、重新启动的条件可控。
1.2 暂停的本质是“对三组数字的一致性管理”
想要真正理解暂停的逻辑,不需要去啃伺服原理图,只需要记住运动控制中同时存在三组数字:
- 目标位置脉冲数:PLC想发送的总量。
- 已发送脉冲计数:PLC输出口实际发出的脉冲数,或者伺服编码器反馈换算出来的脉冲数。
- 机械当前位置:最终由伺服电机带着负载到达的位置。
正常情况下,这三者按比例同步前进。急停要暂停时,真正要做的是:把“目标位置脉冲数”冻结在当前状态,把“已发送脉冲计数”冻结在当前值,然后计算出一个“剩余脉冲数”,最后把这个“剩余脉冲数”作为下一次重新启动时的新目标。
这就像你开车去一个目的地,导航规划了30公里路程,开到第12公里时高架上封路了。你做的不是把导航关掉,而是记录“还剩18公里”,然后在下一个入口重新规划继续走。急停暂停也是同一个道理:不是丢掉目标,而是把“还剩多少脉冲”这个关键值保存下来,等待恢复指令。
1.3 位置环保持是暂停的基础保障
还有一点必须提:暂停期间,伺服驱动器的使能状态该怎么处理。
我见过一些调试人员,急停一触发,干脆把伺服的使能也一起关掉。这样做的坏处很明显——当伺服失去使能,电机轴处于自由状态,如果是垂直轴,重力会让它直接下滑;如果是水平轴,只要负载摩擦不够大,机械系统也可能发生微小位移。这一动,编码器位置就变了,暂停时记录的“剩余脉冲”就失去了意义。
正确的处理方式应该是:急停触发后,脉冲发送暂停,但伺服驱动器保持使能,让位置环继续锁定电机轴。如果驱动器配了“急停保持”或“停机保持”的参数,优先启用它。这样机械位置被电气锁定,暂停前后的坐标基准完全一致。当然,如果工艺要求必须断开使能,那就要额外考虑回机械原点的逻辑,这已经超出了脉冲暂停的范畴。
2. 跨平台可移植方案的底层逻辑
2.1 为什么这年头做运动控制必须考虑“可移植”
说实话,十年前做设备,一个PLC型号用到项目报废都未必换。但现在不行了,芯片缺货要换型号、客户指定品牌、出口设备要求不同认证,任何一个原因都可能让项目从某日系知名PLC换成某国产PLC,或者直接迁移到IEC 61131-3平台。你之前写好的各种轴控逻辑,包括暂停逻辑,如果全是针对某一款硬件指令死写的,换平台就等于重写一遍。
脉冲暂停这个功能恰恰是重灾区。市面上主流PLC的高速脉冲输出指令各有各的脾气:有的用“目标脉冲数+运行标志”两个软元件控制,有的用“频率+脉冲数+加减速时间”参数表控制,有的直接封装好了MC_MoveAbsolute这种运动控制功能块。如果暂停逻辑直接贴在这些指令上,跨平台时根本没有复用性。
所以我给自己定了一个规矩:凡是运动控制逻辑,一律先抽象出状态机,再做底层接口适配。脉冲暂停只是这个思路的一个典型应用。
2.2 设计目标:业务逻辑与底层硬件彻底解耦
要实现可移植,就得把代码分成两层。
上层是“业务逻辑层”,只关心几个问题:现在是什么状态?收到急停信号后该切到哪个状态?恢复指令到来时还差多少脉冲?这一层不关心你是用哪种PLC,也不关心脉冲输出点的Y编号。
下层是“驱动适配层”,负责把上层翻译成具体平台的指令。比如在三菱系PLC上可能要用PLSY指令重发剩余脉冲;在CODESYS系平台可能要用MC_Power加MC_MoveAbsolute;在国产支持标准库的PLC上,又有自己的高速脉冲函数。上层逻辑写好后,下层只需要做替换。
这样做的好处很明显:换平台时,状态机那几百行代码原封不动搬过去,只需要重写最下面一小段“发脉冲”的封装接口。一次调试成功的暂停/恢复逻辑,可以跟着你走很多个项目。
2.3 不同平台的脉冲输出指令差异对比
拿我实际接触过的几类平台举例,帮你感受下适配层要处理的差异有多大。
| 平台类型 | 脉冲输出典型方式 | 暂停实现思路 | 注意事项 |
|---|---|---|---|
| 日系某品牌高速脉冲 | 软元件触发式发送,指令运行中修改软元件无效 | 关闭运行标志,停止发送,保存剩余脉冲变量 | 重启时要重新写入目标脉冲数 |
| 国产主流PLC高速脉冲 | 结构体参数配置脉冲数/频率/方向 | 暂停位单独控制,停止后从缓存区读取剩余值 | 注意读取缓存区的时序,提前读取可能读到旧值 |
| IEC 61131-3平台 | 运动控制库指令,带Enable输入 | 断开Enable输入,位置保持 | 恢复时务必重新执行定位指令,注意缓冲模式选择 |
从表里能看出一个规律:不管平台怎么变,“停止发送”这件事总有办法做,真正难的是“如何拿到正确的剩余值”,然后再“把剩余值作为新目标发出去”。这就是适配层要做的最核心的事。
3. 核心实现:一个可移植的脉冲暂停状态机
3.1 状态机的四个基本状态
我实现脉冲暂停功能时,没有直接在大循环里用一大堆IF去判断,而是写了一个标准的四状态状态机。四个状态分别是:
- 空闲态:没有任务,输出关闭,目标脉冲数为0。
- 运行态:正在发脉冲,持续监控暂停信号。
- 暂停态:脉冲已停止,剩余脉冲已保存,等待恢复或者急停复位。
- 结束态:脉冲全部发完,任务完成,输出停止,等待下一次触发。
状态之间转移条件很简单:空闲态收到启动命令,进入运行态;运行态收到暂停请求,进入暂停态;暂停态收到恢复命令,回到运行态;运行态脉冲发送完成,进入结束态;任何状态下收到复位命令,回到空闲态。
这套状态机与具体PLC无关,甚至可以用在纯软件仿真环境里。它的核心价值在于:暂停/恢复不是一个拍脑袋的动作,而是一组有明确边界的状态迁移,每一步都知道自己从哪来、要到哪去、需要保存什么数据。
3.2 ST语言实现示例
下面这段是我在实际项目里用过的简化版代码,用结构化文本ST编写,你在支持ST的平台上基本可以照搬。它定义了一个功能块FB_PulsePauseCtrl,上层逻辑只管调用它,底层驱动的差异被封装在输出变量上。
FUNCTION_BLOCK FB_PulsePauseCtrl VAR_INPUT bEnable : BOOL; // 功能块使能 bStart : BOOL; // 启动脉冲任务 bPauseReq : BOOL; // 暂停请求,来自急停逻辑 bResumeCmd : BOOL; // 恢复命令 bStopAndReset : BOOL; // 完全停止并复位 udiTargetPulse : UDINT; // 本次任务的目标脉冲总数 udiFbPulse : UDINT; // 当前实际已发送/反馈脉冲数 END_VAR VAR_OUTPUT xRunCmd : BOOL; // 给底层驱动:运行输出 udiRemainPulse : UDINT; // 剩余脉冲数,用于重新下发 UDI_CMD_PULSE : UDINT; // 给底层驱动的目标脉冲数 iState : INT; // 当前状态 0/1/2/3 END_VAR VAR udiLastFbPulse : UDINT; // 进入暂停时保存的已发脉冲 xPaused : BOOL; END_VAR状态机主逻辑如下:
CASE iState OF 0: // 空闲态 xRunCmd := FALSE; UDI_CMD_PULSE := 0; udiRemainPulse := 0; IF bStart THEN udiLastFbPulse := 0; UDI_CMD_PULSE := udiTargetPulse; iState := 1; END_IF 1: // 运行态 xRunCmd := TRUE; udiRemainPulse := udiTargetPulse - udiFbPulse; // 防止计算溢出或反馈超过目标 IF udiRemainPulse > udiTargetPulse THEN udiRemainPulse := 0; END_IF // 暂停请求抢占 IF bPauseReq THEN udiLastFbPulse := udiFbPulse; xRunCmd := FALSE; iState := 2; ELSIF udiFbPulse >= udiTargetPulse THEN xRunCmd := FALSE; UDI_CMD_PULSE := 0; iState := 3; END_IF 2: // 暂停态 xRunCmd := FALSE; udiRemainPulse := udiTargetPulse - udiLastFbPulse; IF bResumeCmd THEN // 关键:把剩余脉冲作为新目标发给底层驱动 UDI_CMD_PULSE := udiRemainPulse; xRunCmd := TRUE; iState := 1; ELSIF bStopAndReset THEN iState := 0; END_IF 3: // 结束态 xRunCmd := FALSE; IF bStopAndReset THEN iState := 0; END_IF END_CASE这段代码看起来简单,但有几个细节必须说明白。
第一,运行态里计算剩余脉冲时,我用的是udiFbPulse反馈值而不是本地累加数。这就是第五章会讲的坑之一:现场环境下,单纯在PLC内部累加发送脉冲数,极容易因为外部干扰和驱动器缓存导致偏差。用驱动器反馈或者高速计数值,才是可靠做法。
第二,暂停态恢复时,把剩余脉冲作为新目标写进UDI_CMD_PULSE,不代表所有平台都能直接这样用。某些老式日系平台中,发过一次脉冲后,除非有专门的复位动作,否则再写入新的目标值不会生效。这时候适配层就得额外做一次指令复位,再重新触发发送。这就是为什么我反复强调状态机通用,但适配层必须按平台定制。
第三,急停信号不一定直接等于bPauseReq。更安全的方式是:急停信号触发时,先做硬件级停止,同时把bPauseReq置位,让状态机进入暂停态。急停复位后,操作员确认安全,再给bResumeCmd。这样软件状态和硬件安全链路各司其职。
3.3 底层驱动的适配思路
不同平台的适配层写法差异比较大,我简单说两个常见方向的思路。
某日系平台的适配思路:它的高速脉冲指令本质上是“触发后自动发送”,暂停时直接关闭运行位,恢复时把剩余脉冲数重新写入目标寄存器,再置位运行位。关键点在于,写目标寄存器前必须先执行一次指令的复位,清零发送完成标志,否则指令会认为上一次任务还没结束,不接收新目标。
IEC 61131-3平台的适配思路:直接用运动控制库里的定位指令。暂停时把Enable断开,轴会按照配置的停止模式停下来;恢复时重新以当前位置为起点,执行一段相对定位,目标值就是剩余脉冲数。这种方式最为优雅,因为运动控制库已经帮你处理了位置保持和缓冲问题。但要注意,暂停期间不要调用轴回零、轴参数重载等功能,否则会破坏位置基准。
如果你的平台是纯硬件的脉冲输出,没有软件缓存机制,那还有一个物理层兜底方案:在PLC高速脉冲输出和伺服驱动器的脉冲输入之间,串联一个由安全继电器控制的门控回路。急停时,门控回路直接切断物理脉冲通道,但伺服使能保持。这种方法暂停最彻底,电气上绝对安全,代价是需要额外硬件,而且恢复时需要一个同步脉冲计数机制,避免PLC侧计数和驱动器实际收到的脉冲数不一致。
4. 实操过程中的细节与避坑
4.1 急停信号接到PLC里,这四件事必须做对
急停信号是暂停功能的“触发器”,它要是不可靠,所有暂停逻辑全是空谈。我总结了几条硬规矩。
第一,必须硬接线,不能只靠总线通讯。急停是安全回路,它应该在硬件层面直接切断危险源。PLC里接收急停信号时,建议用运动控制任务的最高优先级中断程序处理,而不是在主扫描周期里轮询——主扫描里顺序执行,遇到长扫描周期,急停信号实时性就差很多。
第二,判断急停信号要用常闭逻辑。急停按钮和继电器现场普遍都接成常闭,因为常闭回路断线时会报故障,比常开回路更安全。到PLC程序里,你应该检测的是“常闭信号从ON变OFF”,而不是等着一个常开的急停信号变成ON。很多事故就出在接反了上。
第三,对急停信号本身要做滤波,但滤波时间必须极短。急停信号不是普通传感器,它需要快速响应,同时又得防止抖动误触发。一般我会在程序里做连续两次扫描一致性确认,或者用硬件滤波器设置1~2毫秒的滤波时间,绝对不要用那种几百毫秒的软件延时去滤急停信号。
第四,急停触发后,软件侧要把“暂停请求”置位成锁存状态。因为现场操作员按完急停,设备已经停了,他可能会立刻松手复位急停按钮。如果PLC侧没有锁存急停状态,复位那一瞬间暂停请求消失,设备可能自己恢复运行——这是绝对不能接受的。暂停请求必须一直保持到操作员明确按下“复位并继续”按钮。
4.2 暂停后恢复的三种策略,按工艺选
急停解除后,你有三种恢复策略可选,没有绝对优劣,只有合不合适。
第一种:直接继续发剩余脉冲。这是效率最高的方式,适合定位精度要求不特别苛刻的场景。缺点是,急停期间如果有任何微小机械位移,恢复后继续发剩余脉冲,终点位置会叠加这个偏差。精度要求高的设备不建议直接用。
第二种:回退一段距离再重新走到目标。有些设备工艺允许在急停后先反向回退一段安全距离,然后把剩余脉冲重新规划成一次新的定位。这种策略能消除暂停期间的微小位移影响,代价是恢复周期长一点。
第三种:手动模式点动校正后重置任务。适合大型设备、多轴联动机构。急停恢复后,操作员用手动模式把轴移动到安全参考点,确认无碰撞风险后,清空原任务,重新下发完整的目标定位指令。这种情况下,不要尝试恢复剩余脉冲,而是让设备重新执行完整的定位逻辑。
我一般会在HMI上做成三种按钮:“继续”“回退继续”“重新定位”,让现场操作人员根据实际情况选择。状态机只需要预留不同的恢复命令入口即可,核心逻辑不用改。
4.3 伺服驱动器侧要配合调的参数
暂停功能不是PLC单方面的事,伺服驱动器的参数如果不配合,程序写得再好也白搭。
重点看几个方面。第一是“急停时的停机模式”,很多驱动器允许设置急停是动态刹车还是位置保持。对脉冲暂停场景,建议选择位置保持,而不是自由停车。第二是“使能断开延时”或“欠压保持时间”,确保在急停瞬间母线电压还没有掉下去时,位置环仍有能力锁住电机。第三是“指令脉冲禁止输入”或者“脉冲清除”功能,如果你用了外部硬件门控方案,需要把门控信号和驱动器输入端子关联,并设成有效电平。
还有一点容易被忽略:如果你用编码器反馈来计算剩余脉冲,务必确认驱动器的电子齿轮比和PLC侧的脉冲单位一致。伺服转一圈是10000个脉冲还是131072个脉冲,差分信号是多少倍的细分,这些参数不统一,暂停恢复时算出来的剩余值就是错的。
5. 常见问题与排查实录
5.1 常见问题速查表
下面的表格里是我在实际调试中遇到的典型问题,按“现象—原因—解决思路”整理成了速查表,直接拿来当排障清单用也行。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| 急停后恢复,轴比预期位置多走一段 | 驱动器缓存了未执行的脉冲 | 暂停时同时关闭指令使能和驱动缓存,恢复前清除缓存 |
| 恢复后轴反向运动 | plc侧目标脉冲被清零,恢复时用了0值重发 | 确认重新写入的目标是剩余脉冲而不是当前计数器值 |
| 暂停期间电机轴缓慢下滑 | 伺服使能被断开或保持力不够 | 检查急停时使能回路,确认驱动器处于位置保持模式 |
| 恢复后位置整体偏移固定距离 | 暂停期间外部拖拽或机械位移 | 改用“回退再定位”策略,或增加原点校验流程 |
| 程序里已停止发脉冲,轴还在动 | 正在执行的是一段长脉冲任务,关闭标志没被立即响应 | 查看PLC是否支持异步停止,必要时采用硬件脉冲门控 |
| 多轴同时暂停时,只有部分轴恢复正确 | 状态机复位时序不一致 | 所有轴的暂停恢复命令统一用同一周期任务输出 |
5.2 我踩过的三个坑
第一个坑是关于“软件暂停成功但驱动器继续走完”的。某次用国产带缓存的高速脉冲模块,暂停位置OF后,脉冲命令在模块内部还有一个不小的FIFO缓冲区,里面的脉冲还在继续往伺服发。程序上看已经暂停,轴实际还在跑完那几百个脉冲。后来我把暂停逻辑改成“先关闭使能缓存,再断开运行位”,并把暂停完成标志延迟到驱动器反馈停稳后才置位,问题才解决。
第二个坑是恢复时的“第一段正常、第二段冲出去”。原因是恢复时我把剩余脉冲写入目标后,漏掉了清除发送完成标志。PLC的脉冲指令认为上一段还没有发完,不接受新目标,等到我下一次触发时,莫名其妙把两段合并到一起发了出来。看上去就像设备突然抽风。这让我养成了一个习惯:每次下发新目标前,先把“发送完成”和“运行中”两个标志位都清零。
第三个坑发生在半夜加班时,设备急停后回零,Z相原点信号一直被暂停状态遮蔽,导致回零逻辑找不到原点,轴一直慢慢转个不停。排查了很久才发现,暂停功能把回零相关的高速计数通道也锁住了。后来我规定,急停暂停只锁轴运动状态,绝不去动回零和硬限位相关的输入通道。
5.3 关于“已发脉冲数”到底取谁的
最后必须单独强调一下:计算剩余脉冲时,优先使用伺服驱动器的实际反馈值,或者独立高速计数器的计数值,而不是PLC程序内部的那个累加变量。
原因很简单,脉冲输出经过端子、线缆、驱动器内部整形,中间任何一点受到干扰,都可能多脉冲、少脉冲,而程序内部累加数根本感知不到。真正可靠的剩余值,是目标值减实测反馈值。如果现场没有反馈通道,最低限度也要用PLC高速脉冲输出指令自带的“当前发送值”寄存器,不要自己在中断里加加减减。
6. 一点扩展思路:这套状态机还能怎么用
脉冲暂停的状态机一旦写好,你会发现它不止能处理急停,很多场景都能复用。
软限位触发的暂停就是一例。轴运动过程中,如果检测到接近开关提前触发,不需要急停,只需要让状态机进入暂停态,然后走回退逻辑。代码几乎不用改,只要把触发源从硬急停信号换成软限位信号就可以。
HMI上的“暂停/继续”按钮也一样。调试时经常需要让轴走到一半停住,观察机械结构有没有干涉。你把按钮信号接到bPauseReq和bResumeCmd上,立刻就有了一个人工暂停工具,而且和急停逻辑能共用同一套位置保持和恢复机制。
再往大了想,如果你以后从脉冲接口的设备升级到总线伺服,比如用实时以太网总线通讯,这套状态机的思路也依然成立,只是把底层适配从“发脉冲指令”换成“执行运动控制块+速度控制”。状态机里的暂停、恢复、剩余位置计算,仍然是你整台设备运动控制的地基。
我在实际项目里体会最深的是:做运动控制,不要指望某一款PLC的原生指令能覆盖所有需求,更不要指望现场不会遇到急停后需要恢复的场景。把你的控制逻辑拆成“状态机+驱动适配层”,花两周时间做出来的这套框架,后面两三年都在替我省钱省时间。
最后分享一个小技巧——在所有暂停恢复动作里,加一个“恢复确认时间锁”。也就是说,急停复位后,操作员必须额外的确认按钮按够500毫秒,恢复命令才生效。这个锁在自动化设备上看起来多余,但在客户操作现场真的能避免不少误触发的麻烦。调试时可以先把时间设短一点,验收前再改回去。