简介基于C#实现的EtherCAT主站框架程序是一份面向工业自动化开发者与上位机工程师的工程源码包主要用于在.NET环境下实现EtherCAT协议主站功能涵盖数据链路层帧处理、从站管理、实时数据交换与同步等核心机制可帮助快速搭建主站原型或研究协议栈实现。资源包共289个文件压缩包大小约1.48MB以dll运行库、cs源码、csproj工程文件、json配置、pdb调试符号为主另含WPF界面文件xaml/baml与单元测试工程结构清晰便于编译调试与二次开发。目前已有105人浏览学习。该框架在协议实现之外还考虑了与MES/ERP等上层系统的集成接口、UI设计以及安全与异常处理能够为初学者提供完整的工程参考也为进阶开发者节省从零搭建主站的工作量是学习C#工业通信开发的一份实用资料。1. 用 C# 写 EtherCAT 主站这不是玄学是产线在用的事实很多人第一次听「用 C# 写 EtherCAT 主站」的第一反应是实时协议用托管语言这不是玄学吗EtherCAT 周期 1ms、同步抖动要微秒级C# 有 GC、有 JIT怎么可能稳。但实际产线上大量运动控制上位机就是 C# 写的如果主站能直接以类库形式嵌进上位机就不用中间再挂一层转接设备。这个框架源码包里的 EthercatMaster 工程做的就是这件事把网卡收发、报文封装、从站状态机、PDO 映射、DC 同步这些主站该干的活用 C# 实现并暴露成一组可控的类接口。适合三类人做运动控制上位机的工程师、想研究 EtherCAT 协议但不想从 C 语言啃起的开发者、以及需要在 Windows 环境下验证从站设备的产线测试人员。2. EtherCAT 主站的分层骨架四层架构与一条 1ms 周期数据通路拿到这个项目源码先别急着点运行。先把框架的分层搞清楚否则后面改任何一行都不知道会影响谁。我拆过不少主站代码无论 C 还是 C#最后都会落到四层网卡抽象层、数据链路层、状态机与配置层、应用接口层。这个框架能跑通不是因为某个类写得巧妙而是这四层边界切得干净周期任务里每一行代码都知道自己在第几层。2.1 四层架构与各自的边界主站框架的分层可以用一张表说清每一层只对上层暴露最少接口。层级核心职责对应类型网卡抽象层打开/关闭网卡、发送原始帧、接收原始帧、过滤指定 EtherTypeNetAdapter 类数据链路层EtherCAT 报文头组包/拆包、三种寻址方式、WKC 校验Datagram 与 FrameBuilder 类状态机与配置层从站 INIT/PREOP/SAFEOP/OP 切换、SDO 邮箱读写、FMMU/SM 配置、DC 时间同步SlaveStateMachine 与 MailboxProcessor 类应用接口层周期任务回调、PDO 数据上下行接口、断线重连策略CycleEngine 与 PdoChannel 类为什么必须这样分层核心原因是 EtherCAT 主站的周期任务只有一条在一个周期里完成组帧、发帧、收帧、解帧、映射。如果这些逻辑全堆在同一个类里后面调 DC 同步和 PDO 映射时改动边界会互相传染改一行数据链路层的代码应用层跟着崩。分层之后网卡层换实现比如从 pcap 封装换 raw socket上层代码一行不用动从站从 A 型号换成 B 型号只需要改配置层的对象字典参数。2.2 周期任务模型一个线程跑完收、发、解、映射主站的命脉在线程模型。常见做法是双线程一个高优先级线程跑周期任务另一个低优先级线程处理邮箱SDO等慢速请求。周期线程里不能出现任何阻塞调用连日志输出都最好别放。框架的 CycleEngine 核心循环大致如这段代码// 周期任务主循环默认 1000us 周期用高精度计数器做相位对齐 public void RunCycle(int cycleUs) { var sw Stopwatch.StartNew(); long nextTick 0; while (!_cancelled) { long nowUs sw.ElapsedTicks * 1_000_000L / Stopwatch.Frequency; if (nowUs nextTick) { // 这里不用 Thread.Sleep避免线程切换引入的毫秒级抖动 SpinWait.SpinUntil(() sw.ElapsedTicks * 1_000_000L / Stopwatch.Frequency nextTick); } SendAndReceive(); // 组帧 - 发送 - 接收 - 解帧 UpdatePdOutputs(); // 把应用层的输出数据写入过程数据区 nextTick cycleUs; } }逻辑说明Stopwatch 基于主板高精度事件计数器换算成微秒做绝对时间刻度nextTick 是下一帧的绝对时间点。每次循环先算当前时间没到点就自旋等待到点后执行一帧完整的收发然后把应用层输出数据映射进发送缓冲区。SpinUntil 会占满一个 CPU 核但在实时周期任务里这是值得的——用 CPU 换稳定周期产线上没人会在乎一个核的空转。参数说明cycleUs 单位是微秒典型 EtherCAT 周期是 1000 或 250nextTick 用累加而不是每次用当前时间加周期是为了避免累计的调度延迟不断后移。这里有个常见误用有人用 DateTime.Now 做时间基准它的分辨率只有 15ms 左右跑不了微秒级周期谁用谁翻车。2.3 报文封装与解包Datagram 类的核心字段EtherCAT 报文看起来复杂其实一个 Datagram 就 10 字节头 数据 2 字节工作计数器WKC。框架里对应 Datagram 类我一般建议先把报文头的长度和命令两个字段看熟。// EtherCAT Datagram 头的组包共 10 字节 public byte[] BuildHeader(byte command, ushort slaveAddr, ushort offset, ushort dataLen) { byte[] hdr new byte[10]; hdr[0] command; // 命令类型0x04 逻辑读, 0x06 逻辑写, 0x02 配置读等 hdr[1] 0; // 索引号每个周期递增用于匹配请求和响应 hdr[2] (byte)(slaveAddr 0xFF); // 从站地址低字节配置寻址时 hdr[3] (byte)(slaveAddr 8); // 从站地址高字节 hdr[4] (byte)(offset 0xFF); // 从站寄存器/内存偏移低字节 hdr[5] (byte)(offset 8); hdr[6] (byte)(offset 16); hdr[7] (byte)(dataLen 0xFF); // 数据长度字段 hdr[8] (byte)(dataLen 8); hdr[9] 0; // 保留位固定填 0 return hdr; }逻辑说明header 里最关键的是 command 和 offset。配置寻址时slaveAddr 是站地址offset 是从站内部的寄存器地址比如 AL Control 的 0x0120逻辑寻址时slaveAddr 字段被忽略offset 换成主站逻辑地址空间的偏移。框架在发送前会根据寻址类型重写这两个字段外部调用者不用关心。参数说明dataLen 表示该 datagram 携带的数据字节数最大 4096整个 EtherCAT 帧里可以塞多个 datagram但总长不能超过以太网帧的 1518 字节所以实际工程里一个周期帧往往由「逻辑读 逻辑写 若干配置读写」组成组帧的顺序决定了从站响应的排列。WKC工作计数器是收帧后最重要的校验依据——每个 datagram 期待响应方把 WKC 加 1主站收到帧后必须校验 WKC 是否符合预期如果多个周期 WKC 都不达标说明从站没处理你的帧这时候优先看从站状态和错误寄存器而不是盲目重发。3. 从零搭最小主站状态机切换、周期发送与第一个可跑的 Demo分层理解透了接下来拿框架里的最小示例工程走一遍。跑通这个你就有了一张「还能不能继续调」的底牌。最小主站只需要三样东西一块能发原始帧的网卡、一套能正确切换从站状态的流程、一个稳定的周期循环。3.1 网卡初始化与原始帧收发EtherCAT 不像 TCP/IP它不经过操作系统的协议栈主站程序要自己打开网卡、发原始以太网帧。在 C# 里常见做法是引用 pcap 封装库直接操作网卡也有用 raw socket 的我一般首选 pcap 封装——它在 Windows 下兼容性好过滤规则也简单。初始化步骤// 打开网卡并设置 EtherCAT 帧过滤规则 var device PacketDeviceList.GetAllDevices() .FirstOrDefault(d d.Name.Contains(EtherCAT)); var conn device.Open(DeviceMode.Promiscuous, 1000); conn.SetFilter(ether proto 0x88A4); // 只收 EtherCAT 类型帧减少 CPU 开销逻辑说明打开网卡后要把它设成混杂模式否则网卡只按目的 MAC 收包而 EtherCAT 帧经常用广播地址不设混杂模式会漏帧。SetFilter 这行很关键——把无关帧直接丢在驱动层周期任务就少很多干扰。参数说明DeviceMode.Promiscuous 是混杂模式超时参数 1000 表示接收超时 1 秒这只是读超时上限实际周期任务用阻塞读或轮询。注意绑定的网卡必须支持 1000Mbps 全双工百兆网卡的帧间隔抖动会明显放大。常见做法是在打开网卡前先检查驱动设置把节能模式、中断调节关掉具体细节留到避坑章说。网卡选型上Intel 芯片的服务器网卡比瑞昱的消费级网卡稳得多尤其是 DC 同步场景建议直接选带双口的企业级网卡。3.2 从站状态机切换INIT 到 OP 的完整序列EtherCAT 从站要进 OP 得经历 INIT → PREOP → SAFEOP → OP。框架里把这一步封装成 SlaveStateMachine核心还是写 AL Control 寄存器0x0120再读 AL Status0x0130确认。关键代码// 请求从站切换到目标状态 public EtcState RequestState(uint slaveAddr, EtcState target) { // 状态码INIT1, PREOP2, SAFEOP4, OP8 WriteToSlave(slaveAddr, 0x0120, new byte[] { (byte)target }); // 轮询 AL Status 寄存器直到从站确认或超时 var sw Stopwatch.StartNew(); while (sw.ElapsedMilliseconds 1000) { var status ReadFromSlave(slaveAddr, 0x0130, 2); var current (EtcState)(status[0] 0x0F); if (current target) return current; if ((status[1] 0x01) ! 0) // 错误位被置起 { // 读错误寄存器 0x0F00常见原因邮箱没初始化/看门狗超时 var err ReadFromSlave(slaveAddr, 0x0F00, 1)[0]; throw new EtcStateException($切换失败, 错误码 0x{err:X2}); } } throw new TimeoutException(状态切换超时); }逻辑说明写 AL Control 只是「请求」从站内部要经过一系列初始化动作检查邮箱、检查输入输出数据等才会在 AL Status 里确认。所以写完之后必须轮询状态寄存器。错误位在第二个字节的第 0 位置位时要去读 0x0F00 错误寄存器代码里把错误码抛出来省得在黑匣子里猜。参数说明超时 1000ms 对冷启动够用从 INIT 切 PREOP 之前要先完成邮箱配置SM0/SM1PREOP 切 SAFEOP 之前要先配好 FMMU 和 SM2/SM3否则从站会拒绝切换。顺序错了状态就停在原地这是接手主站框架最常踩的坑——后面避坑章专门说。3.3 周期任务里的看门狗掉线的最后一道防线主站周期任务跑起来后最怕两件事从站没回应、从站进了错误状态但主站没察觉。框架里周期循环发完一帧后会检查收到的 WKC 数量连续 N 帧不达标就触发看门狗回调。// 周期收帧后的 WKC 校验与掉线计数 private void CheckWatchdog(WkcResult result, int allowedLossLimit) { if (!result.AllDatagramsAcked) { _lostFrameCount; if (_lostFrameCount allowedLossLimit) { _onSlaveLost?.Invoke(result); // 触发掉线回调停下轴、亮红灯、记录时间戳 _lostFrameCount 0; } } else { _lostFrameCount 0; // 恢复响应立刻清零避免累计误报 } }逻辑说明所有 datagram 都拿到预期 WKC 才算一帧成功连续掉线超过上限再触发回调而不是丢一帧就停避免瞬时网络毛刺把产线打断。回调里做的事通常是轴暂停和报警记录。参数说明allowedLossLimit 一般取 35。触发回调后主站要不要自动重连取决于应用场景伺服轴在跑就不能自动切状态否则位置会飞过程数据类应用可以尝试重新初始化。4. 把 PDO 映射和 DC 同步做进框架过程数据对齐到微秒级的三个关键配置最小主站能跑 OP 之后工作重点是过程数据和同步。EtherCAT 和普通现场总线的最大区别在于主站把所有从站的过程数据映射到一段连续的逻辑地址空间里用一条逻辑读/逻辑写帧就完成全线的数据交换。这一步配置乱了从站哪怕状态是 OP数据也不会动。4.1 FMMU 与 SyncManager 的配置顺序先讲概念。每个从站内部有一块物理内存过程数据区FMMU 的作用是把它映射到主站的逻辑地址段SyncManager 管理的是这块内存的读写权限和方向。主站侧不用关心每个从站物理地址在哪只需要为每个从站配一个 FMMU把它的过程数据区映射进自己的逻辑地址空间然后在周期帧里对这段逻辑地址做全局读写。配置顺序很严格先写 SMSM2 做输出、SM3 做输入再写 FMMU最后才能切 SAFEOP。代码示例// 配置每个从站的 FMMU把从站 0x1000 起 8 字节过程数据映射到逻辑地址 0x1000 起 public void ConfigureFmmu(uint slaveAddr, uint logicalAddr, uint physicalAddr, byte length) { byte[] fmmu new byte[16]; byte[] la BitConverter.GetBytes(logicalAddr); byte[] pa BitConverter.GetBytes(physicalAddr); fmmu[0] la[0]; fmmu[1] la[1]; fmmu[2] la[2]; fmmu[3] la[3]; fmmu[4] (byte)(length - 1); // 长度字段实际长度减 1 fmmu[5] 0; // 逻辑起始位从第 0 位开始 fmmu[6] pa[0]; fmmu[7] pa[1]; fmmu[8] pa[2]; fmmu[9] pa[3]; fmmu[10] 0; // 物理起始位 fmmu[11] 0; // 类型0 表示读写 fmmu[12] 1; // 激活位1 启用该 FMMU WriteToSlave(slaveAddr, 0x0600, fmmu); // 第一个 FMMU 从 0x0600 开始 }逻辑说明FMMU 寄存器从 0x0600 起算每个 FMMU 占 16 字节写完后从站会在下一个周期把物理地址的内容同步到逻辑地址空间。如果 FMMU 没激活周期帧里的逻辑读数据永远是零。参数说明length 单位是字节寄存器里存的是 length-1logicalAddr 必须在整个总线范围内唯一两个从站映射到同一个逻辑地址数据帧就会互相污染physicalAddr 通常填从站过程数据区起始地址 0x1000具体要看从站 EEPROM 里的配置。整条总线所有 FMMU 映射完成后周期帧里加一个逻辑读、一个逻辑写 datagramWKC 期望值等于参与映射的从站数量。WKC 不对说明有从站没被这段地址覆盖优先检查 FMMU 物理地址与长度设置。4.2 PDO 映射配置写对象字典的正确姿势FMMU 解决的是「数据放哪」PDO 映射解决的是「对象字典里的变量按什么顺序放进过程数据区」。这部分走 CoE 邮箱。框架里封装了一个 SdoWrite 方法背后是邮箱数据报 SDO 请求头// 配置 RxPDO 映射把 0x6040 控制字映射到过程数据第 0 个字 public void MapRxPdo(uint slaveAddr) { // 先清零 0x1C12RxPDO 分配再写映射数量 SdoWrite(slaveAddr, 0x1C12, 0x00, new byte[] { 0x00, 0x00 }); SdoWrite(slaveAddr, 0x1C12, 0x01, new byte[] { 0x00, 0x16 }); // 指向 0x1600 // 0x1600 sub 0 写映射条目数量sub 1 写第一条映射 // 32 位映射字段index(16位) subIndex(8位) bitLength(8位) byte[] entry BitConverter.GetBytes(0x6040); // 控制字对象索引 byte[] mapped new byte[4]; mapped[0] entry[0]; mapped[1] entry[1]; mapped[2] 0x00; // 子索引 0 mapped[3] 16; // 位长度 16 位 SdoWrite(slaveAddr, 0x1600, 0x01, mapped); }逻辑说明每个从站的 PDO 映射写在它自己的对象字典里0x1C12 决定哪些 PDO 被激活0x1600 这类对象定义具体映射条目。主站写好这些之后从站会按映射关系把 0x6040 的值自动放到过程数据区对应位置后面周期帧读写过程区时就不需要再走 SDO这也是 EtherCAT 周期数据能跑到微秒级的根基。参数说明映射字段里 32 位布局0x6040 是标准伺服驱动协议里的控制字sub 0 子索引固定为 0位长度 16 表示占用一个 word。每个从站支持的最大映射条目数写在 0x1600 的 max sub-index 里超了会返回 SDO abort。注意这里有个高频翻车点有些从站需要先写 0x1600 的条目数到 sub 0再写映射条目顺序反过来从站会拒绝。SDO 失败别光重试要读 abort code——通常是 0x06090030 之类直接告诉你「对象不存在」还是「子索引越界」。4.3 DC 同步与漂移补偿DC 同步是整个框架里最讲究的部分。所有从站共享同一个总线时钟主站周期帧在总线上传播时每个从站会记录帧到达的本地时刻DC 模式的要点是让从站的输入/输出锁存与 SYNC0 信号对齐。主站要做的读从站 DC 时间0x0900算出与主站本地时间的偏差然后微调周期发送时刻消除漂移。// 读取从站 DC 时间并计算与主站本地时间的偏差 public long ReadDcOffset(uint slaveAddr) { // DC 时间寄存器 0x09008 字节单位纳秒 byte[] raw ReadFromSlave(slaveAddr, 0x0900, 8); long slaveDcTime BitConverter.ToInt64(raw, 0); long localTime Stopwatch.GetTimestamp() * 1_000_000_000L / Stopwatch.Frequency; return localTime - slaveDcTime; // 正值表示从站时钟偏慢 }逻辑说明EtherCAT 的 DC 时间是一个全局纳秒计数器主站本地时间用高精度计数器换算成纳秒后与从站对比。偏差会随温度漂移累积不能只校一次常见做法是每 1000 个周期做一次 PI 补偿把偏差保持在几百纳秒内。补偿动作是微调下一帧的发送时刻——提前或延后几个微秒而不是改周期长度。参数说明0x0900 读回的是从站接收到的最近一个 DC 时间参考用 64 位纳秒表示换算成周期时要小心溢出int 存不下。PI 参数没有万能值我一般先做一次开环测量记录偏差曲线再根据斜率定 KpKi 给 Kp 的十分之一左右。DC 调好后从站的模拟量或编码器数据会明显看到抖动变小这也是判断 DC 有没有生效的最快方式。5. 框架实战避坑五个高频故障与排查方法跑框架和看框架是两回事。下面五条是我在几个模拟项目 X 的现场调试里反复踩过的坑每条按现象、原因、解决给出来多数都可以在半小时内定位。如果你遇到的不在这五条里优先去看从站错误寄存器 0x0F00 和 AL Status 的 0x0134这两个寄存器能过滤掉一半以上的疑难杂症。5.1 周期任务偶发丢帧从站报看门狗超时现象主站周期运行几分钟或几小时后某个从站突然报看门狗超时重新初始化后恢复日志里丢帧不成规律有时一串丢三四帧。原因网卡的中断调节和节能特性。大量网卡驱动默认启用中断聚合帧到达后不是立刻通知 CPU而是攒一批再中断。周期任务下累积的中断延迟会在某些高负载瞬间超过从站看门狗阈值从站就误认为主站掉线。解决在系统侧把该网卡的中断调节关掉速率和双工固定为 1000Mbps 全双工关闭节能以太网。改完丢帧曲线会明显变平直。建议在网卡设置里把「接收缓冲区」调小到 128 或 256减少帧在驱动层的排队时间。这个坑的特征是「换台电脑就没问题」本质是不同网卡驱动的默认策略不同。5.2 状态机停在 PREOP切不到 SAFEOP现象写状态请求后轮询 AL Status从站一直停在 PREOP错误位偶尔置起读错误寄存器是 0x0010邮箱看门狗超时一类。原因最常见的是配置顺序错了。从 INIT 切 PREOP 必须先把邮箱的 SM0/SM1 配置写入PREOP 切 SAFEOP 必须先把 FMMU 和 SM2/SM3 配好。有人图省事先把所有寄存器都写了但某个 PDO 映射对象被漏掉从站检查不通过状态就顶住不动。解决严格按「INIT → 写 SM0/SM1 → 切 PREOP → 写 PDO 映射 → 写 FMMU/SM2/SM3 → 切 SAFEOP → 切 OP」的顺序来。每步都轮询确认切不过去就按 0x0134 状态寄存器的错误码去查从站手册。我在框架里专门加了一个 DebugOptions.LogStateSteps 开关把每步写入和确认结果打到日志排查时别把它关掉。5.3 周期抖动从 20us 放大到 100us现象示波器看 SYNC0 信号间隔不是均匀的 1ms而是忽大忽小最大值接近 100us主站 CPU 占用率不高。原因周期线程的调度优先级不够或者周期等待用了 Thread.Sleep(1)。Sleep 的精度在 Windows 上只有 15ms 左右虽然传 1ms实际会睡过头加上系统线程切换抖动自然大。框架设计里虽然用了 SpinWait但外层还有代码在周期任务里偶然做了日志输出、字符串拼接也会放大抖动。解决给周期线程设置最高优先级把日志输出移到另一个线程周期循环里禁止任何内存分配和 I/O如果要用 Sleep 做降载配合 timeBeginPeriod(1) 先把系统时钟精度提到 1ms 再说。改完再用总线负载统计脚本验证具体方法见下一章。5.4 DC 同步偏移持续增大从站间相位差拉开现象从站 1 和从站 2 的同步输入信号边沿相位差从几十纳秒慢慢拉到几百纳秒最后超过同步窗口报警。原因DC 补偿只做了一次没有周期校正。主站本地时钟和从站 DC 时钟本身都有温漂晶振频率差每小时可能累计几十微秒的误差。温度变化越大漂移越快。解决改成周期性补偿。每 500 或 1000 个周期重读从站 DC 时间做一次偏移修正修正量用 PI 控制平滑输出不要一步到位否则会造成更大的跳变。注意补偿值有上限超过某个阈值就报警说明晶振坏了或时钟源配置不对。5.5 C# GC 在周期中途触发导致帧间隔突变现象系统运行半小时后某一次周期帧间隔突然变大之后恢复多次触发的间隔没有规律帧间隔突变的时刻和 GC 日志吻合。原因托管语言逃不过 GC。周期循环里如果创建了临时数组、用了 LINQ 或字符串拼接GC 会在分配达到阈值时触发full GC 一次几十毫秒对 1ms 周期是灾难。这是 C# 写主站最大的「玄学」——不是协议复杂而是运行时在拖后腿。解决周期循环里零分配。所有帧缓冲区、PDO 映射数组在启动时一次性分配循环里只复用避免装箱、避免 LINQ必要时用 Server GC 加关闭 concurrent GC让 GC 在后台线程跑减少对周期线程的干扰。我在框架里把发送缓冲区和接收缓冲区都做成预分配池就是为了这一条。6. 进阶验证技巧用帧间隔统计验收主站的实时性前面调完那么多参数怎么验收不能光看从站没有报警要把总线上的真实帧时刻抓出来统计。我一般用 pcap 在混杂模式下同时抓主站发出的每一帧记录帧的时间戳然后算相邻帧间隔的分布。这个脚本比任何「感觉顺畅」都靠谱。// 捕获 N 帧 EtherCAT 帧统计帧间隔的均值、最大值和抖动 long[] timestamps new long[captureFrameCount]; int count 0; conn.OnPacketArrival (sender, e) { if (count captureFrameCount) timestamps[count] e.Timeval.Date.Ticks; // 只存时间戳不存帧内容 }; while (count captureFrameCount) Thread.Sleep(10); // 等待捕获线程收集数据 double maxIntervalMs 0, totalMs 0; for (int i 1; i count; i) { double diffMs (timestamps[i] - timestamps[i - 1]) / 10.0; // Ticks 转微秒 totalMs diffMs; if (diffMs maxIntervalMs) maxIntervalMs diffMs; } double avgMs totalMs / (count - 1); Console.WriteLine($平均间隔 {avgMs:F1}us, 最大 {maxIntervalMs:F1}us, 抖动 {maxIntervalMs - avgMs:F1}us);逻辑说明统计帧间隔比听从站报警更早暴露问题。平均间隔约等于周期设置值最大值代表最坏情况下的延迟。如果最大值和平均值之差稳定在几十微秒内说明主站调度合格超过 100us 就要回去查线程优先级和 GC。我习惯用三个指标验收平均帧间隔应该落在设定周期的 ±5us 内偏差大说明时间基准换算有误最大帧间隔与平均值之差这是抖动控制在 50us 内是基本盘DC 同步场景要压到 20us 以内WKC 达标率统计 N 帧中所有 datagram 都拿到预期 WKC 的比例要求 100%哪怕 99.99% 也说明有偶发丢帧要追原因。把测量脚本固化成一个启动参数每次换网卡驱动、换机器、更新运行库之后先跑一遍这个统计再上产线。有一回我图省事跳过脚本直接上了产线结果换了一台工控机后帧间隔抖动从 30us 涨到 180us现场用了两天才定位到是网卡驱动的默认中断节流被打开了。从那以后我每次换环境都强制走一遍帧间隔统计跑不过阈值就不往下走。这套方法同样适合你们手里的 EthercatMaster 改造希望帮到你。本文还有配套的精品资源点击获取