05 主站实现:IgH EtherCAT Master 与 SOEM

05 主站实现:IgH EtherCAT Master 与 SOEM

本文重点:Linux 平台上两个最常用的开源 EtherCAT 主站栈——IgH EtherCAT Master(内核态)与 SOEM(用户态)——的架构、API 使用模式、对比选型,以及运行主站所需的实时环境配置。
面向读者:准备在 PC/工控机上跑 EtherCAT 主站的机器人/工控工程师。

具体 API 签名、版本行为以官方文档为准:IgH(etherlab.orggitlab.com/etherlab.org/ethercat)、SOEM(github.com/OpenEtherCATsociety/SOEM)。


1. 主站栈全景

EtherCAT 主站只需要一块普通以太网卡(MAC 层直接收发原始帧),不需要专用硬件,因此主站的差异全部体现在软件栈上:

主站 类型 运行环境 许可 典型场景
Beckhoff TwinCAT 商用 Windows + 实时扩展 商业许可 事实标准、工程工具链最完整(扫描、ESI 管理、示波)
Acontis EC-Master 商用 Linux/Windows/RTOS/裸机 商业许可 需要商业支持、认证、跨平台的产品
KPA EtherCAT Master 商用 多平台 商业许可 同上
IgH EtherCAT Master(EtherLab) 开源 Linux(内核模块) GPLv2(内核部分)/ LGPL(用户库,以随源码的 COPYING 为准) Linux 实时控制、ros2_control 集成的主流选择
SOEM(Simple Open EtherCAT Master) 开源 用户态 C 库,可移植 GPLv2(带链接例外的历史版本存在,以仓库 LICENSE 为准) 嵌入式、快速原型、对内核无侵入
SOEM 的兄弟项目 SOES 开源 从站协议栈 同 SOEM 自研从站固件

许可细节随版本变动(SOEM 曾从 GPLv2+例外调整过表述),商用闭源集成前务必阅读当前版本的 LICENSE 文件,以官方仓库为准。

选型的第一性问题:

  1. 要不要硬实时 + DC 高精度同步? → 伺服多轴插补(CSP 周期 ≤1ms)基本要求 IgH + PREEMPT_RT(或 Xenomai/RTAI),或商用栈。
  2. 能不能改内核 / 装内核模块? → 不能(如某些嵌入式 Linux、容器环境)则选 SOEM。
  3. 要不要商业支持与责任兜底? → 要则 Acontis/KPA/TwinCAT。

2. IgH EtherCAT Master(EtherLab)

2.1 架构:内核态主站

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌────────────────────────────────────────────┐
│ 用户态应用(周期实时任务) │
│ libethercat (ecrt_* API) │
├──────────────┬─────────────────────────────┤
│ /dev/EtherCAT0 字符设备(ioctl / mmap) │
├──────────────┴─────────────────────────────┤
│ ec_master.ko 内核主站模块 │
│ ├─ 状态机(总线扫描、从站配置、EEPROM) │
│ ├─ Domain 管理(过程数据映像) │
│ └─ Datagram 队列 / 帧组装 │
├────────────────────────────────────────────┤
│ 网卡驱动层 │
│ ec_generic.ko(通用,走内核网络栈收发) │
│ ec_igb.ko / ec_e1000e.ko / ec_8139too.ko │
│ (网卡专用 native 驱动,绕过网络栈,低抖动) │
└────────────────────────────────────────────┘

关键点:

  • 主站运行在内核:周期帧的组装、收发在内核上下文完成,配合 native 网卡驱动可以把中断路径做到最短。
  • 网卡驱动两种模式
    • ec_generic:不改网卡驱动,通过内核网络协议栈的 raw 接口收发。部署最简单(任何网卡都能用),但每帧多走一遍网络栈,抖动更大。
    • native 驱动ec_igbec_e1000eec_r8169 等):IgH 为特定网卡型号打补丁的专用驱动,网卡被主站独占、完全绕过 Linux 网络栈。支持的网卡型号和内核版本强绑定,选硬件前先查官方仓库 devices/ 目录和文档。
  • 一块网卡 = 一条总线:被主站接管的网卡不再出现在 ip link 中作普通网卡使用。
  • 配置文件 /etc/ethercat.conf(或发行版对应位置):MASTER0_DEVICE(网卡 MAC 地址)、DEVICE_MODULES(驱动模块名)。

2.2 应用 API:典型生命周期

