十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

EtherCAT实时控制实战:IgH主站搭建与最小闭环实现

EtherCAT实时控制实战:IgH主站搭建与最小闭环实现 简介围绕IGH与Xenomai实时系统控制电机的小型实践项目面向正在学习EtherCAT主站搭建、实时运动控制的嵌入式开发者也适合需要快速验证实时通信链路的工程师。压缩包内包含完整的编译环境与源码下载后只需在main.c中修改自己电机的ID即可运行省去从零配置IGH主站与Xenomai实时内核的繁琐步骤。包内共307个文件压缩包约9.97MB以161个h头文件为主配合a/so/la等静态与动态库文件、7个ko内核模块以及conf配置文件构成一套可直接编译的EtherCAT从站通信工程框架同时提供了makefile与常用工具脚本方便按需裁剪和重新编译。目前已有3160人学习下载。借助该工程读者可以快速跑通IGH与Xenomai控制电机的整体流程理解实时补丁、主站配置、周期通信等关键环节作者还提供了配套的演示视频和搭建教程入口能进一步降低上手门槛适合渴望动手实践的初学者也可作为中高级嵌入式工程师搭建实时控制验证环境的参考模板。1. igh 实时控制电机动起来编译环境、代码与最小闭环台下那台伺服空载点动都发飘控制周期 4ms速度环改来改去没有意义——总线抖动已经把性能吃掉了。解决这类实时控制问题通常不是补 PID而是换一条有实时性的数据通路。IgH 是 Linux 下最常见的 EtherCAT 主站实现一条以太网线串起伺服、IO、编码器代价是你得自己准备编译环境、加载主站、按 CiA402 状态机给控制字。这篇文章把一个能跑的最小项目拆给你看搭编译环境、组织驱动代码、让电机转起来再教你用抖动数据反推配置问题。适合刚接手 EtherCAT 驱动、或者想把手动点动改成程序点动的工程师。整个链路不长但每一步都有明显的排错信号跟着做能建立起对 IgH 实时控制的基本手感。2. 编译环境搭建内核头文件、IgH 主站和 RT 内核怎么选2.1 编译环境包含哪三层EtherCAT 主站是软件优先级最高的周期任务。IgH 在 Linux 下运行时主站模块运行在内核态应用层既可以通过 RTDM 接口在用户态访问也可以直接把控制逻辑编译成内核模块。两种方式共用同一套编译产物ec_master.ko 主站模块和对应的网卡驱动所以编译环境是所有后续代码的地基。我带项目时一般会先把三层分清楚内核源码树/lib/modules/$(uname -r)/build、IgH 源码、应用代码。很多人第一步就栽在 configure 时报找不到内核头文件或者 insmod 时报版本不匹配原因是 IgH 编译出的 .ko 必须和当前运行内核的 vermagic 一致。只要换了内核这三样都得重新对齐。2.2 RT 内核与普通内核怎么选实时性从哪来如果只是验证性点动普通内核加 IgH 的 FreeRun 模式也能让电机转但周期抖动可能到几百微秒速度环和位置环都很难看。主流项目里实时化路径有两条RT-Preempt 和 Xenomai。RT-Preempt 是单内核方案把 Linux 内核改造成完全可抢占发行版直接提供 -rt 内核组装成本低Xenomai 是双内核方案实时任务走独立的实时内核抖动更小但 IgH 需要启用 RTDM 支持环境的复杂度高一截。第一次跑这个项目我建议直接用发行版的 RT-Preempt 内核。Debian/Ubuntu 系安装 linux-image-rt-amd64 和对应的 headers 包即可。装完重启进 rt 内核确认 CONFIG_PREEMPT_RT 生效后再编译 IgH。这样做的好处是后面如果发现周期抖动不合格你只需要把主站和应用代码迁到 Xenomai 那套接口业务逻辑不用推翻。硬实时要求不高的场景RT-Preempt 能覆盖大多数。2.3 IgH 源码编译步骤源码解压后先看 configure 参数。下面这组是适合第一跑的最小配置# 进入 IgH 源码目录后先跑 configure ./configure \ --with-linux-dir/lib/modules/$(uname -r)/build \ --enable-generic \ --disable-rtdm # 编译主站与工具 make modules sudo make modules_install # 安装 ethercat 命令行工具 sudo make install # 刷新模块依赖让 modprobe 能找到 ec_master sudo depmod -a--with-linux-dir 指到运行中内核的构建目录这是最常见的报错来源--enable-generic 打开通用网卡驱动generic driver它不绑定具体硬件适合第一次验证链路--disable-rtdm 是保险开关暂时避开 Xenomai 的用户态接口。之后如果要走用户态控制再回来打开 --enable-rtdm 并编译 Xenomai 用户库。编译过程中出现 “linux/version.h: No such file or directory”八成是 headers 包没装全出现 “Cannot use CONFIG_MODVERSIONS” 之类则是内核源码树和当前运行内核不完全一致去把对应版本的 headers 装齐再继续。2.4 加载主站并验证从站链路编译之后的加载顺序有讲究先加载主站再加载网卡驱动。EtherCAT 专用网卡如果已经被内核自带驱动占用要先解绑再通过 main_devices 参数把网卡交给主站。# 加载主站把准备好做 EtherCAT 端口的网卡 MAC 填进去 sudo modprobe ec_master main_devices00:1b:21:12:34:56 # 加载通用网卡驱动 sudo modprobe ec_generic # 查看总线上所有从站 sudo ethercat slavesmain_devices 的 MAC 地址用 ip link 查要和 EtherCAT 实际插的网口对应上。从站没上电时 slaves 列表是空的如果能列出从站型号说明链路已经打通。报 “Device or resource busy” 是网卡还被原生驱动占用先 sudo ethtool -i 看当前驱动再把它解绑。如果只有一张网卡建议后续加一块独立网卡跑 EtherCAT避免调试过程中连不上机器。2.5 这一层要记住的产物产物作用验证方式ec_master.ko主站核心负责 EtherCAT 状态机和周期帧调度/sys/class/ethercat/master0 存在ec_generic.ko通用网卡驱动把任意网卡接入主站ethercat slaves 能列出从站ethercat命令行工具查从站、PDO、状态机ethercat version 输出版本号/dev/EtherCAT0用户态接口节点启用 RTDM 后才会出现ls /dev/EtherCAT*这一层能通过说明编译环境成立接下来可以把精力放到应用代码上。3. 最小项目代码PDO 映射、域注册和实时循环怎么组织3.1 工程结构只需要三个文件控制代码按内核模块来写这是 IgH 实时控制最直接的方式主站模块在内核态应用模块也在内核态周期任务不经过系统调用边界。工程里只需要三个文件rt_motor/ ├── motor_main.c ├── Makefile └── run.shMakefile 复用内核的 kbuild 机制把 motor_main.c 编成 motor_main.korun.sh 负责解绑网卡、加载主站模块、再加载应用模块。这样拆的好处是环境初始化失败时能根据报错快速定位是主站问题还是应用问题。3.2 初始化顺序决定能不能拿到数据在处理周期循环之前先把初始化链路理顺。代码顺序和 IgH 要求的一致性很强不能跨步骤#include linux/module.h #include linux/kernel.h #include linux/hrtimer.h #include ecrt.h static ec_master_t *master; static ec_domain_t *domain; static ec_slave_config_t *sc; static unsigned char *domain_data; // 以下变量保存 PDO 条目在数据域里的偏移注册时由 IgH 回填 static unsigned int ctrl_off, status_off, vel_off, vel_act_off, mode_off; static int __init motor_init(void) { // 第 1 步请求 0 号主站对应 /sys/class/ethercat/master0 master ecrt_request_master(0); if (!master) return -EBUSY; // 第 2 步创建数据域所有 PDO 数据都在域里交换 domain ecrt_master_create_domain(master); if (!domain) return -ENOMEM; // 第 3 步配置从站 0。 // vendor_id 和 product_code 先用 0 通配所有从站 // 第一次跑建议用 sudo ethercat slaves -p 查真实 ID 再填 sc ecrt_master_slave_config(master, 0, 0x00000000, 0x00000000); if (!sc) return -EINVAL; // 第 4 步配置从站的 RPDO/TPDO 映射slave_syncs 见 3.3 if (ecrt_slave_config_pdos(sc, EC_END_OF_SYNCS, slave_syncs) 0) return -EINVAL; // 第 5 步把 PDO 条目注册进数据域 if (ecrt_domain_reg_pdo_entry_list(domain, regs) 0) return -EINVAL; // 第 6 步激活主站并拿到域数据首地址 ecrt_master_activate(master); domain_data ecrt_domain_data(domain); return 0; } module_init(motor_init);这段代码的要点在两个地方一是请求主站前不要碰任何从站配置否则拿到的是旧状态二是 ecrt_domain_reg_pdo_entry_list 必须在激活之前完成因为激活会固化 PDO 映射。vendor_id 和 product_code 填 0 可以通配但我习惯在跑通后把真实 ID 填进去防止从站地址变化时误挂设备。3.3 PDO 映射控制字、状态字与速度对象伺服这边走的是 CiA402 规范控制字 0x6040、状态字 0x6041、目标速度 0x60FF、实际速度 0x606C 都是标准对象。光注册条目不够还要把对象挂到同步管理器上否则从站还是按出厂映射解析报文// PDO 条目定义包含 RPDO 与 TPDO 用到的全部对象 static ec_pdo_entry_info_t pdo_entries[] { {0x6040, 0, 16}, // 控制字 {0x6060, 0, 8}, // 操作模式 {0x60FF, 0, 32}, // 目标速度 {0x6041, 0, 16}, // 状态字 {0x606C, 0, 32} // 实际速度 }; // PDO 分组0x1600 是 RPDO10x1A00 是 TPDO1 static ec_pdo_info_t pdos[] { {0x1600, 3, pdo_entries 0}, {0x1A00, 2, pdo_entries 3} }; // Sync Manager 映射2 号 SM 输出3 号 SM 输入 static ec_sync_info_t slave_syncs[] { {0, EC_DIR_OUTPUT, 0, NULL}, {1, EC_DIR_INPUT, 0, NULL}, {2, EC_DIR_OUTPUT, 1, pdos 0}, {3, EC_DIR_INPUT, 1, pdos 1}, {0xff} }; static ec_pdo_entry_reg_t regs[] { // 前两项是 alias 和 position0 表示不使用别名、选择第一个从站 {0, 0, 0x6040, 0, ctrl_off}, // 控制字 {0, 0, 0x6041, 0, status_off}, // 状态字 {0, 0, 0x6060, 0, mode_off}, // 操作模式 {0, 0, 0x60FF, 0, vel_off}, // 目标速度 {0, 0, 0x606C, 0, vel_act_off}, // 实际速度 {} };对象子索引读写方向含义0x60400主站→从站控制字驱动状态机切换0x60410从站→主站状态字反映当前状态0x60600主站→从站操作模式3 为速度模式0x60FF0主站→从站目标速度用户单位0x606C0从站→主站实际速度用于闭环反馈槽位 0x1600/0x1A00 是多数 CiA402 伺服的默认 PDO 槽如果你的伺服出厂映射占用的是 0x1601/0x1A01把索引改过去即可。ecrt_slave_config_pdos 会把这套配置下发到从站覆盖默认映射跑不通时先用 ethercat pdos 查看从站当前生效的映射而不是只改代码。3.4 周期循环里的调用顺序实时循环是 IgH 项目的核心一次周期的调用顺序比任何优化都重要static enum hrtimer_restart motor_cycle(struct hrtimer *timer) { // 1. 接收上一帧数据 ecrt_master_receive(master); // 2. 把数据拆到域内偏移 ecrt_domain_process(domain); // 3. 在这里读写 PDO 数据详见第 4 章 // EC_READ_U16(domain_data status_off); // EC_WRITE_U16(domain_data ctrl_off, cw); // EC_WRITE_S32(domain_data vel_off, target_speed); // 4. 把输出排队并发送 ecrt_domain_queue(domain); ecrt_master_send(master); // 5. 把定时器推进到下一个周期 hrtimer_forward_now(timer, ns_to_ktime(CYCLE_NS)); return HRTIMER_RESTART; }receive 负责收帧process 负责把帧里的数据拆到 domain_data 对应的偏移domain_queue 把新输出打包到发送缓冲区send 真正把帧发到总线上。顺序一旦颠倒轻则数据是一只旧一个周期重则从站报同步错误。CYCLE_NS 通常先设 10000001ms跑通后再往下压。4. 让电机转起来CiA402 使能流程与常见实时控制坑4.1 控制字三步使能伺服上电后处于 Switch on disabled要进入 Operation enabled控制字要走 0x06 → 0x07 → 0x0F 三个台阶。每写一步都要等状态字跟上不能连发static void motor_enable(void) { uint16_t sw; // 第一步0x06 让设备从 Switch on disabled 到 Ready to switch on EC_WRITE_U16(domain_data ctrl_off, 0x06); do { msleep(10); sw EC_READ_U16(domain_data status_off); } while ((sw 0x6F) ! 0x21); // 第二步0x07 切换到 Switched on EC_WRITE_U16(domain_data ctrl_off, 0x07); do { msleep(10); sw EC_READ_U16(domain_data status_off); } while ((sw 0x6F) ! 0x23); // 第三步0x0F 进入 Operation enabled EC_WRITE_U16(domain_data ctrl_off, 0x0F); do { msleep(10); sw EC_READ_U16(domain_data status_off); } while ((sw 0x6F) ! 0x27); }掩码 0x6F 保留状态字 bit0~bit3、bit5、bit6忽略厂商自定义位0x21、0x23、0x27 分别是三个状态下的关键位组合。如果状态字始终停在 bit3 置位的值Fault 状态说明从站侧有报警先读 0x603F 错误码定位而不是继续发控制字。使能成功后给速度对象 0x60FF 赋值电机就会转起来。4.2 速度模式下的参数选择操作模式对象 0x6060 写 3 表示速度模式单位由伺服参数决定有些驱动器默认 rpm有些是 user-defined比如 mm/min。控制代码里注意区分有符号数目标速度是 int32方向通过符号位表达// 切换到速度模式 EC_WRITE_U8(domain_data mode_off, 3); // EC_READ_U8(domain_data mode_off) 读回确认 // 给定正反转速度单位视伺服配置而定 EC_WRITE_S32(domain_data vel_off, 500);0x6060 不能只在 SDO 里写一次就完事如果它不在 PDO 映射里运行中切换模式会丢失同步。把 0x6060 纳入 regs 数组后每次周期都会把模式值重新下发配合速度给定模式切换才可靠。4.3 三个高频坑现象排查方向slaves 只能列到一半从站检查 IN/OUT 线序能进 OP一跑就 Fault读 0x603F 报警码核对 PDO 位宽电机一顿一顿先测周期抖动再关中断合并第一个坑是 PDO 位宽。很多伺服要求 PDO 条目按 32 位对齐控制字和状态字虽然是 16 位对象实际映射槽位是 32 位。代码里用 EC_WRITE_U16 写低 16 位没问题但注册表里如果漏了对应的填充项从站会直接拒绝进入 OP。第二个坑是环网接线。EtherCAT 从站有 IN/OUT 两个网口接反时链路层级错乱表现为 ethercat slaves 能列到部分从站但后面的从站全部离线。逐台检查主站到从站、从站到从站的线序比查代码更快。第三个坑是周期与看门狗。控制周期从 4ms 突然压到 1ms 后如果主站发帧间隔抖动过大从站看门狗会认为通信中断电机表现为一顿一顿然后状态字翻成 Fault。这时候先不要调 PID回到周期循环里测抖动。4.4 用 ethercat 工具确认故障边界# 查看从站序号、厂商 ID、产品码 sudo ethercat slaves -p # 查看从站 0 的 PDO 映射 sudo ethercat pdos -p 0 # 把从站 0 直接切到 OP 状态 sudo ethercat states -p 0 OP # 把 0 号从站的调试级别拉到 4 sudo ethercat debug -p 0 4ethercat pdos 能直接看到当前生效的 RPDO/TPDO 映射和代码里 regs 数组对比不一致时优先修从站配置或代码映射。states 命令可以在不启动应用的情况下单独验证从站能否进入 OP适合隔离“主站问题”和“应用问题”。5. 验证时序抖动用 jitter 数据反调网络参数5.1 在周期循环里埋一个计时器电机转起来之后第一件事不是调速度环而是量化实时性。实时性只看平均周期没意义要看最大抖动。hrtimer 回调里每周期用 ktime_get_ns() 取一次时间戳static u64 last_ns, max_jitter, total_jitter; static unsigned long cycles; static enum hrtimer_restart motor_cycle(struct hrtimer *timer) { u64 now ktime_get_ns(); u64 delta now - last_ns; u64 dev; if (cycles 0) { // 每个周期实际间隔与理想周期 CYCLE_NS 的差即抖动 dev delta CYCLE_NS ? delta - CYCLE_NS : CYCLE_NS - delta; if (dev max_jitter) max_jitter dev; total_jitter dev; } last_ns now; cycles; // 每 10 万周期约 100 秒 1ms打一次抖动统计 if (cycles % 100000 0) printk(jitter max_ns%llu avg_ns%llu\n, max_jitter, total_jitter / cycles); // 原有接收、处理、发送逻辑不变 ecrt_master_receive(master); ecrt_domain_process(domain); ecrt_domain_queue(domain); ecrt_master_send(master); hrtimer_forward_now(timer, ns_to_ktime(CYCLE_NS)); return HRTIMER_RESTART; }5.2 抖动超标时的三个调优方向如果 max_ns 长期大于几个毫秒问题通常不在 IgH而在 CPU 和网卡的中断行为。先绑核再关中断合并最后才换驱动# 绑核把应用核和中断核分开避免调度器抢占 # 举例把 hrtimer 任务和网卡中断都固定到 CPU2 # 关闭网卡中断合并EtherCAT 要求低延迟合并会积压帧 sudo ethtool -C eth0 rx-usecs 0 tx-usecs 0 # 把 CPU 调频策略固定在最高频率避免省电模式拉长执行时间 sudo cpupower frequency-set -g performance网卡中断合并是 EtherCAT 抖动第一大来源。通用网卡驱动为了节省 CPU 会把多个收包中断合并成一次上报帧到达主站的时间因此被延迟导致发帧间隔不规则。ethtool 两端关闭合并后抖动通常会明显下降。最后一步才是把通用驱动换成 IgH 专用的 e1000e 或 igb 驱动它能绕开协议栈直接操作 DMA 描述符中断路径更短。把这三条命令加进 run.sh再跑一轮 10 万周期统计最大抖动压到几十微秒以内后才值得把精力放回速度环参数。本文还有配套的精品资源点击获取
返回列表