ARTICLE DETAIL

资讯详情

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

ROS2 Control、MoveIt2与EtherCAT机械臂集成:从伺服配置到运动规划实战

ROS2 Control、MoveIt2与EtherCAT机械臂集成:从伺服配置到运动规划实战 如果你最近也在折腾机械臂大概已经攒齐了一堆东西六个伺服驱动器一张 EtherCAT 网卡一台装了 Ubuntu 22.04 的工控机脑子里还有“用 ROS2 Control 和 MoveIt2 跑起来”的计划。真等动手时才会发现MoveIt2 规划得再漂亮也只是在 URDF 的世界里自嗨ROS2 Control 能管理关节但不知道 EtherCAT 总线上那一堆 PDO 怎么填EtherCAT 主站和伺服从站倒是能通信却又和 ROS2 不在一个世界里说话。这篇文章就是把三层之间的桥全部接通从安装环境、配置伺服从站到写硬件接口、启动 ROS2 Control最后用 MoveIt2 做第一次真实运动规划。整个过程我会按自己实际走过的路径来写包括那些文档里不会写、但大概率会踩的坑。1. 系统全景数据从 MoveIt2 到伺服电机要经过哪几道门1.1 三个软件层各管什么很多人一开始容易把 MoveIt2 和 ROS2 Control 的职责搞混。简单说MoveIt2 是“军师”负责在虚拟环境里算出末端从 A 点到 B 点的关节路径ROS2 Control 是“监工”负责把这条路径按控制周期拆成每一个关节的目标指令并且保证硬件按指令执行。EtherCAT 则是最后的“传令兵”——监工把每个控制周期的关节位置写给总线伺服从站收到再驱动电机。这条链路从规划到执行大致是这样的MoveIt2 里的 move_group 节点完成运动规划发布 FollowJointTrajectory 目标。ros2_control 的 joint_trajectory_controller 收到目标插值生成一连串离散点。硬件接口hardware_interface在每个控制周期调用 write()把当前周期的关节位置/速度转换成协议数据。EtherCAT 主站把协议数据打包成帧经网卡发到伺服驱动器。伺服驱动器在位置模式或速度模式下闭环转动电机。编码器反馈数据再沿同一条链路返回 read()更新 /joint_states形成闭环。这条链路里任何一环断了都会出现“MoveIt2 显示规划成功但机械臂纹丝不动”或者“ROS2 Control 报激活失败”的诡异现象。所以最先要做的不是写代码而是在脑子里把这个数据流画清楚。1.2 为什么最终选了 EtherCAT 而不是脉冲口或 CANopen如果你的机械臂只有四个舵机用 PWM 脉冲口完全够代码也简单但换成六轴工业伺服情况就变了。第一六个电机需要严格的同步脉冲口方案每个轴独立给脉冲时间上天然存在微小偏差负载一变化偏差会更大。第二EtherCAT 有分布式时钟DC机制主站和所有从站在同一个时基上每隔一个固定周期比如 1ms完成一次输入输出刷新从站之间的同步精度能做到微秒级这一点对六轴联动非常关键。EtherCAT 的另一个好处是拓扑灵活伺服从站上有 IN/OUT 两个网口一根网线串下去就能连六个电机。相比 CANopen 需要单独处理波特率和终端电阻EtherCAT 的接线容错率高不少调试期间少了很多物理层的折腾。2. 环境准备在 Ubuntu 22.04 上装齐 ROS2、MoveIt2 与 EtherCAT 主站2.1 ROS2 Humble 和 MoveIt2 的安装命令我用的系统是 Ubuntu 22.04配合 ROS2 Humble属于长期支持版本社区资料最多遇到问题能搜到大量解决方案。安装基础桌面版之后再补上 MoveIt2 和 ros2_control 相关包sudo apt update sudo apt install ros-humble-desktop sudo apt install ros-humble-moveit sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers sudo apt install ros-humble-joint-state-publisher-gui装完确认版本分别执行ros2 pkg prefix moveit_core和ros2 pkg prefix ros2_control能看到安装路径就说明没问题。建议在这之前先把.bashrc里的 ROS 环境配置好echo source /opt/ros/humble/setup.bash ~/.bashrc source ~/.bashrc这里有个容易忽视的细节MoveIt2 依赖的规划器插件OMPL和运动学求解器KDL会在安装 moveit 时被拉进来但 TRAC-IK 不在默认安装里。TRAC-IK 在遇到奇异位形时比 KDL 成功率高很多后面配置 MoveIt2 会用到提前装好不亏sudo apt install ros-humble-trac-ik-kinematics-plugin2.2 EtherCAT 主站选型IGH 与 SOEM 放在同一个表里看写硬件接口之前必须先确定用哪种 EtherCAT 主站方案。我用过的两个主流方案对比如下主站方案实现层级优点需要留意的地方IGH EtherCAT 主站内核态性能好、实时性强、自带 ethercat 命令行工具需要编内核模块、版本匹配麻烦SOEM用户态可嵌入 ROS2 节点调试直观跨平台需要用线程调度保证实时性IGH 是很多老工程师的习惯选择内核态驱动在处理大数据包时抖动小但它和当前内核升级相关的坑不少编译模块、指定版本、重启后重新加载一套流程走下来半天就没了。SOEM 则直接把协议栈放到用户空间我能在硬件接口里直接调用它的发送接收函数写起来和调普通函数一样非常适合快速验证链路。第一次打通建议用 SOEM等你确定整套方案没问题再根据业务需要决定要不要迁移到 IGH 或商业主站。2.3 实时性不用一上来就上 RT 内核但线程优先级必须处理很多教程会直接让你装 PREEMPT_RT 补丁内核理由是控制周期抖动越小越好。我在首次验证时用的就是普通内核控制周期 2ms虽然没有实时内核那么漂亮但跑通链路完全够用。真正重要的是EtherCAT 发送线程不能跟桌面程序抢 CPU否则卡顿一次就可能触发看门狗。实操上我会做两件事把 EtherCAT 通信线程绑定到指定 CPU并设置 SCHED_FIFO 调度策略优先级 80 以上。控制周期用std::this_thread::sleep_until配合 steady_clock避免 sleep_for 带来的累计误差。如果之后要跑到 1ms 以下周期再去折腾 RT 内核也不迟。这个从简到繁的顺序能帮你省掉很多前期排查时间。3. EtherCAT 从站接线与伺服驱动器配置最初耗时的部分3.1 接线、站号与从站扫描EtherCAT 接线是菊花链结构主站网卡出线接到第一个伺服驱动器的 IN 口再从 OUT 口接到下一个伺服驱动器依次串完六个轴。注意两点不能经过普通交换机链路必须从第一个伺服从站开始顺序相连。上电之后在工控机上用主站工具扫描一下能看到每个从站的厂商编号、产品代码和名字。IGH 用ethercat slavesSOEM 环境则在 example 里跑slaveinfo。第一次扫描最常见的结果是只看到第一个从站或者全部读不到。如果是只看到第一个基本是后面某个伺服从站的站号冲突或者没上电如果全部读不到检查网卡驱动是否被系统识别、有没有被 NetworkManager 接管。这种网络管理器干扰主站的问题非常普遍建议直接用sudo nmcli device set enp3s0 managed no把网卡从 NetworkManager 手里拿回来再继续扫描。3.2 同步管理器与 FMMU看懂这两个概念才能自己改映射从站扫描能看到但要让总线跑起来还必须理解两个概念同步管理器Sync Manager和 FMMU。每个伺服从站至少有两个同步管理器一个管输出主站到从站一个管输入从站到主站。在周期通信里主站把目标位置放进输出同步管理器对应的缓存区伺服从站把实际位置、状态字等反馈放进输入同步管理器。FMMU 则负责把过程数据的一段地址映射到从站寄存器里——换句话说主站发送的一帧数据里哪几个字节给哪个从站、哪个从站把哪几个字节写回都由 FMMU 决定。对新手来说最容易犯的错是“PDO 里字段顺序定义好但 FMMU 长度给错了”。比如伺服从站配置了 8 字节输入、8 字节输出但主站侧 PDO 映射只定义了 4 字节总线周期跑几次之后后面的字节全错位轻则从站报错重则位置指令会跑到另一个轴上去。所以我强烈建议修改 PDO 映射之后先用主站工具的 PDO 信息页IGH 下是ethercat pdos对一遍总字节数和字段顺序再开始闭环验证。3.3 以汇川伺服从站为例的配置流程我手头这台机械臂用的是汇川伺服驱动器配置流程可以作为一个具体参考。上电后进入驱动器面板设置几个关键参数站号设为 1 到 6与机械臂关节号一一对应通信协议选择 EtherCAT运行模式选择位置模式或速度模式取决于你想在 ROS2 侧怎么控制电子齿轮比需要根据减速比和编码器分辨率换算这一步直接影响最终的单位换算。电子齿轮比的算法是这样的电机编码器每转的脉冲数乘以减速比得到机械臂输出关节每转对应的脉冲数。比如编码器 131072 线减速比 50:1那关节输出端转一圈就需要 131072×50 个脉冲。ROS2 控制用的是弧度所以每个弧度对应的是这个数值除以 2π。把这个换算关系在代码里明确固定下来后面硬件接口的 read 和 write 才不用动不动改。同时要把配套的 ESI 文件从站描述文件通常伺服厂商会提供放到主站能找到的位置。IGH 放在/opt/etherlab/etc就能被识别SOEM 则在代码里显式指定文件路径。ESI 文件里包含了 PDO 的默认映射和从站名字扫描后能看到“EL3204”或者“IS620N”之类的名字就说明加载成功。4. 编写 ROS2 Control 硬件接口把 EtherCAT 帧翻译成关节指令4.1 三种硬件接口怎么选ROS2 Control 提供了三种硬件接口ActuatorInterface、JointInterface 和 SystemInterface。六轴机械臂我建议直接用 SystemInterface因为它允许在一个硬件实例里管理多个关节正好对应一台机械臂的六个关节。如果你用六个独立的 ActuatorInterface反而会在控制器管理器里多出很多配置项逻辑也更分散。SystemInterface 的核心方法就几个on_init初始化参数on_configure准备通信资源on_activate建立 EtherCAT 连接并进入 OP 状态read从总线读反馈write把目标位置写进 PDO。整个类通过插件机制注册ROS2 Control 在启动时会根据 URDF 里的插件名加载它。4.2 一个 SystemInterface 的核心代码骨架下面是最小可用的实现逻辑我简化了错误处理只保留主干#include hardware_interface/system_interface.hpp class EthercatSixAxisArm : public hardware_interface::SystemInterface { public: hardware_interface::return_type on_init( const hardware_interface::HardwareInfo info) override { // 读取 URDF 中声明的关节数、指令/状态接口 // 初始化每个关节的 pos_, vel_, cmd_ 数组 return hardware_interface::return_type::OK; } hardware_interface::return_type on_activate( const rclcpp_lifecycle::State /*previous_state*/) override { // 1. 打开网卡 // 2. 加载 ESI跳过不存在的从站 // 3. 进入 OP 状态 // 4. 把六个关节对应的 PDO 映射缓存下来 return hardware_interface::return_type::OK; } hardware_interface::return_type read() override { // ecx_receive_processdata(loop_, timeout); // 从 PDO 输入区解析六个关节实际位置 / 速度 for (size_t i 0; i 6; i) { joint_pos_[i] raw_to_rad(input_[i], gear_ratio_[i]); joint_vel_[i] raw_to_rad_per_sec(input_vel_[i], gear_ratio_[i]); } return hardware_interface::return_type::OK; } hardware_interface::return_type write() override { // 把 cmd_ 从 rad 转回驱动器原始单位 for (size_t i 0; i 6; i) { output_[i] rad_to_raw(cmd_[i], gear_ratio_[i]); } // ecx_send_processdata(loop_); return hardware_interface::return_type::OK; } };关键点在于 read 和 write 不要自己维护循环它们是被 ROS2 Control 的控制循环周期调用的。真正保证 EtherCAT 总线周期的是另一个独立线程它负责周期性调用主站的发送/接收函数并把收发结果暂存到共享缓冲区read 和 write 再从缓冲区里拿数据。把总线通信和控制循环拆成两个线程逻辑清晰调试时也更方便。4.3 坐标正负方向是隐藏的最大的坑这一步看起来简单实际最容易翻车。六个关节里总有某个电机的旋转方向和机械臂期望的方向相反。如果你不预处理会出现“MoveIt2 规划一条向下运动轨迹但实际关节却向上转”这在无负载时可以靠肉眼发现一旦装上负载可能直接造成关节超限甚至撞机。我习惯在代码里专门维护一张方向表每个关节一个direction_[i]值为 1 或 -1在 read 和 write 里分别乘上。同时把电机编码器的零位和机械臂的零位统一好通常做法是让机械臂处于一个标准姿态比如竖直状态然后把这一位置作为编码器偏置写入 Flash主站读取后用偏置补偿。这一步在第一次上电调试时花半小时做干净后面会省很多事。4.4 控制周期、线程优先级与总线超时实际运行时控制周期我一般设 2ms500HzEtherCAT 总线周期可以做到 1ms。两者不需要完全一致但建议总线程周期不超过控制周期否则反馈数据会显得“旧”。线程优先级方面EtherCAT 主站发送线程设成 RT 优先级 85 左右ROS2 Control 的控制循环保持在默认优先级避免两边的实时线程互相抢 CPU触发看门狗。总线超时需要区分“数据帧超时”和“看门狗超时”。SOEM 返回 EC_ERR 时一般是一帧数据没发出去重试即可如果从站状态从 OP 跌到 SAFE-OP那是从站看门狗超时说明主站连续多个周期没刷新数据这时要重点查线程调度是否被阻塞、网卡中断是否被其他任务拖死。我踩过最典型的坑是上位机里跑了一个move_group的 RViz 拖动操作UI 线程吃掉大量 CPU直接把总线线程挤占导致从站周期超时连锁掉线。5. 启动管线从 URDF 到关节真正动起来5.1 URDF 与 ros2_control 标签要合成一个文件机械臂的 URDF 是整个系统的基础。你可以用 SolidWorks 插件导出一个粗略模型也可以直接用 MoveIt Setup Assistant 从零搭一个简化模型。关键在于每个关节必须有明确的 lower/upper limit 和 effort/velocity limit这些值会被 MoveIt2 用来做规划边界也会被 ROS2 Control 用来做安全校验。在 URDF 末尾加入 ros2_control 标签让它能加载我们写的硬件插件ros2_control nameSixAxisArm typesystem hardware pluginarm_hardware/EthercatSixAxisArm/plugin param nameethercat_ifenp3s0/param param namecycle_us1000/param /hardware joint namejoint1 command_interface nameposition/ state_interface nameposition/ state_interface namevelocity/ /joint joint namejoint2 command_interface nameposition/ state_interface nameposition/ state_interface namevelocity/ /joint !-- joint3 ~ joint6 依此类推 -- /ros2_controlplugin 名必须和代码里PLUGINLIB_EXPORT_CLASS注册的类名一致否则启动时报“找不到插件”。这个错误很常见但并不难排查——ros2 pkg plugins --base hardware_interface能看到当前系统里注册的硬件接口插件列表务必确认你的类在列表里。5.2 控制器配置文件里需要真正理解的地方创建 config/controllers.yaml配置两个控制器一个负责发布关节状态一个负责接收轨迹目标。arm_controller: type: joint_trajectory_controller/JointTrajectoryController joints: - joint1 - joint2 - joint3 - joint4 - joint5 - joint6 state_publish_rate: 100 action_monitor_rate: 20 joint_state_broadcaster: type: joint_state_broadcaster/JointStateBroadcasterjoint_trajectory_controller 会接收 MoveIt2 发来的 FollowJointTrajectory action并按时间戳插值生成轨迹点。接口类型如果只有一个 position它的插值方式就是线性插值位置速度不直接控制如果加了 velocity 接口控制器会做梯形速度规划。首次验证建议只用 position 接口简单直接不容易炸。5.3 启动顺序决定你是不是要盲目等三分钟实际启动牵扯到 lifecycle 节点ROS2 Control 里 controller_manager 是一个带 lifecycle 的节点控制器本身是它的子组件。启动流程是启动 robot_state_publisher发布静态 TF 和 robot_description。启动 controller_manager加载 arm_controller 和 joint_state_broadcaster。硬件接口 on_activate 执行此时 EtherCAT 主站建立连接。激活 joint_state_broadcaster 和 arm_controller。在 launch 文件里我会先启动一个 EtherCAT 主站节点负责扫描从站和进入 OP 状态再启动 controller_manager因为硬件接口的 on_activate 依赖主站已经就绪。如果顺序反了硬件接口会在 on_activate 里反复尝试打开网卡表现为启动过程特别慢屏幕上全是超时错误。推荐用如下顺序的 launch 文件ros2 launch arm_bringup bringup.launch.py正常日志会依次显示从站进入 OP、硬件接口激活、控制器激活成功。看到 “Successfully created ROS timer with period ...” 基本就是跑起来了。5.4 一步步验证控制器列表、关节状态、单轴手动运动进入运行状态后先不要急着发目标手动验证一遍ros2 control list_controllers # 期望两个 controller 都是 active ros2 topic echo /joint_states --once # 能看到六个关节的 position/velocity数值应该来自真实编码器确认反馈正常后用命令行直接给一个关节发正弦或单步位置目标比如关节 3 移动到 0.2 radros2 action send_goal /arm_controller/follow_joint_trajectory control_msgs/action/FollowJointTrajectory \ -f { goal: { trajectory: { joint_names: [joint1,joint2,joint3,joint4,joint5,joint6], points: [ { positions: [0.0, 0.0, 0.2, 0.0, 0.0, 0.0], time_from_start: {sec: 2} } ] } } }执行的时候人离开机械臂活动范围手放在急停上观察它是否按期望方向和速度移动。这一步验证了ROS2 Control 能发指令 → 硬件接口能转换单位 → EtherCAT 能把数据写进伺服 → 电机响应。链路到这里算打通了一半。6. MoveIt2 配置与从规划到执行的完整链路6.1 用 MoveIt Setup Assistant 生成 SRDF 与配置包MoveIt2 的核心配置是 SRDF 和规划组。运行 Setup Assistant 导入 URDFros2 run moveit_setup_assistant moveit_setup_assistant新手最容易漏掉的是碰撞矩阵Setup Assistant 会自动计算相邻连杆不可碰撞但不相邻的连杆默认也不碰撞这是理想模型。实际机械臂的线束、法兰、末端工具可能超出一根杆的几何范围导致 MoveIt2 规划出一条“理论不碰、实际碰线束”的路径。所以第一次生成配置后要手动添加不许碰撞的连杆对或者直接用几何体把线束、末端工具包成大致的圆柱再重新生成碰撞矩阵。6.2 规划组、运动学求解器与 OMPL 的取舍规划组arm包含 joint1 到 joint6设置末端执行器为 tool0。运动学求解器不建议用默认的 KDL换成 TRAC-IK奇异点附近成功率高很多。规划器用 OMPL 的 RRTConnect它对六轴空间搜索效率和成功率都比较均衡。MoveIt 规划的第一步是“找位形”IK 失败会导致规划器找不到目标状态的可行解。如果你在 RViz 拖拽末端时发现目标变红、规划不了多半是 IK 没解出来换 TRAC-IK 后大概能解决八成问题。剩下两成是目标点本身在机械臂可达范围之外这个只能通过工作空间分析提前规避。6.3 从 RViz 拖拽目标到真实机械臂动起来配置好 move_group 后启动 MoveIt2 的规划执行 launchros2 launch arm_moveit_config moveit_planning_execution.launch.pyRViz 打开 Motion Planning 面板后你会看到当前机械臂模型和真实机械臂的关节角应该基本一致这是 /joint_states 反馈驱动的。在 InteractiveMarker 里拖动末端到目标位置点 Plan再点 Execute。move_group 会把规划好的轨迹通过 FollowJointTrajectory action 发给 arm_controller然后 ros2_control 负责执行。第一次执行时我会做一件事把 RViz 里的“允许启动状态”选项打开否则 move_group 会报“start state collision”之类错误。同时检查arm_controller的动作服务器名字是否和 move_group 配置里一致。一般 setup assistant 生成的 controllers.yaml 已经自动匹配但如果你之间的机器人命名空间没配对就会出现 move_group 发了目标但找不到控制器的现象日志里会明确提示。6.4 第一次规划失败的排查顺序不管前期多顺利第一次完整规划执行大概率会出问题。常见失败按顺序排查目标点是否在机械臂可达范围内、是否处于奇异位形附近。开始状态是否和 MoveIt2 内部模型一致最好先手动执行一次 return-to-home 轨迹把状态同步到模型。关节限制是否和 URDF 一致如果 URDF 里角度单位写错一位小数点规划器会在某个关节上限附近反复失败。自碰撞矩阵是否误杀有些连杆对在接近真实姿态时会被碰撞检测拦下。时间参数化是否合理同样的路径如果在 2 秒内完成六个关节各 90 度的运动规划器会提示速度超限。这五个问题里面第一类最好排查第二类最常见第三类最阴间——它往往只在某个特殊位形暴露不影响其他路径。7. 我实际跑起来之后踩过的坑按现象分类记在这里7.1 从站一直停在 SAFE-OP进不了 OP 状态有一个很典型的现象总线扫描正常从站在 SAFE-OP但启动后 OP 状态始终立不起来。日志显示 PDO 长度校验失败。我排查了很久最后发现是 ESI 文件里的 PDO 映射和伺服驱动器实际固件版本不一致。厂商更新固件后输入端多了一个扩展状态字PDO 总字节数从 16 变成 20但主站侧加载的还是旧版 ESI。解决方法是替换成和新固件配套的 ESI 文件重新扫描再对一遍 PDO 信息。以后遇到这种问题不要急着改代码先对比从站实际发送的 PDO 大小和主站配置的大小是否一致。7.2 controller_manager 报 activate failed但 MoveIt2 和 EtherCAT 都正常这种情况最气人总线通了从站在 OPEtherCAT 主站正常但 ROS2 Control 里arm_controller激活超时。排查后发现是硬件接口的on_activate阻塞时间太长。原因是它在打开 EtherCAT 主站时做了一个几秒钟的重试循环而 controller_manager 激活控制器的默认超时只有 3 秒。解决办法有两个一是把重试逻辑移到一个独立初始化函数里让 on_activate 快速返回二是把 hardware interface 的 read 和 write 在激活前就准备好激活后直接开始通信。这也是为什么我建议启动顺序里先让 EtherCAT 主站独立启动再加载 controller_manager。7.3 规划成功但电机实际反向转动甚至剧烈抖动这个问题在第一次执行点动时就会出现。反向转动是方向表的问题前文已经提过不再赘述。剧烈抖动则更棘手——我遇到过位置模式下增益调太高伺服从站内部位置环和 ROS2 控制循环产生共振。解决办法不是改代码而是把伺服从站的刚性参数调低或者把 ROS2 侧控制周期从 1ms 放宽到 2ms。如果你的机械臂本身刚性比较差更要注意不能一上来就追求极端的 1ms 周期先跑稳再提速。7.4 总线偶尔丢帧从站看门狗报警周期性出现“丢帧报警”或者“从站掉线——恢复——再掉线”大概率不是硬件问题而是主站线程优先级不够。在 Ubuntu 普通内核下桌面环境、USB 事件、Wi-Fi 网卡都会抢占 CPU。我的处理方式把 EtherCAT 主站线程设置成SCHED_FIFO80并把进程用taskset绑定到某个 CPU core同时关掉该核上的其他中断亲和性情况立刻稳定下来。如果你的网卡是 USB 转网口基本可以断定会不稳定尽量用主板自带的 PCIe 网卡。8. 一些剩下的建议与我的个人体会整条链路跑通之后回头再看最花时间的其实不是代码而是各种“单位对不上”“方向反了”“时序超时”这些工程细节。硬件接口和 MoveIt2 的配置都是标准套路真正要花的心思在于把伺服从站的手册读明白把每个关节的脉冲/弧度/方向用一个统一的数据结构管理起来并且做好日志记录。我个人的建议是第一次跑通时把所有速度设到原来的 10%把伺服从站的加速度限制也调低目的不是效率而是保护设备和调试人员。机械臂上电后的第一次真实运动手一定要放在急停按钮上不要依赖软件层面的限位——软件限位只是防线之一硬急停才是最后的保险。等你在低速度下确认了每个关节的方向、零位和行程再逐步放开速度。从启动 EtherCAT 主站到 MoveIt2 里拖拽执行中间确实隔着一堆配置和代码调试但把这几个环节拆开理解每一条链路都不复杂。也希望这篇文字能帮你少走几个我走过的弯路留出来的时间多做几次整机标定和轨迹精度测试。
返回列表