用户态通过 libethercat(头文件 ecrt.h)与 /dev/EtherCAT0 交互。典型代码骨架(函数名以你所用版本的 ecrt.h 为准):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <ecrt.h>

// ── 1. 初始化阶段(非实时,只做一次)──────────────────
ec_master_t *master = ecrt_request_master(0); // 取得主站 0

ec_domain_t *domain = ecrt_master_create_domain(master); // 过程数据域

// 每个从站:按 (alias, position, vendor_id, product_code) 定位
ec_slave_config_t *sc = ecrt_master_slave_config(
master, 0, 0, VENDOR_ID, PRODUCT_CODE);

// 配置 PDO 映射(数组描述 SM → PDO → Entry 层级)
ecrt_slave_config_pdos(sc, EC_END, slave_syncs);

// 把 PDO Entry 注册进 domain,拿到其在过程映像中的字节偏移
unsigned int off_target_pos;
ec_pdo_entry_reg_t regs[] = {
{0, 0, VENDOR_ID, PRODUCT_CODE, 0x607A, 0x00, &off_target_pos},
{}
};
ecrt_domain_reg_pdo_entry_list(domain, regs);

// (可选)DC 分布式时钟:ecrt_slave_config_dc(sc, assign_activate,
// sync0_cycle_ns, sync0_shift_ns, sync1_cycle_ns, sync1_shift_ns);

// ── 2. 激活:此后不允许再改配置 ─────────────────────
ecrt_activate(master);
uint8_t *pd = ecrt_domain_data(domain); // 过程映像基地址

// ── 3. 周期实时循环(如 1kHz)───────────────────────
while (running) {
wait_next_cycle(); // clock_nanosleep 绝对时间

ecrt_master_receive(master); // 收上一周期返回的帧
ecrt_domain_process(domain); // 校验 WKC、更新输入映像

int32_t actual = EC_READ_S32(pd + off_actual_pos); // 读输入
EC_WRITE_S32(pd + off_target_pos, target); // 写输出

ecrt_domain_queue(domain); // 输出映像入队
ecrt_master_send(master); // 发出本周期帧
}

// ── 4. 退出 ─────────────────────────────────────────
ecrt_release_master(master);

要点:

阶段 规则
ecrt_request_master 内核模块已加载、从站已上电(主站会自动扫描总线)
ecrt_activate 所有 domain / slave_config / PDO 注册必须完成;激活后配置冻结
周期循环 receive → process → (业务) → queue → send 的顺序固定;先收后发,本周期收到的是上一周期发出的帧
DC 模式 还需周期调用 ecrt_master_application_time() / ecrt_master_sync_reference_clock() / ecrt_master_sync_slave_clocks()(具体组合以版本文档为准)
实时约束 循环内不得 malloc、不得写日志到磁盘、不得调用可能阻塞的系统调用

2.3 ethercat 命令行工具

IgH 附带的 ethercat 工具直接与内核主站交互,是不写一行代码就能验证总线的手段(详细排障用法见 07-组网调试与排障.md):

1
2
3
4
5
6
7
8
9
ethercat slaves            # 列出从站:位置、状态(INIT/PREOP/SAFEOP/OP)、名称
ethercat slaves -v # 详细信息:Vendor/Product、DC 支持、端口拓扑
ethercat pdos # 显示各从站当前的 SM/PDO/Entry 映射
ethercat sdos # 列出对象字典(支持 SDO Info 的从站)
ethercat upload -p 0 0x6041 0x00 # SDO 读(从站0 状态字)
ethercat download -p 0 0x6060 0x00 8 # SDO 写(运行模式=CSP)
ethercat states OP # 请求所有从站迁移到 OP(也可 -p N 指定)
ethercat reg_read -p 0 0x0300 8 # 读 ESC 寄存器(错误计数器)
ethercat xml # 按扫描结果生成从站描述 XML

3. SOEM(Simple Open EtherCAT Master)

3.1 架构:用户态、无内核依赖

SOEM 是一个纯 C 库,通过 raw socket(Linux 上 AF_PACKET,Windows 上 WinPcap/npcap)直接收发 0x88A4 帧:

