做 CODESYS 开发的人,十个里有八个碰到过 EtherCAT 断线;剩下两个,一个刚入行还没遇到,一个正在现场骂娘。EtherCAT 断线不是小事:伺服轴还在跑,总线突然没了,轻则报警停机,重则机构直接撞车。更头疼的是,断线原因千奇百怪——线缆松动、从站掉电、EMI 干扰、配置错位、从站 EEPROM 异常……你光靠看诊断页面只能知道“断了”,但很难第一时间判断“为什么断”“断在哪个环节”“能不能自动恢复”。
这篇文章我就用 CODESYS + ST 语言,手把手写一个 EtherCAT 状态监测功能块。它不依赖厂家专用库,只要你的工程里能读到从站 WcState 和 EtherCAT 状态字,就能直接用。代码不快,但每一步我都会讲清楚为什么这么写,包括断线确认的滤波逻辑、重连节奏、停机策略,以及我在现场踩过的那些坑。不管你是刚接触 CODESYS 的新手,还是已经在用汇川、施耐德等基于 CODESYS 平台控制器的老手,这套思路都通用。
1. 断线发生时,系统到底经历了什么
1.1 现象:从“跑得好好”到“全线报警”
正常生产时 EtherCAT 总线是 1ms 甚至 250us 一个周期在跑,主站每个周期都要和所有从站交换过程数据。一旦链路断开,主站收不到从站的有效数据,第一反应并不是立刻停机,而是先报一个类似 “EtherCAT Link Lost” 或者从站 WcState 异常的错误。
这时候如果你盯着 HMI,会看到一连串连锁反应:
- 总线主站状态从 OP 掉回 SAFEOP 或 INIT;
- 部分从站状态字变成 0x001B 之类的错误码;
- 伺服驱动器如果走的是 CiA402 协议,Statusword 里的 “Fault” 位置 1,轴直接报跟随误差;
- 你的程序里如果还用旧数据继续发输出,输出模块可能保持上一次的值,这就是最危险的地方——机构不会停,而是“僵住”。
我见过不少现场,操作工描述故障就是一句话:“机器突然全红了。”但真正的问题是这个“全红”是谁先触发的。是主站网卡断了?是从站电源掉了?还是某个从站自己挂掉了?如果程序里没有状态监测,你连排查方向都没有。
1.2 本质:WcState、状态机和你漏看的那几个字节
要写监测程序,先得明白 EtherCAT 在工作状态下每个周期在干什么。
EtherCAT 主站在 OP 状态下,每周期会发送一帧报文,帧里带着每个从站的输出数据;每个从站收到后,把输入数据插入对应位置,然后传递到下一个从站。帧回到主站后,主站会比较一个叫“工作计数器(Working Counter,WcState)”的东西。
工作计数器的本质是一个计数器:每个从站在收到数据处理帧后,如果认为自己正常处理了,就会把对应的计数器加 1。主站发出去的帧里记录期望值,收回来再看看实际值,如果对不上,那就说明某个从站没“干活”。
所以你在 CODESYS 的从站 I/O 映射里看到的WcState变量,绝大多数情况下会显示为 TRUE/FALSE,或者一个 0/1 的值。TRUE 代表这个从站的工作计数器匹配,FALSE 代表这个从站在上一周期没有正常参与通信。这个变量不需要你算,但你必须盯住它。
除了 WcState,还要看从站状态。EtherCAT 协议里从站状态机一共四档:INIT、PREOP、SAFEOP、OP。正常运行时在 OP,也就是说周期数据在交换;如果链路异常,从站可能退到 SAFEOP(输出被切断,但 SDO 还能通)甚至 INIT(只剩底层通信)。CODESYS 里很多从站的映射里都有State或者EtherCATState变量,值可能是 1、2、4、8,分别对应 INIT、PREOP、SAFEOP、OP。这个编码是 EtherCAT 协议定的,不是厂家乱标的。
1.3 为什么“直接重启”是新手最常见的错误
很多新手遇到断线后的第一反应是:把 PLC 切到 STOP 再 RUN,或者重新扫描从站,甚至断电重启整个柜子。这种操作不能说没用,但属于“闭着眼睛修电脑”——你确实可能把系统恢复过来,但你根本不知道问题有没有复发风险。
举个例子:如果断线是因为从站电源接触不良,你重启 PLC 后从站重新上电,EtherCAT 会重新初始化,设备确实能跑起来。但电源端子依然是松的,下次震动一来又断。如果你没有在程序里留下断线记录、重连次数、故障从站编号,这种问题就是“幽灵故障”,一个月出现两三次,每次都要靠人去现场重启。
再比如:如果断线是因为主站网卡驱动异常,你重启 PLC 当然能恢复,但根源在实时环境或者网卡驱动,不解决下次照样断。
所以正确做法是:在程序里主动监测、主动记录、主动尝试恢复,同时把断线原因尽可能固定下来,让后续排查有据可查。
2. 写状态监测前,先吃透这几个关键指标
2.1 WcState:最快暴露问题的信号
WcState 是状态监测的第一道防线。我在实际项目里,通常会在 EtherCAT 主站设备下把所有从站的 WcState 汇总到一个数组里,然后在 PLC 程序里轮询。
有一点要注意:不同的 CODESYS 版本、不同厂家的从站驱动,WcState 的映射类型不一样。有的映射成布尔量,有的映射成 BYTE,还有的映射成 WORD。你在程序里比的时侯,要么直接IF bWcState[i],要么IF byWcState[i] = 1,先打开从站映射表看清楚,不要想当然。
WcState 的坑在于它并不可靠到能覆盖所有断线场景。它只能反映“从站这个周期有没有正常参与过程数据交换”,但如果是主站网卡本身挂了、或者网线断在主站侧而不是从站侧,WcState 可能集体异常,也可能全部保持上一个值不动。所以只用 WcState 不够,还要配主站状态和状态字。
2.2 从站状态机和 AL 状态码:判断卡在哪个环节
从站状态字能告诉你从站到底处于哪一档。CODESYS 里主站扫描到从站后,如果是标准的 EtherCAT 从站,你通常可以在 I/O 映射里看到一个叫State的变量,值大概是:
| State 值 | 状态 | 说明 |
|---|---|---|
| 1 | INIT | 只有寄存器通信,过程数据没建立 |
| 2 | PREOP | 邮箱通信正常,过程数据未激活 |
| 4 | SAFEOP | 输入有效,输出被切断 |
| 8 | OP | 正常周期运行 |
如果从站退到 SAFEOP 但还在线,说明链路没断,问题可能在从站内部,比如同步错误、看门狗超时、PDO 配置变化。如果直接变成 INIT,那要么总线链路中断,要么从站被复位了。
另一个有用的信息是 AL 状态码。EtherCAT 从站有一个地址是 0x0130 的 AL Status Code 寄存器,里面记录了上次状态切换失败或异常的具体原因。CODESYS 的 EtherCAT 诊断页面里能直接看到这个码,常见的有 0x0001(无法初始化)、0x0012(无效邮箱配置)、0x001A(看门狗超时)等等。程序里也可以用 SDO 读取,不过我建议现场排查先用自带诊断页看,程序里还是以 WcState 和状态字为主,简单直接。
2.3 主站侧诊断:把 CODESYS 自带诊断页用起来
CODESYS 对 EtherCAT 的诊断支持其实挺全的,只是很多人没仔细看。打开设备树里的 EtherCAT 主站,切到“诊断”或“EtherCAT 从站诊断”页面,你能看到:
- 每个从站的当前状态和状态码;
- 每个从站的工作计数器匹配情况;
- 链路上的错误计数;
- 最近一次状态切换失败的原因。
我在调试阶段会一直开着这个页面,手动拔插从站,观察状态变化。这样能直观建立“断线在软件上的表现”的认知。等页面上的现象看明白了,再写程序监测就有的放矢。
如果你的控制器是 Linux 平台,比如在 RK3568 板子上跑 CODESYS Control for Linux,诊断逻辑完全一样,只是实时网卡驱动和性能优化要单独处理,EtherCAT 应用层的代码不用改。
3. ST语言状态监测功能块:从需求到完整代码
3.1 我们先说清楚这个功能块要回应哪些问题
在动手写代码之前,我把这个监测功能块的“需求清单”列出来,都是现场最常问的问题:
- EtherCAT 什么时候断的?断在哪个从站?
- 断了以后有没有人已经处理过?重连了几次?
- 断线发生以后,程序有没有第一时间把输出切安全状态?
- 链路自己恢复以后,要不要自动回到运行?还是必须等人确认?
- 万一重连多次都失败,能不能给出一个明确的故障信号,通知维护人员?
针对这五个问题,功能块的输入输出这样设计:
- 输入:使能信号、所有从站的 WcState 数组、主站当前状态字;
- 输出:当前运行状态码、故障从站编号、重连次数、安全停机信号、重配置请求信号、不可恢复故障锁存;
- 内部逻辑:断线滤波、状态锁存、重配置节奏控制、超时管理。
下面给出完整代码,你可以直接复制到一个新功能块里,然后在主程序里实例化调用。
3.2 完整ST代码(可直接复制到CODESYS里试)
FUNCTION_BLOCK FB_EtherCAT_Monitor VAR_INPUT xEnable : BOOL; // 功能块使能,TRUE投入监测 xResetAck : BOOL; // 人工确认复位(故障后按下) aWcState : ARRAY[0..7] OF BOOL; // 各从站WcState,按实际映射接入 nSlaveCount : UINT; // 实际从站数量 eBusState : WORD; // 主站/总线当前状态字,1=INIT 2=PREOP 4=SAFEOP 8=OP END_VAR VAR_OUTPUT eStatus : INT; // 0=正常 1=链路丢失 2=恢复中 3=故障 nFaultSlave : INT; // 故障从站索引,-1表示无 nReconnectCnt : UINT; // 断线重连累计次数 xSafeStop : BOOL; // 安全停机信号,断线后立即置TRUE xReconfigCmd : BOOL; // 重配置请求,上升沿触发主站重连 xFault : BOOL; // 不可恢复故障,需要人工复位 END_VAR VAR tonLossFltr : TON; // 断线确认滤波 tonWaitBeforeRe : TON; // 重连前等待 tonReconfigTime : TON; // 重连超时看门狗 xLinkLossMem : BOOL; // 断线锁存 xRecoveryActive : BOOL; // 恢复流程进行中 bAllOk : BOOL; // 所有从站WcState正常标志 i : INT; // 循环变量 tonLossFltrSet : BOOL; // 内部触发位 END_VAR VAR CONSTANT ETHCAT_ST_OP : WORD := 8; // OP 状态编码,按从站映射实际调整 T_LOSS_MS : TIME := T#20MS; // 断线确认滤波时间 T_RECON_WAIT : TIME := T#500MS; // 断线确认后等待多久发起重连 T_RECON_TIMEOUT : TIME := T#5S; // 重连超时时间 END_VAR然后是功能块正文:
// 扫描所有从站WcState,只要有一个异常就置bAllOk为FALSE bAllOk := TRUE; nFaultSlave := -1; IF xEnable THEN FOR i := 0 TO nSlaveCount - 1 DO IF NOT aWcState[i] THEN bAllOk := FALSE; // 记录第一个异常从站编号 IF nFaultSlave < 0 THEN nFaultSlave := i; END_IF END_IF END_FOR ELSE bAllOk := FALSE; END_IF // 状态分支1:尚未发生断线锁存时,正常监测 IF NOT xLinkLossMem THEN IF xEnable AND bAllOk AND (eBusState = ETHCAT_ST_OP) THEN // 一切正常 eStatus := 0; xSafeStop := FALSE; tonLossFltr(IN := FALSE, PT := T_LOSS_MS); ELSE // 疑似断线,持续20ms确认,避免偶发抖动误触发 tonLossFltr(IN := TRUE, PT := T_LOSS_MS); IF tonLossFltr.Q THEN xLinkLossMem := TRUE; // 锁存断线状态 xSafeStop := TRUE; // 立即安全停机 nReconnectCnt := nReconnectCnt + 1; END_IF END_IF ELSE // 状态分支2:已确认断线,走恢复流程 eStatus := 1; xSafeStop := TRUE; // 第一次进入恢复流程时,先等500ms,让总线链路稳定一下 IF NOT xRecoveryActive THEN xRecoveryActive := TRUE; xReconfigCmd := FALSE; tonWaitBeforeRe(IN := FALSE, PT := T_RECON_WAIT); END_IF // 等待完成后发出重配置请求 IF xRecoveryActive AND NOT xReconfigCmd THEN tonWaitBeforeRe(IN := TRUE, PT := T_RECON_WAIT); IF tonWaitBeforeRe.Q THEN xReconfigCmd := TRUE; eStatus := 2; // 进入恢复中 tonReconfigTime(IN := TRUE, PT := T_RECON_TIMEOUT); END_IF END_IF // 等待重连结果 IF xReconfigCmd THEN // 如果总线重新回到OP,且所有从站WcState都正常,认为恢复成功 IF bAllOk AND (eBusState = ETHCAT_ST_OP) THEN xLinkLossMem := FALSE; xRecoveryActive := FALSE; xReconfigCmd := FALSE; xSafeStop := FALSE; tonReconfigTime(IN := FALSE, PT := T#0MS); eStatus := 0; // 如果超时还没恢复,进入不可恢复故障 ELSIF tonReconfigTime.Q THEN xReconfigCmd := FALSE; xRecoveryActive := FALSE; xFault := TRUE; eStatus := 3; tonReconfigTime(IN := FALSE, PT := T#0MS); END_IF END_IF END_IF // 状态分支3:故障锁存后,必须人工确认才复位 IF xFault AND xResetAck THEN xFault := FALSE; xLinkLossMem := FALSE; xRecoveryActive := FALSE; xReconfigCmd := FALSE; xSafeStop := FALSE; eStatus := 0; END_IF3.3 逐段拆解:代码为什么这样写
先看第一段扫描逻辑。我用FOR循环遍历从站数组,只要有一个 WcState 为 FALSE,就把bAllOk置 FALSE,同时记录下第一个异常从站的编号。这里用nFaultSlave记录数据,是为了后续 HMI 报警时能直接告诉操作工“2号从站有问题”,而不是让他自己数。
再看断线确认这段。我自己在项目里踩过坑:直接把 WcState 异常当断线处理,结果 EtherCAT 恢复过程中的一个抖动周期就让整个系统停机。所以这里加了 20ms 的滤波时间。注意这个时间不是乱拍的,EtherCAT 周期如果是 1ms,20ms 就相当于 20 个周期持续异常才确认断线,足够滤掉偶发通信毛刺,又不会迟钝到影响安全。如果你做的是运动控制,轴在高速运行时建议把滤波时间适当缩短,比如 5~10ms,安全优先级更高。
最后是恢复流程。我特意在断线确认后加了 500ms 等待时间,这个细节很多人忽略。断线往往是物理扰动导致的,比如网线被踢松了一下、从站瞬间掉电恢复,链路刚恢复时电气信号还不稳定,立即发重连请求大概率还是失败。等 500ms,让链路先稳定下来,再启动重配置,实测成功率能提高不少。这个数字你可以根据现场情况调整,但我建议不要小于 200ms,也不要大于 2s,否则停机时间太长。
3.4 自动重连逻辑:不是无脑重启,而是有节奏地自救
代码里的xReconfigCmd不是直接去操作总线,它只是一个请求信号。真正干重连活的,是你在主程序里实例化的重配置功能块。CODESYS 的 EtherCAT 库不同版本功能块名字不太一样,常见的是FB_EtherCATReconfig或者在主站设备上直接调用ReconfigSlave方法。你只要在xReconfigCmd上升沿触发一次即可。
为什么要单独用一个输出信号而不是直接在监测功能块里调用重配置?一个功能块只做一件事,这样代码可读性和复用性都好。我在现场看到过有人把重连逻辑写成一个 500 行的巨型功能块,后期想改一个超时时间都要翻半天,完全没有必要。
重连超时我设置成 5 秒。EtherCAT 从站重新初始化,一般就是从 INIT 到 PREOP 再到 SAFEOP 最后 OP,正常 1 秒内能完成。如果 5 秒还回不来,基本可以断定不是瞬时干扰,大概率是硬件问题或者配置问题,这时候再无限重连已经没有意义,所以代码里置xFault,让维护人员介入。
4. 断线后的停机与恢复策略
4.1 停机策略:先停下来,再谈恢复
断线后的第一要务不是“恢复”,而是“安全停机”。代码里xSafeStop在断线确认后立刻置 TRUE,这份信号要接到你的运动控制和输出控制逻辑里。
建议这么做:
- 所有数字量/模拟量输出在断线异常时必须切断,或者输出到预设的安全值;
- 如果带伺服轴,把
xSafeStop接到轴的停止命令上,或者通过 Controlword 发快速停止指令; - 如果你的设备有抱闸,断线后要按电气图纸设计逻辑,该抱闸的立刻抱闸;
- 千万不要让程序在断线后继续按照旧坐标做插补,位置已经丢了,盲目运行就是撞机。
我曾经在一个搬运设备上吃过亏:当时只做了断线报警,没做安全输出切断,结果从站断线后 DO 模块保持上一次输出,气缸在中位“僵住”,后来从站重连成功,输出瞬间恢复,气缸直接弹出,差点打伤人。后来我在所有 DO 输出前面都加了“总线正常”这个软互锁,没这个信号,任何输出都不允许生效。
4.2 自动重连与人工干预如何配合
自动重连能解决“瞬时干扰”和“从站短时掉电”这两类问题,但不可能解决所有问题。所以代码里我设计了重连次数累计和不可恢复故障锁存。
具体配合原则:
- 第一次断线后,自动重连 1 次,很多情况能恢复;
- 如果重连失败,不要立刻继续重试,等人工检查一下物理链路;
- 连续多次重连失败,置
xFault,并且要求人工按复位按钮才清除锁存; - 重连次数要记录到掉电保持变量里,方便维护人员在第二天复盘。
还有一个细节:恢复成功以后,不要马上让设备全速跑起来。我的习惯是,重连成功后,先让设备回一次原点,确认位置和 IO 状态都没问题,再恢复自动流程。EtherCAT 断线意味着至少一个周期内主站对从站“失明”,哪怕数据恢复了,过程状态是否和断线前一致也是未知数。贸然接着跑,跟蒙眼开车没区别。
4.3 参数整定:滤波时间、重连次数、超时时间怎么定
这三个参数很多人不知道怎么调,我给一个参考范围:
| 参数 | 建议值 | 调整方向 |
|---|---|---|
| 断线确认滤波时间 | 10~50ms | 运动控制设备取小值,IO设备可适当取大值 |
| 重连前等待时间 | 200ms~2s | 现场振动大、电源波动多时增大 |
| 重连超时时间 | 3~10s | 从站数量多、初始化慢时增大 |
| 最大重连次数 | 1~3次 | 连续故障后建议不再自动重连 |
参数不是死的,要根据设备实际运行情况调。我见过一台设备,因为现场有大功率变频器频繁启停,EtherCAT 偶尔被干扰掉线又自动恢复,如果滤波时间设太短,一天能误触发好多次。后来把滤波时间调大到 50ms,配合屏蔽和接地整改,误触发基本消失。
5. 现场排查与避坑实录
5.1 典型故障现象与原因对照表
写程序只是第一步,真正难的是现场排查。我整理了这几年最常遇到的几种情况:
| 故障现象 | 可能原因 | 排查方向 |
|---|---|---|
| 固定某个从站 WcState 变 FALSE | 该从站电源异常、从站本身故障、从站与前站间网线松动 | 检查从站供电、换网线、观察该从站状态灯 |
| 全部从站 WcState 集体异常 | 主站网卡/驱动问题、主站侧网口松动、总线供电不足 | 检查主站网卡、主站侧水晶头、供电容量 |
| 重连后短时间内再次断线 | 电磁干扰、屏蔽层接地不良、网线质量差 | 检查网线屏蔽、布线走向、加磁环 |
| 断线后 WcState 一直为 TRUE 但机器不动作 | 从站状态退回 SAFEOP,过程数据停止但没报断线 | 检查从站状态字和 AL 状态码,看是哪个环节状态切换失败 |
| 上电后从站无法进入 OP | 从站 EEPROM 配置和主站扫描不一致、SDO 配置下发失败 | 重新扫描总线、核对从站配置、检查从站地址 |
5.2 排查步骤:从软件到硬件
现场遇到断线问题,我一般按这个顺序排:
第一步,看 CODESYS 诊断页。先不看程序逻辑,直接在主站设备下看从站状态、工作时间计数器、AL 状态码。这一步能确定是“链路断了”还是“从站状态错误”。
第二步,看物理链路。如果诊断页显示某个从站完全离线,那就从该从站往上游一段一段检查。EtherCAT 是菊花链拓扑,任何一个中间点断开,后面的从站全部离线。用一个简单方法:站在故障段中间位置,用手轻压水晶头,如果状态灯闪动,基本就是接触不良。
第三步,看现场干扰。排除硬件连接问题后,还有偶发断线,就要考虑电磁干扰。重点检查:
- 网线是否和动力线走在同一个线槽里;
- EtherCAT 网线屏蔽层是否单端可靠接地;
- 从站电源是否有独立供电,还是和电机驱动共用电源;
- 柜内是否有变频器、伺服驱动器靠近 EtherCAT 走线。
第四步,看程序逻辑。如果以上都没问题,再回头检查程序。有时候断线不是真的断,而是从站状态切换失败,程序里却还在等 WcState,诊断方向就错了。
5.3 独家心得:那些文档里不会写的细节
第一,记得给 EtherCAT 网线做标签。这听起来很土,但真的有用。现场所有网线两端都写清楚“主站 -> 1号从站”、“1号从站 -> 2号从站”,排查时能省一半时间。尤其设备使用几年后再去维护,没人记得线是怎么走的。
第二,WcState 数组不要直接在中断任务里做复杂处理。我见过有人把断线监测放在一个 250us 的中断任务里,还用了 FOR 循环和定时器,结果任务时间被拉长,反而影响了 EtherCAT 周期。建议单独用一个 1~5ms 周期的任务跑监测功能块,或者放在主循环里,滤波时间足够长,响应速度足够了。
第三,把重连次数和断线时间戳写到掉电保持变量里。CODESYS 里用 RETAIN 变量或者读写 CSV 日志都能做到。我习惯的是把nReconnectCnt和最近一次断线时间戳(用 UTC 时间)存到掉电保持区,这样维护人员第二天来看,能直接知道昨晚设备自己重连过几次。这个问题如果不去统计,你会觉得设备“好像没断过”,其实它已经偷偷断了好几次。
第四,施耐德、汇川这些基于 CODESYS 的控制器,EtherCAT 诊断接口和 ST 语法基本一致。你在一家平台上写的监测功能块,稍微改改映射变量就能移植到另一家。但要注意不同控制器的任务周期能力和 EtherCAT 库版本差异,功能块的输入输出结构不用大改。
最后再分享一个我自己的习惯:每次写完状态监测,我不会急着把设备跑起来,而是故意在运行中拔一次从站网线,看程序能不能按预期停机、能否自动重连、重连后会不会误动作。这个过程叫“故障演练”,比任何代码审查都管用。设备在现场出问题不可怕,可怕的是你的程序对故障毫无准备。这个监测功能块,就是你给程序加的第一道保险。