
OP 模式下插入 SDO 帧SOEM 与 IGH 的机制对比和四种工程解法写在前面这篇是给正在把 EtherCAT 主站往短周期里压的人写的。如果你只跑 4 ms 周期、偶尔读个温度随便怎么插都行如果你的周期已经压到 250 µs 甚至 125 µs还想在 OP 态下塞 SDO那下面每一条约束都得算清楚。一、先破除一个误解邮箱和 PDO 在硬件上是分开的很多人的第一反应是OP 态下做 SDO 会打断 PDO。这个担心大部分是多余的。EtherCAT 从站 ESC 里的 SyncManager 是物理分通道的SM方向用途典型长度SM0Master → Slave邮箱出CoE/SDO 请求128 / 192 / 256 BSM1Slave → Master邮箱入CoE/SDO 响应128 / 192 / 256 BSM2Master → Slave过程数据输出 RxPDO按 PDO 大小SM3Slave → Master过程数据输入 TxPDO按 PDO 大小SM0/SM1 跑邮箱SM2/SM3 跑 PDOESC 硬件层面各自独立不存在邮箱通信打断 PDO这回事。OP 态完全允许邮箱通信。真正会伤到 PDO 周期的只有两件事带宽——SDO 子报文挤在同一帧里发出去把帧撑大抬高T_ser吃掉周期预算阻塞——主站实现里等 SDO 响应的代码卡住了周期推进所有方案都是围绕这两点做文章。把这条记牢后面就不会被各种玄学说法带偏。二、先把账算明白SDO 到底有多慢2.1 邮箱是小的SDO 是碎的SDO 单帧不可能大。邮箱 SM0/SM1 典型只有 128–256 字节常见默认 192范围 50–256。所谓大 SDO指的是对象数据量大要切成很多段。扣掉邮箱头和 CoE 头的开销每个分段的真实有效载荷邮箱大小每分段有效数据128 B约 114 B192 B约 178 B256 B约 242 B2.2 分段传输是严格握手的数据超过 4 字节就走分段传输segmented transfer而且必须先收到 initiate segment 的响应才能发下一段。一个分段最少占 2 个周期写 SM0 读 SM1多数从站的应用层处理还要再加 1 个周期以上。于是吞吐公式非常简单SDO 吞吐 ≈ mailbox_payload / (k × T_cycle) k ≈ 2 ~ 3取决于从站响应速度2.3 算个实例传 1 MB邮箱 256 B、k2周期耗时125 µs约 1.1 秒250 µs约 2.2 秒1 ms约 8.7 秒4 ms约 35 秒这里有个反直觉但很重要的结论周期越短SDO 反而越快。因为每秒能跑的邮箱事务数更多。所以短周期让 SDO 更难做是个伪命题——短周期对 SDO 的吞吐是好事难的是别让 SDO 帧撑破周期预算和别让等待卡住周期。把这两件事分开想方案就清晰了。2.4 还有个隐藏的地雷分段下载期间邮箱通道会被这个事务占满PDO 映射、紧急报文0x1003、其他 CoE 服务全被堵住。如果从站应用逻辑依赖 PDO 实时刷新这段时间的控制质量会下降。所以大块传输最好放在停机或维护模式别在主站正常控生产时干。三、SOEM底层够用上层是同步的3.1 原生接口SOEM 的ec_SDOread()/ec_SDOwrite()是同步阻塞的内部循环调用ecx_srconfirm()发收邮箱帧并反复轮询从站的邮箱状态直到事务完成或超时。ecx_srconfirm()的语义很明确——blocking send-receive-confirm带自动重试专供非过程数据帧使用。它不是给实时周期任务准备的。3.2 三个致命陷阱陷阱一ec_receive_processdata(timeout)的 timeout 根本不生效。内核会把SO_RCVTIMEO按系统 tick 向上取整。250 Hz 系统下最小粒度就是 4 ms——你设 50 µs、100 µs实际都变成 4 ms。更糟的是部分驱动会把小 timeout向下取整为 0而 0 表示无限等待。结果就是设了超时函数却死等。陷阱二SMI系统管理中断。实测中 SMI 可以让 PC 数毫秒完全无响应这时候设什么超时都没用周期必崩。陷阱三网卡 coalescing。rx-usecs的聚合延迟可能比 SOEM 超时还长导致各种诡异行为。必须ethtool-Ceth0 rx-usecs0rx-frames1adaptive-rx offethtool-Keth0 rx-fcs on rx-all on3.3 底层其实支持并发只是上层没用值得肯定的是SOEM 的 nicdrv 层设计是支持多帧在途的ecx_inframe()是非阻塞的检查rxbufstat[idx]是不是EC_BUF_RCVD收到帧的 index 与请求的不匹配时只要帧有效且处于EC_BUF_TX状态就归档到rxbuf[idxf]并标记EC_BUF_RCVD供后续按 index 取用也就是说乱序、多帧在途、按 index 归档底层全都具备。问题出在上层 PDO API 是发一帧等一帧。3.4 SOEM 的正确姿势/* ---- 周期任务绝不等待 ---- */staticvoidrt_cyclic(void){/* 关键timeout 传 0立即返回 */intwkcec_receive_processdata(0);if(wkcexpected_wkc){/* 本周期数据有效 */}else{/* WC 不达标沿用上拍数据 告警周期照常走 */}/* ---- 你的控制算法 ---- */ec_send_processdata();/* 睡到下一周期hrtimer / rt_task 驱动 */}/* ---- 非实时线程SDO 放这里 ---- */staticvoid*nr_sdo_thread(void*arg){intszsizeof(val);ec_SDOread(slave,0x6064,0x00,FALSE,sz,val,EC_TIMEOUTRXM);returnNULL;}socket 创建时务必加SOCK_NONBLOCKsocket(PF_PACKET,SOCK_RAW|SOCK_NONBLOCK,htons(ETH_P_ECAT));结论SOEM 原生的 SDO 接口只能放非 RT 线程。要在 RT 循环里插邮箱必须自己写状态机基于ecx_mbxsend()/ecx_mbxreceive()每周期只推进一步。四、IGH天生就是异步的4.1 三组接口别用错组别接口时机特性配置组ecrt_slave_config_sdo8/16/32()activate 之前激活时统一下发最适合上电初始化阻塞组ecrt_master_sdo_upload/download()非实时线程阻塞超时建议 50–500 ms能拿 abort code异步组create_sdo_requestsdo_request_state实时周期专用非阻塞主站 FSM 慢慢处理只有第三组是给运行期实时任务准备的。前两组放进 RT 线程抖动是迟早的事。4.2 异步 request 骨架/* 1) activate 之前创建一个 request 一次只能处理一个事务 */ec_sdo_request_t*reqecrt_slave_config_create_sdo_request(sc,0x1018,0x01,4);ecrt_sdo_request_timeout(req,500/* ms */);/* 2) 非实时线程发起调用不阻塞 */ecrt_sdo_request_read(req);/* 3) RT 循环里只查状态绝不等待 */while(running){ecrt_master_receive(master);/* 非阻塞 poll */ecrt_domain_process(domain);switch(ecrt_sdo_request_state(req)){caseEC_SDO_REQUEST_BUSY:break;/* 下一周期再看 */caseEC_SDO_REQUEST_SUCCESS:handle_data(req);break;caseEC_SDO_REQUEST_ERROR:log_abort(req);break;default:break;}/* ---- 控制算法 ---- */ecrt_domain_queue(domain);ecrt_master_send(master);rt_task_wait_period(NULL);/* 周期由定时器决定与 SDO 无关 */}4.3 SDO 是怎么被发出去的IGH 内部维护一个外部 datagram 环形缓冲ext_datagram_ring32 槽FSM 把邮箱 datagram 排进去ecrt_master_send()打包时和 domain datagram 一起发出一次 ioctl 搞定。每个 datagram 带index、working_counter、jiffies_sent、skip_count跑状态机INIT → QUEUED → SENT → RECEIVED ↘ TIMED_OUT → 重新入队重试 ↘ ERROR超时阈值是EC_IO_TIMEOUT。关键帧没回来只会让某个 datagram 停在SENT、WC 不更新绝不会拖长周期。4.4 驱动形态决定一切Generic 驱动原生适配驱动收发路径内核 socketsendmsg/recvmsg直接 DMA 环形缓冲SKB每次收发分配/释放预分配环形零拷贝拷贝至少一次额外拷贝无适用开发调试生产部署、硬实时⚠️Generic 驱动不适用于 Xenomai 内核空间模式。只要关心周期确定性就必须上原生驱动——Generic 走内核协议栈抖动是毫秒级的多帧在途时会直接把周期打散。五、正面 PK维度SOEMIGHSDO 接口语义同步阻塞异步 requestRT 友好 阻塞配置期周期推进依赖receive返回会阻塞定时器驱动与收包解耦超时可靠性有坑tick 向上取整、驱动向下取整为 0状态机 jiffies 判定可靠多帧在途nicdrv 层支持index 归档上层不支持原生支持32 槽 ext ring邮箱事务失败处理需自己判返回值内置重试 abort code TIMED_OUT 状态驱动要求标准网卡即可但要关 coalescing原生驱动才能压到短周期改造成本中——需自写状态机低——异步 request 开箱即用适用周期≥ 500 µs 轻松 250 µs 需大幅改造可压到 100 µs 级一句话IGH 在架构上就赢了SOEM 不是不能用是得你自己把异步那一层补上。六、四种工程解法方案 A搭车piggyback做法SDO 子报文排在周期帧里一起发。这正是 IGH 的默认行为。┌─────────────────────────────────────────┐ │ LRW(PDO) │ FPRD(邮箱状态) │ FPWR(SDO) │ ← 一个帧 └─────────────────────────────────────────┘✅ 实现最省事IGH 天然支持❌周期帧长度随有无 SDO 而变→T_ser不恒定 → 抖动适用周期 ≥ 1 ms或对抖动不敏感的场景。不适用于125/250 µs 级短周期。方案 B固定邮箱槽位 ★ 短周期推荐做法无论有没有 SDO周期帧里恒定预留一个邮箱槽位。没 SDO 时填无害的 NOP/空 datagram。有 SDO│ LRW(PDO) │ FPRD │ FPWR(SDO) │ 无 SDO│ LRW(PDO) │ FPRD │ NOP(空) │ ← 长度完全一样✅帧长恒定 →T_ser恒定 → 时序完全可预测✅ 邮箱事务的节奏被量化成每周期固定 N 字节可精确计入预算❌ 每周期固定多花约slot_bytes × 0.08µs256 B 槽约 20 µs这是不影响 PDO 周期的正解。20 µs 的固定开销换来的是完全没有抖动。方案 C独立非周期帧 / 空闲窗口 ★ 大 SDO 推荐做法周期帧发完后在同周期的空闲窗口发独立的邮箱帧。IGH 里ecrt_master_send()管应用 datagramecrt_master_send_ext()管非应用 datagramwhile(running){ecrt_master_receive(master);/* 非阻塞回收 */ecrt_domain_process(domain);/* ---- 控制算法 ---- */ecrt_domain_queue(domain);ecrt_master_send(master);/* 周期帧大小恒定、最小化 */ecrt_master_send_ext(master);/* 空闲窗口非周期/邮箱帧 */rt_task_wait_period(NULL);}✅ 周期帧完全不受 SDO 影响✅ 大块数据可以在多个周期的空闲窗口里慢慢传❌ 前提T_send T_ext 裕量 ≤ T_cycle短周期下空闲窗口可能很小适用周期 ≥ 250 µs、SDO 数据量中等偏大。方案 D错开时序 ★ 超大块数据核心思路别在快周期 OP 下硬传换个时间传。阶段做法说明启动前 PREOPecrt_master_sdo_upload/download参数在这里配完PDO 还没跑整个周期都给邮箱用运行期少量参数异步 sdo_request每周期查 state一次只推进一步运行期大块临时降速切 4 ms 传完再切回 250 µsPDO 预算松了 16 倍运行期超大块退到 SAFEOP / PREOP完全独占总线临时降速是工程上最实用的一招切周期 → 传完 → 切回来代价是几百毫秒的控制质量下降但不用改架构、不用加硬件。⚠️ 注意有些对象只能在特定状态写。比如 PDO 映射对象0x1C12(RxPDO assign) /0x1C13(TxPDO assign) /0x1600/0x1A00通常只能在 PREOP 改。想在 OP 下改映射先退 PREOP。方案 E换个协议治本如果数据量真的很大别死磕 SDO 分段。协议单次块大小特点适用SDO 分段~200 B每段 ACK最慢小参数、几个对象SDO Complete Access整个对象一次读写完整对象轮次骤减结构化参数块FoE1488 BMTU 限序列号 CRC16 块级重传 流控窗口固件、大文件EoE标准以太网帧可跑 TCP/IP配置/诊断通道实测对比邮箱 256 BFoE 一帧带约 240 B 有效数据SDO 分段只有约 200 B 出头。对几百 KB 的数据这个差距会放大成几秒到十几秒。选 FoE 的时机从站有支持 FoE 的 Bootloader、允许停机、数据量 几十 KB。留在 CoE 的时机从站没有独立 Bootloader、必须在线升级不停机、固件要和配置参数打包一起传。七、决策树OP 态下要传 SDO │ ├─ 数据量 邮箱大小单帧搞定 │ └─ YES → 方案 B固定槽位IGH 用异步 request │ ├─ 周期 ≥ 1 ms │ └─ YES → 方案 A直接搭车最省事 │ ├─ 周期 250 µs ~ 1 ms数据量中等 │ └─ YES → 方案 B 或 C │ ├─ 周期 250 µs │ ├─ 预算够 → 方案 B固定槽位严格算帧长 │ └─ 预算不够 → 方案 D临时降速 / 退 PREOP │ └─ 数据量 几十 KB └─ YES → 方案 EFoE 方案 D停机传八、红线与检查清单绝对不能碰的红线ecrt_master_send()到ecrt_master_receive()之间绝不插入 printf、mutex、阻塞 IO。一旦周期超时轻则总线抖动重则从站进 SafeOP 报看门狗。这类问题在代码评审阶段极难发现通常到设备挂到现场才暴露。实时路径只做数据搬运和业务状态机。上线前逐条核对周期源与通信解耦——节拍来自rt_task_wait_period/ hrtimer绝不依赖receive的返回时刻周期帧长度恒定——不管有没有 SDO字节数一模一样数据有效性靠 WC 判定不靠等待——IGH 查ecrt_domain_state()的wc_stateSOEM 比较ecrt返回的 wkc 与ec_group[0].outputsWKCSDO 阻塞调用只在非 RT 线程——ec_SDOread、ecrt_master_sdo_upload/download一律不放周期任务大块 SDO 一次只推进一步——异步状态机每周期最多一个邮箱事务关掉网卡 coalescing——ethtool -C eth0 rx-usecs 0 rx-frames 1 adaptive-rx offIGH 短周期必须上原生驱动——Generic 驱动抖动毫秒级DC 对齐收发窗口——SYNC0 落在收完上帧、发下帧之前的窗口内SYNC0 周期不得超过主站实际周期否则误报 AL Status 0x001A给帧重复留预算——ETG 建议同周期重复发送相同帧以提升鲁棒性至少支持 3 个相同帧/周期。开了就是 3 倍带宽预算表里必须留位置总线独占——环上出现其他主站或无关 EtherCAT 帧两边都会因 index 不匹配而丢弃或误判白白消耗带宽九、总结回到开头那个判断邮箱和 PDO 在硬件上是分通道的OP 态插 SDO 根本不是能不能的问题而是怎么插才不撑破帧、不卡住周期的问题。三句话收尾带宽账——Σ帧字节 ≤ 12.5 × T_cycleµs这是 100 Mbps 的物理上限超了任何软件技巧都救不回来确定性账——周期帧长度必须恒定这是短周期下唯一不能妥协的东西架构账——IGH 的异步 sdo_request 开箱即用SOEM 得自己补状态机但补完之后效果等价最后一句经验之谈带宽不够时别想着更快地把字节推上去要想着哪些字节根本不需要在这个周期推。对于 SDO答案通常更简单——换个时间推。