1. SOEM 是什么:一个库撑起整个 EtherCAT 主站
EtherCAT 主站要干的活不少:扫总线、读从站 EEPROM、配 FMMU/SM、周期收发过程数据、处理邮箱协议、管 DC 时钟。商用主站(TwinCAT / EC-Master)把这些封装成黑盒,而 SOEM(Simple Open EtherCAT Master)用纯 C 把它们全部开源——OpenEthercatSociety(B&R 主导的开源组织)维护,MIT 协议随便商用,GitHub 仓库 OpenEthercatSociety/SOEM。
- 轻:核心代码几千行 C,无 C++ 运行时依赖,MCU / DSP / ARM 裸机都能跑(配 osal 层);
- 跨平台:Linux(raw socket,可配 PREEMPT_RT)、Windows(Npcap/WinPcap)、裸机(RT-Kernel 直接驱动以太网 MAC);
- 透明:状态机、FMMU 分配、DC 补偿全部可见可改,适合学习和定制;
- 生态:EtherCAT 官方一致性测试常拿 SOEM 做基准主站,社区资料多。
代价也明确:它不提供 TwinCAT 那样的上位机 IDE、示波器和一键配置,PDO 映射、DC 参数、实时调度都要自己写——这正是本文要带你看懂的部分。
2. 目录结构:六个源码文件一眼看穿
SOEM 的 src/ 目录核心就六个 ethercat*.c,按功能分工非常清晰:
| 文件 | 职责 | 关键函数(前缀) |
|---|---|---|
| ethercatbase.c | 最底层:帧收发、EtherCAT 帧结构、8 位地址 / 广播寻址 | ecx_setupdatagram / ecx_send_sync / ecx_receive_sync |
| ethercatmain.c | 主站主体:全局 ec_slave[]、状态机、ec_init、主循环 | ecx_init / ecx_config / ecx_send_processdata |
| ethercatconfig.c | 总线配置:扫描从站、自动寻址、FMMU / SM 生成、IOmap 分配 | ecx_config_init / ecx_config_map / ecx_config_map_group |
| ethercatcoe.c | CoE 邮箱:SDO 读写、对象字典(0x1600/0x1A00 映射就是经它下发) | ecx_coe_read / ecx_coe_write / ecx_coe_param |
| ethercatdc.c | DC 分布时钟:参考时钟选择、Sync0 周期、漂移补偿 | ecx_configdc / ecx_dcsync0 / ecx_dcsync01 |
| ethercatfoe.c 等 | FoE 固件升级、EoE 以太网、SoE 等附属协议 | ecx_FOEread / ecx_FOEwrite 等 |
剩下 osal/(操作系统抽象层:socket 封装、互斥锁、计时——跨平台的全部差异收敛在这一层)和 include/ethercat.h(对外唯一头文件,ec_xxx 前缀 API 在此声明,内部转发到 ecx_ 实现)。写应用只需要包含 ethercat.h。
3. 六步跑通一个主站:最小流程全景
任何 SOEM 应用都是这六步。先给全景(每步 API + 总线状态变化),后三节逐个源码走读:
| # | 动作 | API | 总线状态变化 |
|---|---|---|---|
| 1 | 打开网卡、绑定 socket | ec_init(ifname) | 无(本机网络层) |
| 2 | 扫描总线:自动寻址、读每个从站 SII(EEPROM)、解析 PDO 配置 | ec_config_init(FALSE) | 从站 → PREOP |
| 3 | 生成 FMMU / SM 配置、分配 IOmap、映射过程数据 | ec_config_map(&IOmap) | 从站 → SAFEOP |
| 4 | 配置分布时钟(DC 参考时钟 + Sync0) | ec_configdc() | DC 同步建立 |
| 5 | 请求 OP:置 OP 状态并等待所有从站确认 | ec_writestate(0) + ec_statecheck | 从站 → OP |
| 6 | 周期循环:发 / 收过程数据 + 状态监控 | ec_send_processdata / ec_receive_processdata / ec_readstate | OP 持续运行 |
其中第 3 步最关键——它把本专题 EEPROM / PDO / SM·FMMU 三篇文章的概念一次性落地:主站读从站的 PDO 映射(0x1600 / 0x1A00),换算成 FMMU 寄存器值写回去,建好"逻辑地址 → 从站物理内存"映射表,再给每个从站的过程数据在 IOmap 里分配偏移。第 6 步的循环是你真正写控制逻辑的地方:往 IOmap 的偏移处写目标值、从 IOmap 读回实际值。
4. 源码走读①:ec_init —— 网卡怎么打开
ec_init 的活:打开 socket、绑定指定网卡、探测 EtherCAT 能力。Linux 下传接口名("eth0"),Windows 下传适配器索引或 GUID(可用 SOEM 自带 simple_test 的网卡枚举先查)。核心逻辑:
注意第 ① 步用的是 广播写 + 逐站应答的方式:主站先把所有从站地址清零,再逐个写"第 n 个从站地址 = n",配合从站级联的"转发并扣留"机制,最终每个从站拿到唯一的 8 位站地址。之后的 FMMU 配置、邮箱通信都用这个站地址定位。ec_slavecount 就是这个函数填充的全局计数——后面循环打印每个从站都靠它。
5. 源码走读②:ec_config_init —— 扫描总线与自动寻址
ec_config_init 是第一次真正"上总线说话"的函数,干三件事:自动寻址、读 SII、解析 PDO 配置。逐条看:
这个函数结束后,ec_slave[1..ec_slavecount] 每个元素都填好了 站号、EEPROM 身份(厂商/产品/版本)、初始状态,并且每个从站都已被切到 PREOP(邮箱可用、过程数据未开)。第 ③ 步读对象字典很关键——它决定了下一步 ec_config_map 能算出每个从站"过程数据区要多大"。
6. 源码走读③:ec_config_map —— FMMU / SM / IOmap 一网打尽
这是 SOEM 最"魔法"的一步,其实逻辑非常直白——就三件事:算总长、写 FMMU/SM、分偏移。对照本专题的 SM·FMMU 篇看:
分配规则一句话:从站 1 的输出段排在最前,各从站输出段先连排、输入段再连排——这就是后面你在 IOmap 里"第 n 个轴的数据在哪"的偏移依据。每个从站的 Obytes / Ibytes 来自它的 PDO 映射(0x1600 输出映射 / 0x1A00 输入映射,每个对象 index|subindex|bitlen 三元组算出的总位数 ⌈Σbitlen/8⌉ 字节),与 PDO 详解篇的位打包规则完全一致。返回的 iolen 就是整个过程数据区总长度。
7. 源码走读④:过程数据循环 —— 一帧 LRW 走遍全网
OP 之后主站进入周期循环,SOEM 的收发模型非常简洁:ec_send_processdata 组装并发出,ec_receive_processdata 收回来。核心是它内部用一条 LRW(Logical Read Write) 广播帧,同时覆盖所有从站的 FMMU 逻辑段:
关键点:send 和 receive 必须成对——receive 靠同一帧号(working counter)确认帧回来过;发完立刻收、收完立刻处理,这一对 API 的耗时就是你的抖动来源之一(所以实时性要求高的项目要配 PREEMPT_RT 或裸机)。ec_readstate 则周期读回每个从站的 AL 状态寄存器(0x0130),一旦有从站因看门狗/故障掉出 OP,ec_slave[i].state 会反映出来——这就是你的断线检测主通道。
8. DC 配置与同步:ec_configdc 衔接同步模式篇
多轴同步必须 DC(详见同步模式篇)。SOEM 的 DC 配置分两步:ec_configdc() 选参考时钟(第一个带 DC 能力的从站)、计算并写各从站 DC 寄存器(0x0900 系列);然后按需用 ec_dcsync0() 设 Sync0 周期与相移(周期单位 ns,如 1ms=1,000,000ns):
注意:SOEM 的 DC 补偿在 OP 后由主站持续进行——它周期读参考时钟的 0x092C(系统时间)再广播写各从站 0x0920(Sync 时间),实现 ppm 级漂移校正。若你的从站没有 DC 能力(SII 里没声明,见 EEPROM 篇),ec_configdc 会跳过它,该轴就退回 SM 同步——多轴场景下这是明显的不同步隐患。
9. 状态机与 OP 起不来的排查顺序
SOEM 应用 80% 的现场问题集中在"进不了 OP"。按顺序排查(也对应状态机迁移条件):
| # | 检查项 | 现象 / 手段 |
|---|---|---|
| 1 | ec_config_init 返回 0 | 总线没扫到从站:查线缆、供电、从站 EEPROM 是否损坏(SII 读不出身份) |
| 2 | 从站停在 PREOP | 邮箱不通:查 CoE 参数读写(ec_coe_read 试 0x1000 看响应)、从站地址是否被占用 |
| 3 | 从站停在 SAFEOP | SM/FMMU 配置问题:ec_config_map 后检查 ec_slave[i].state,查 0x1C32(同步模式)/ PDO 映射是否被主站正确覆盖 |
| 4 | OP 请求被拒 | 读 0x0130 AL 状态 + 0x0134 状态码:DC 未配(多轴)、看门狗、从站应用未就绪 |
| 5 | OP 后周期性掉出 | 看门狗/超时:ec_send/receive 周期不稳定、DC 补偿未建立就进 OP(同步模式篇) |
定位技巧:打印 ec_slave[i].ALstatuscode(0x0134 的错误码)——它直接告诉你被拒原因(如 0x0019=无效 SM 配置、0x001C=无效 DC 配置、0x0012=看门狗)。先定位卡在哪一档状态,再按上表找原因,比瞎试快得多。
10. 交互演示:六步流程 + IOmap 分配可视化
点"下一步"走完 SOEM 六步:看每步调用了什么 API、总线状态如何变化;第 3 步开始展示 IOmap 怎么按"输出段先排、输入段后排"分配给 3 个从站。
- 第 1~2 步:网卡打开、总线扫描(3 个从站:伺服×1 + 数字量模块×2);
- 第 3 步:IOmap 分配开始——伺服输出 4B(0x00-0x03)、输入 4B(0x04-0x07)排在最前,模块输出 2B、输入 2B 依次后接;
- 第 4~6 步:DC 配置、进 OP、周期循环——循环里数据按你看到的偏移流动。
11. 可编译最小主站代码 + 避坑清单
下面是完整的 SOEM 六步最小主站(minimal_master.c),配套 Linux gcc 编译命令;把它跑通,你就有了自己的 EtherCAT 主站骨架。
避坑清单(都来自真实现场):
| 坑 | 现象 | 对策 |
|---|---|---|
| ec_init 网卡名错 | 返回 0,找不到总线 | Linux 用 ip link 确认接口名;Windows 先跑网卡枚举工具拿索引/GUID |
| raw socket 无权限 | ec_init 秒失败 | sudo 运行;或设 CAP_NET_RAW 权限位 |
| 从站卡 PREOP / SAFEOP | OP 请求被拒 | 打印 ec_slave[i].ALstatuscode 定位,按第 9 节排查表逐级查 |
| send/receive 不成对 | 数据偶发错乱、工作计数器异常 | 严格成对调用,且在同一周期内收完立即处理 |
| OP 后周期性掉出 | 看门狗超时 | 周期必须稳定:裸机/RT 内核 + 高精度定时器;检查 DC 补偿是否建立 |
| PDO 映射与 ESI 不一致 | IOmap 偏移对不上、数据错位 | ec_config_map 前用 ec_coe 改写 0x1600/0x1A00 或核对 ESI 默认映射(PDO 篇) |
一句话收尾:SOEM 把 EtherCAT 主站的复杂度收敛成六个 API 调用 + 一个 IOmap 数组——扫描靠 ec_config_init、映射靠 ec_config_map、运行靠 send/receive 循环;读懂这三步的源码,你就掌握了整个主站的心脏。