06 ROS 2 与 EtherCAT 集成
本文重点:如何把 EtherCAT 主站接入 ros2_control——使用现成的
ethercat_driver_ros2,或自写hardware_interface::SystemInterface;周期链路、DC 与控制周期对齐、MoveIt 到伺服 CSP 的完整数据流。
前置阅读:05-主站实现-IgH与SOEM.md。
涉及第三方包的接口细节以其官方文档/仓库为准。
1. ros2_control 架构回顾
EtherCAT 在 ROS 2 里的接入点是 ros2_control 的硬件抽象层:
1 | ┌──────────────────────────────────────────────┐ |
每个控制周期 Controller Manager 依次执行:read()(硬件 → state interface)→ 各 controller 的 update() → write()(command interface → 硬件)。EtherCAT 集成的本质就是:在 read/write 里完成一次过程数据交换。
硬件通过 URDF 的 <ros2_control> 标签声明:
1 | <ros2_control name="my_arm" type="system"> |
2. 路线一:ethercat_driver_ros2(推荐起点)
ICube-Robotics/ethercat_driver_ros2 是社区较成熟的通用方案(细节、支持的 ROS 发行版与 API 以官方仓库文档为准):
| 特性 | 说明 |
|---|---|
| 主站后端 | 基于 IgH EtherCAT Master(需先按上一篇部署内核模块) |
| 架构 | 提供通用 EthercatDriver SystemInterface + ec_module 插件体系(pluginlib) |
| 内置模块 | 通用 CiA402 驱动器模块、通用 GPIO 模块等;厂商专用从站可自写插件 |
| 配置方式 | URDF <ros2_control> 标签指定从站插件 + YAML 文件描述 PDO 通道映射,无需写 C++(标准从站的情况下) |
2.1 配置模式(示意)
URDF 中每个 joint/gpio 关联一个 ec_module 及其 YAML:
1 | <ros2_control name="ec_system" type="system"> |
YAML 把从站的 PDO 通道映射到 state/command interface(示意结构,键名以官方文档为准):
1 | vendor_id: 0x00000000 # 从站厂商 ID(ethercat slaves -v 可查) |
factor/offset 完成编码器计数 ↔ 弧度的线性换算;CiA402 插件内部处理 Operation Enabled 状态机(控制字/状态字握手),应用只看到 position 接口。
2.2 何时不够用
- 厂商从站有非标对象/特殊初始化序列(SDO 启动脚本超出配置能力时)→ 写自定义 ec_module 插件;
- 需要 SOEM 后端、或对主站生命周期有特殊管理需求 → 路线二。
3. 路线二:自写 SystemInterface
直接在 SystemInterface 里调用 IgH ecrt_*(或 SOEM ec_*)API。生命周期回调与 EtherCAT 阶段的对应:
| 回调 | EtherCAT 侧动作 | 注意 |
|---|---|---|
on_init(HardwareInfo) |
只解析 URDF <ros2_control> 参数(从站位置、PDO 表、换算系数),分配好所有缓冲 |
不碰硬件;info_.joints 里拿 param |
on_configure |
ecrt_request_master、创建 domain、slave_config、注册 PDO entry |
失败要返回 ERROR 而不是崩溃 |
on_activate |
ecrt_activate、取 domain 数据指针、SDO 写运行模式(如 0x6060=8 CSP)、驱动器使能状态机 |
激活后配置冻结;首个 write 前 command 要初始化为当前反馈值,防止跳变 |
read() |
ecrt_master_receive + ecrt_domain_process,从过程映像读 0x6064 等 → state interface |
|
write() |
command interface → 过程映像 0x607A 等,ecrt_domain_queue + ecrt_master_send |
|
on_deactivate |
驱动器下使能、停发目标 | 从站看门狗会兜底,但主动下使能更干净 |
3.1 read/write 周期与 update_rate 的关系
- Controller Manager 的
update_rate(controller_manager.yaml)决定 read/update/write 的调用频率——它就是 EtherCAT 的总线周期,因为每次 write 发一帧。 - 因此 update_rate 必须等于你规划的 EtherCAT cycle time(如 1000Hz = 1ms),并且 controller_manager 所在线程/进程要按上一篇 §5 做实时化(
SCHED_FIFO、绑核、mlockall;ros2_control 支持在具备权限时以实时优先级运行 update 循环,具体机制以其文档为准)。 - receive 在 read() 开头、send 在 write() 结尾,天然形成 “上周期帧在本周期开头收回” 的流水线,与 IgH 推荐用法一致。
3.2 实时循环纪律
read/write 运行在控制主循环里,等同硬实时代码:
- 禁止内存分配:所有 vector 在 on_init 里 resize 好;不要用会分配的字符串操作。
- 禁止常规日志:
RCLCPP_INFO涉及锁和分配。需要日志用节流宏(低频)或无锁环形缓冲 + 后台线程落盘;错误状态先记标志位,出循环再报。 - 禁止阻塞调用:SDO 读写(
ecrt_slave_config_create_sdo_request的异步方式除外)、service、参数读取都不进循环。 - WKC / domain state 异常时不要立即抛异常终止,先按帧计数容忍与降级策略处理(见下一篇)。
4. 端到端周期链路
以 1ms 周期、CSP 模式伺服为例:
1 | t=0.0ms controller_manager 周期唤醒 |
三个”周期”必须对齐:controller_manager update_rate = 主站发帧周期 = 从站 SYNC0 周期。
5. DC 与 ros2_control 周期对齐
| 问题 | 说明 | 对策 |
|---|---|---|
| cycle time 不一致 | SYNC0 周期(ecrt_slave_config_dc / YAML assign_activate 配套参数)≠ update_rate 周期,会导致目标丢失或重复执行 |
三处配置写同一个值,代码里加断言 |
| 主站相位漂移 | clock_nanosleep(CLOCK_MONOTONIC) 与 DC 参考时钟(从站 0 的本地时钟)是两个时钟源,会缓慢漂移 |
周期性 ecrt_master_application_time() + ecrt_master_sync_reference_clock(),或实现主站时钟伺服(微调睡眠周期跟踪 DC);IgH 亦支持以参考从站为准调整(版本相关,以官方文档为准) |
| 发帧抖动预算 | 帧必须在 SYNC0 之前送达从站;抖动过大 → 从站报 DC 同步丢失(AL 0x001A 等) | 经验预算:发帧时刻 + 最大抖动 + 总线传输时间 < SYNC0 时刻。用 sync0_shift 把 SYNC0 相位后移(如周期的 30%~50%),给主站抖动留裕量 |
| 抖动来源排序 | 调度延迟 ≫ 网络栈 ≫ 总线传播 | cyclictest 先量调度抖动;若 max > 100µs 量级,先解决 OS 配置再怀疑其他 |
经验值参考(非规范):1ms 周期下,主站唤醒抖动控制在 ±20µs 内、sync0_shift 设为 400µs 左右,是常见的稳定工作点;具体以实测为准。
6. MoveIt / JTC 到 CSP 目标位置的数据流
1 | MoveIt (move_group) |
要点:
- CSP 模式下插值责任在主站侧(JTC 每周期给一个新目标),伺服只做位置环跟踪 + 少量内插;轨迹平滑度取决于 JTC 插值 + 周期稳定性。
- 模式选择:CSP(周期目标位置)最常用;CSV/CST(速度/力矩)对应 velocity/effort command interface;PP(点到点)不适合 JTC 这种周期流式指令。
- 单位换算与方向(减速比、编码器分辨率、正方向定义)是最常见的”动起来不对”的原因,先在无负载下单轴验证。
7. 实践路线建议(checklist)
按顺序,每步通过再进下一步:
-
- IgH 主站部署完成,
ethercat slaves能看到全部从站且进 PREOP
- IgH 主站部署完成,
-
ethercat upload/download手动读写对象字典,确认驱动器 CiA402 状态机能走到 Operation Enabled(先不带 ros2_control)
-
ethercat states OP+ 观察 WKC 正常,跑通”裸主站”周期循环(可用 IgH 自带示例)
-
- 接 ros2_control:先只挂
joint_state_broadcaster,验证 read 链路(/joint_states 数值、单位正确)
- 接 ros2_control:先只挂
-
- 单轴 +
forward_command_controller小幅度指令,验证 write 链路与方向/换算
- 单轴 +
-
- 挂 JTC,
ros2 action send_goal发短轨迹
- 挂 JTC,
-
- 最后接 MoveIt;全程用 cyclictest / 主站统计监控抖动
原则:总线问题用总线工具(ethercat 命令、Wireshark)解决,别隔着 ros2_control 猜。排障手段见下一篇。
8. 与本工作区 armRobot 项目的关系
/home/cp/work2/ros2Learn/app/armRobot 是一个 6 轴机械臂 ROS 2 栈(Humble + MoveIt 2 + ros2_control)。就 EtherCAT 而言,其 README 的现状是:
arm_hw提供 mock / UART 两种后端;真机 launch 的hardware参数虽然接受ethercat取值,但会明确拒绝启动——“当前没有 EtherCAT master、PDO 映射或真实 EtherCAT backend”,即 EtherCAT transport 只是预留的边界,没有实现。- 现有真机链路走 UART 帧协议(
arm_protocol→ MCU 固件),伺服控制在 MCU 侧。
若将来真实接入 EtherCAT 伺服,本篇的两条路线都适用:
| 路线 | 做法 | 对 armRobot 的改动 |
|---|---|---|
| ethercat_driver_ros2 | URDF 里把 arm_hw 插件换成 ethercat_driver/EthercatDriver,每轴配 CiA402 YAML |
主要是 description/bringup 配置层;arm_safety 的 command_allowed 门逻辑需要在新硬件层重新落点 |
| 自写 SystemInterface | 在 arm_hw 内实现 EtherCAT 后端(IgH API),复用其现有接口边界 |
代码量大,但能保留现有 mock/uart/ethercat 统一的插件选择结构与安全联锁 |
注意 armRobot 强调的安全边界仍然成立:EtherCAT 化不改变”外部硬接线 STO 切断能量、ROS 安全链不能替代 STO”的原则。
下一篇:07-组网调试与排障.md —— 布线、上电检查、WKC/AL Status/Wireshark 排障与性能验证。
正在加载留言…