1
2
3
4
5
6
7
8
9
┌─────────────────────────────┐
│ 应用(可以就是 main 函数) │
│ SOEM 库(静态/动态链接) │
│ ec_send_processdata() ... │
├─────────────────────────────┤
│ raw socket (AF_PACKET) │
├─────────────────────────────┤
│ 普通网卡驱动(不修改) │
└─────────────────────────────┘
  • 不需要内核模块,不需要网卡专用驱动,sudo(或 CAP_NET_RAW)即可运行 → 部署极简,适合嵌入式和交叉编译。
  • 代价:每帧都经过内核网络栈 + 系统调用边界,抖动天花板比 IgH native 驱动高;实时性依赖应用自己做好(RT 调度、绑核)。
  • 单进程内嵌主站:主站生命周期 = 应用进程生命周期,没有”多个应用共享一个主站”的概念。

3.2 典型 API 流程

以广泛流传的 simple_test.c 模式为例(函数名基于经典 ec_ 前缀 API;新版 SOEM 2.x 改为显式 context API,以官方仓库当前文档为准):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "ethercat.h"

char IOmap[4096];

// 1. 绑定网卡并初始化 raw socket
if (ec_init("eth0") <= 0) { /* 失败:权限/网卡名 */ }

// 2. 扫描总线,枚举从站 → ec_slave[] 数组,从站进入 PRE-OP
if (ec_config_init(FALSE) <= 0) { /* 没找到从站 */ }
printf("%d slaves found\n", ec_slavecount);

// 3. 构建过程映像:读各从站 PDO 配置,把输入/输出映射进 IOmap
ec_config_map(&IOmap);

// 4. 配置分布式时钟(测量传播延迟、写偏移补偿)
ec_configdc();

// 5. 请求 SAFE-OP → OP
ec_slave[0].state = EC_STATE_OPERATIONAL;
ec_send_processdata(); // OP 前需先交换一次有效过程数据
ec_receive_processdata(EC_TIMEOUTRET);
ec_writestate(0);
ec_statecheck(0, EC_STATE_OPERATIONAL, EC_TIMEOUTSTATE);

// 6. 周期循环
while (running) {
wait_next_cycle();
ec_send_processdata();
int wkc = ec_receive_processdata(EC_TIMEOUTRET);
if (wkc < expectedWKC) { /* 记录并进入排障逻辑 */ }

// 直接通过 ec_slave[n].outputs / .inputs 指针读写 IOmap
}

ec_close();

SDO 访问:ec_SDOread() / ec_SDOwrite()(非周期,配置阶段使用)。

3.3 SOEM 的适用位置

  • 嵌入式控制器(RT Linux 板卡、甚至配合移植层跑在 RTOS 上)。
  • 工具类程序:扫描器、EEPROM 烧写器、产测脚本。
  • 从站开发时的对测主站。
  • 轴数少、周期 ≥1ms、对抖动要求不极端的机器。

4. IgH vs SOEM 对比

维度 IgH EtherCAT Master SOEM
运行位置 内核模块 + 用户库 纯用户态库
网卡要求 generic 任意网卡;native 驱动仅支持特定型号(低抖动) 任意网卡(raw socket)
实时性上限 高(native 驱动 + PREEMPT_RT,周期可到 100µs 级) 中(受网络栈/系统调用抖动限制,1ms 级周期为宜)
部署复杂度 高:编译内核模块、匹配内核版本、配置驱动接管网卡 低:编译一个库,给权限即可
DC 分布式时钟 完整支持(参考时钟同步、SYNC0/1 配置 API) 支持(ec_configdc + ec_dcsync0),主站侧时钟伺服需应用自己实现
许可 GPLv2(内核);用户库许可以源码 COPYING 为准 GPLv2(历史上带链接例外,以 LICENSE 为准)
多应用共享主站 支持(内核主站可被多个用户程序 request) 不支持(主站内嵌于单进程)
多主站/多网卡 支持多个 master 实例(EtherCAT0/1…) 每进程可 init 不同网卡,冗余口有 ec_init_redundant
命令行工具 ethercat 工具链完整 自带示例程序(slaveinfo、eepromtool 等)
内核版本耦合 强(补丁/驱动需随内核维护)
典型搭档 ros2_control(ethercat_driver_ros2)、EtherLab、LinuxCNC 嵌入式、SOES 对测、原型

经验法则:多轴伺服 + DC 同步插补 → IgH;快速验证、嵌入式、IO 为主 → SOEM。


5. 实时环境:主站跑得准的前提

EtherCAT 的确定性一半靠总线协议,另一半靠主站按时发帧。普通 Linux 调度下,周期任务可能被随意抢占、迁移 CPU、被中断打断,抖动轻松达到几百微秒甚至毫秒级——这会直接变成 DC 同步误差和从站看门狗超时。

5.1 为什么不能用普通调度

干扰源 后果 对策
CFS 公平调度 周期任务被普通负载挤占 SCHED_FIFO 高优先级
CPU 迁移 cache 冷、唤醒延迟 isolcpus + CPU affinity
无关中断 打断实时任务 IRQ affinity 移走无关中断
内核不可抢占段 毫秒级延迟尖峰 PREEMPT_RT 补丁内核
页缺失/换出 不可预期停顿 `mlockall(MCL_CURRENT
CPU 频率调节/C-state 唤醒延迟 performance governor、限制 idle 深度

5.2 配置 checklist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. PREEMPT_RT 内核(发行版 rt 内核或自编译)
uname -v # 应含 PREEMPT_RT

# 2. 隔离 CPU(GRUB 内核参数,隔离 2、3 号核)
# isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3

# 3. 实时任务绑到隔离核 + FIFO 优先级
taskset -c 2 chrt -f 80 ./my_ethercat_app
# 代码内等价:pthread_setaffinity_np + sched_setscheduler(SCHED_FIFO)

# 4. EtherCAT 网卡中断绑到实时核(IgH native 驱动独占网卡时由驱动处理;
# SOEM/generic 模式下手动绑)
grep eth0 /proc/interrupts # 找 IRQ 号
echo 4 > /proc/irq/<IRQ>/smp_affinity # 掩码 0x4 = CPU2

# 5. 关闭该网卡的中断合并与节能(SOEM/generic 模式)
ethtool -C eth0 rx-usecs 0 tx-usecs 0
ethtool -K eth0 gro off gso off tso off

# 6. 锁内存 + 预热栈(代码内)
# mlockall(MCL_CURRENT | MCL_FUTURE);

# 7. 周期定时:绝对时间 clock_nanosleep,避免相位漂移
1
2
3
4
5
6
7
8
9
// 周期任务标准写法:TIMER_ABSTIME 消除累积误差
struct timespec next;
clock_gettime(CLOCK_MONOTONIC, &next);
while (running) {
next.tv_nsec += CYCLE_NS;
while (next.tv_nsec >= 1000000000) { next.tv_nsec -= 1000000000; next.tv_sec++; }
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
do_cycle();
}

验证手段(cyclictest、抖动统计)见 07-组网调试与排障.md


6. ESI 与 ENI 文件的角色

文件 全称 谁提供 内容 谁消费
ESI EtherCAT Slave Information(XML) 从站厂商 单个从站的身份(Vendor/Product)、SM 配置、可选 PDO 映射、DC 模式、EEPROM 内容模板 配置工具(TwinCAT 等)、离线组态;从站 EEPROM(SII)里存的是其精简二进制形式
ENI EtherCAT Network Information(XML) 配置工具从 ESI + 拓扑生成 整条总线的配置:从站顺序、初始化命令序列、周期帧结构、过程映像布局、DC 参数 遵循 ETG.2100 的主站(TwinCAT、Acontis 等)直接加载执行

开源栈的差异:

  • IgH 和 SOEM 都不需要 ENI 文件。它们在线扫描总线(读 EEPROM/SII、读对象字典),配置由应用代码(ecrt_slave_config_pdos / ec_config_map)表达——等价于把 ENI 的信息写进了程序。
  • ESI 文件对开源栈仍有用:查从站的 PDO 对象、DC 模式参数(如 AssignActivate 值 0x0300)、EEPROM 烧写恢复。
  • 商用工具链(TwinCAT)走 “导入 ESI → 组态 → 导出 ENI → 主站执行” 的离线流程,可追溯性更好,是量产设备的常见做法。

7. 小结与选型建议

  • ros2_control 多轴伺服:IgH + PREEMPT_RT + native 网卡驱动,是社区验证最充分的组合(见下一篇)。
  • 嵌入式 / IO 采集 / 原型:SOEM,半天可以跑通第一帧。
  • 量产、需认证与支持:评估 Acontis / KPA / TwinCAT。
  • 无论哪个栈,实时环境配置(§5)不可省略——主站软件再好,调度抖动 1ms 一样丢同步。

下一篇06-ROS2与EtherCAT集成.md —— 把 EtherCAT 主站接入 ros2_control 的两条路线。

文章互动

阅读 --

留言

0 条留言

正在加载留言…