ARTICLE DETAIL

资讯详情

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

ROS2 MoveIt2 Servo实战:UR机械臂实时遥操作与动态响应调参指南

ROS2 MoveIt2 Servo实战:UR机械臂实时遥操作与动态响应调参指南 1. 为什么要在ROS2里折腾MoveIt2 Servo如果你手上有台UR机械臂又刚好在ROS2环境里跑MoveIt2大概率会遇到一个尴尬的场景规划好的轨迹走起来没问题但一旦想让它跟着手柄或者外部传感器的信号实时动起来整个系统就变得又卡又迟钝。MoveIt2默认的规划执行链路是规划—执行两段式的一次完整的规划周期动辄几百毫秒根本谈不上什么实时响应。MoveIt2 Servo就是来解决这个问题的。它绕开了传统的规划器直接把笛卡尔空间的速度指令或者关节空间的增量指令以高频通常100Hz到500Hz喂给机械臂的控制器实现真正意义上的实时遥操作和动态响应。我第一次接触Servo是在一个需要人手拖动UR5e做示教的场景里当时用MoveIt2的笛卡尔规划做跟随延迟大到操作员直接骂人换成Servo之后整个手感完全不一样了。这篇文章适合已经装好ROS2和MoveIt2、手上有UR机械臂或者Gazebo仿真环境、想让机械臂跟手的人。我会从Servo的核心原理讲起把配置、启动、调参、踩坑的完整过程拆开说代码和配置都是可以直接抄的。如果你还没装ROS2建议先把Humble或者Jazzy跑通UR的驱动包和MoveIt2配置能正常加载再来看这篇。2. MoveIt2 Servo的核心机制与方案选型2.1 Servo到底在做什么绕开规划器的实时控制回路传统MoveIt2的运动执行流程是这样的你给一个目标位姿规划器OMPL、Pilz等算出一条从当前状态到目标状态的完整轨迹然后通过FollowJointTrajectory这个Action接口把轨迹点发给控制器控制器再按时间戳插值执行。这条链路里规划本身可能就要几十到几百毫秒而且每次都要重新算根本没法做连续的高频控制。Servo的思路完全不同。它维护一个自己的控制循环你通过话题或者服务持续给它发目标速度或者目标位姿增量它在每个循环周期里做一次正运动学/逆运动学计算把笛卡尔速度映射成关节速度然后直接通过JointJog或者Twist命令发给控制器。整个过程没有规划只有即时的IK求解和速度限幅。这里有个关键点Servo输出的不是位置指令而是速度指令。这意味着底层控制器必须支持速度控制接口。UR的ur_robot_driver在ROS2下是通过scaled_joint_trajectory_controller或者forward_velocity_controller来接收速度指令的前者更常用因为它内部做了速度和位置的混合处理。2.2 为什么选Servo而不是自己写节点有人可能会想不就是发速度指令吗我自己写个节点订阅手柄、算个IK、发JointJog不就行了。理论上可以但实际做起来有几个坑IK求解的实时性和稳定性自己调KDL或者IKFast在奇异点附近很容易解出跳变的角度机械臂会突然抖一下。Servo内部用了平滑和限幅策略还会在接近奇异点时自动降速。碰撞检测Servo可以配置成在每次循环里做碰撞检查虽然会牺牲一点频率但安全性高很多。自己写的话这部分要重新实现。命令类型统一Servo支持Twist、JointJog、Pose三种命令输入还能混合使用自己写要处理各种边界情况。和MoveIt2的集成Servo是MoveIt2官方组件和move_group共享规划场景和机器人模型不用重复加载URDF和SRDF。所以除非你有非常特殊的需求否则直接用Servo是性价比最高的方案。2.3 UR机械臂在ROS2下的控制接口现状UR机械臂在ROS2里的驱动是ur_robot_driver它底层通过UR的RTDE接口和Dashboard接口通信。在Humble和Jazzy下驱动包已经比较成熟了但有几个点要注意控制器类型默认启动的scaled_joint_trajectory_controller同时接受位置和速度指令是Servo最常用的搭配。如果你用的是joint_trajectory_controller它只接受位置指令Servo发速度进去会被忽略或者报错。URSim和真机如果你没有真机可以用URSimUR官方仿真或者Gazebo里的UR模型。URSim的RTDE接口和真机一致调试Servo的手感更接近真实。Gazebo的话ign_ros2_control或者gazebo_ros2_control的配置要单独调速度控制的响应特性和真机有差异。实时性UR的RTDE接口本身是500Hz的但ROS2这边的控制循环能不能跑到那么高取决于你的机器性能和DDS配置。一般跑100Hz到200Hz是比较稳的。3. 环境准备与Servo配置实操3.1 基础环境确认ROS2、MoveIt2、UR驱动假设你用的是Ubuntu 22.04 ROS2 Humble或者Ubuntu 24.04 ROS2 Jazzy。先确认这几个包都装好了# 确认ROS2版本 echo $ROS_DISTRO # 确认MoveIt2相关包 ros2 pkg list | grep moveit # 确认UR驱动 ros2 pkg list | grep ur_robot_driver如果moveit_servo没装用apt补上sudo apt install ros-$ROS_DISTRO-moveit-servoUR驱动如果没装Humble下可以sudo apt install ros-humble-ur-robot-driver ros-humble-ur-descriptionJazzy下包名类似把humble换成jazzy即可。如果你用的是URSim还需要装ur-simulation相关的包或者直接用Docker跑URSim。3.2 UR的MoveIt2配置包生成Servo需要机器人的URDF、SRDF和运动学配置。最省事的办法是用MoveIt2的Setup Assistant生成一个配置包。如果你已经有UR的MoveIt2配置包比如ur5e_moveit_config可以直接用。自己生成的话ros2 run moveit_setup_assistant moveit_setup_assistant加载UR的URDF可以从ur_description包里拿然后按步骤走Self-Collision默认采样密度就行UR的连杆不多几秒就完事。Planning Groups建一个ur_manipulator组 kinematic solver选KDLKinematicsPlugin或者pick_ik。Servo对IK求解器的要求是能快速返回KDL够用但pick_ik在奇异点附近更稳。Robot Poses可以设一个home位姿方便后面测试。End Effectors如果你有夹爪加上没有就跳过。ROS 2 Control这一步要选对UR的控制器配置在ur_robot_driver里已经有了这里主要是生成MoveIt2侧的接口。Launch Files生成demo.launch.py后面改Servo配置会用到。生成完之后配置包里会有一个config/servo.yaml如果没有手动建一个这就是Servo的核心配置文件。3.3 servo.yaml的关键参数逐条拆解Servo的配置参数不少但真正影响手感和稳定性的就那么几个。下面这份是我在UR5e上实测比较稳的配置逐条解释use_gazebo: false command_in_type: speed_units # 推荐用speed_units单位清晰 scale: linear: 0.4 rotational: 0.8 joint: 0.5 publish_period: 0.01 # 100HzUR上够用再高DDS可能扛不住 low_pass_filter_coeff: 2.0 # 滤波系数越大越平滑但延迟越高 move_group_name: ur_manipulator planning_frame: base_link ee_frame_name: tool0 robot_link_command_frame: base_link command_out_type: trajectory_msgs/JointTrajectory command_out_topic: /scaled_joint_trajectory_controller/joint_trajectory is_primary_planning_scene_monitor: true monitor_planning_scene: true check_collisions: true collision_check_rate: 10.0 self_collision_proximity_threshold: 0.01 scene_collision_proximity_threshold: 0.03 incoming_command_timeout: 0.1 num_outgoing_halt_msgs_to_publish: 4 joint_limit_margin: 0.1 override_velocity_scaling_factor: 0.1 smoothing_filter_plugin_name: online_signal_smoothing::ButterworthFilterPlugin几个关键点展开说command_in_typespeed_units表示你发的Twist里的线速度单位是m/s角速度是rad/s。另一个选项unitless是把输入当成-1到1的归一化值再乘以scale。我建议用speed_units因为你可以直接算出来我让机械臂末端以0.1m/s移动对应的指令是多少调试的时候心里有数。scale这个是在speed_units基础上再乘的系数。比如你发Twist.linear.x 0.1scale.linear 0.4实际末端速度就是0.04m/s。UR5e的末端最大速度大概在1m/s左右但实际遥操作时0.05到0.2m/s比较舒服太快了人反应不过来。publish_period控制循环周期。0.01秒就是100Hz。UR的RTDE是500Hz但ROS2这边经过DDS传输和IK计算100Hz是比较稳的。如果你机器性能好可以试0.005200Hz但要注意CPU占用。low_pass_filter_coeff低通滤波用来平滑输入信号。系数越大滤波越强延迟越大。2.0是个折中值如果你用手柄操作觉得抖可以加到3.0如果觉得延迟大降到1.0。check_collisions开启碰撞检查后Servo会在每个循环里检查当前状态和规划场景里的碰撞体。UR5e本身连杆少检查很快但如果你场景里有很多障碍物可能会拖慢循环。collision_check_rate设成10Hz不用每个循环都查。override_velocity_scaling_factor这个参数容易被忽略但它决定了Servo输出速度的上限。0.1表示只允许用UR控制器最大速度的10%。UR5e的关节最大速度是180度/秒10%就是18度/秒对于遥操作来说已经很快了。如果你发现机械臂动得太慢先检查这个参数。3.4 启动文件怎么写把Servo节点拉起来Servo的启动文件需要做几件事加载机器人模型、启动move_group、启动servo_node、配置好话题重映射。下面是一个精简版的launch文件基于UR的官方配置改的from launch import LaunchDescription from launch_ros.actions import Node from moveit_configs_utils import MoveItConfigsBuilder def generate_launch_description(): moveit_config ( MoveItConfigsBuilder(ur5e, package_nameur5e_moveit_config) .to_moveit_configs() ) servo_node Node( packagemoveit_servo, executableservo_node, nameservo_node, parameters[ moveit_config.to_dict(), {move_group_name: ur_manipulator}, {planning_frame: base_link}, {ee_frame_name: tool0}, {command_out_topic: /scaled_joint_trajectory_controller/joint_trajectory}, {command_in_type: speed_units}, {publish_period: 0.01}, {check_collisions: True}, {override_velocity_scaling_factor: 0.1}, ], outputscreen, ) return LaunchDescription([servo_node])注意moveit_config.to_dict()会把URDF、SRDF、运动学配置都塞进参数里Servo节点启动时会自己加载。如果你用的是自定义配置包把package_name换成你的包名。启动顺序很重要先启动UR驱动和控制器再启动move_group最后启动servo_node。如果Servo先起来它找不到规划场景会报错。4. 实时遥操作的完整实现流程4.1 从手柄到机械臂数据链路全打通假设你用一个普通的游戏手柄比如Xbox手柄做遥操作完整的链路是这样的手柄的joy节点发布sensor_msgs/Joy消息包含摇杆的轴数据。你写一个转换节点订阅/joy把摇杆轴映射成geometry_msgs/Twist发布到/servo_node/delta_twist_cmds。Servo节点订阅这个Twist在每个控制周期里做IK算出关节速度发布JointTrajectory到UR控制器。UR控制器通过RTDE把速度指令发给机械臂。这个转换节点是核心下面是一个最小实现import rclpy from rclpy.node import Node from sensor_msgs.msg import Joy from geometry_msgs.msg import Twist class JoyToTwist(Node): def __init__(self): super().__init__(joy_to_twist) self.sub self.create_subscription(Joy, /joy, self.joy_cb, 10) self.pub self.create_publisher(Twist, /servo_node/delta_twist_cmds, 10) self.linear_scale 0.2 self.angular_scale 0.5 def joy_cb(self, msg): twist Twist() # 左摇杆控制XY右摇杆控制Z和偏航 twist.linear.x msg.axes[1] * self.linear_scale twist.linear.y msg.axes[0] * self.linear_scale twist.linear.z msg.axes[4] * self.linear_scale twist.angular.z msg.axes[3] * self.angular_scale # 其他轴根据需要映射 self.pub.publish(twist) def main(): rclpy.init() node JoyToTwist() rclpy.spin(node) rclpy.shutdown()这里有个细节Servo的delta_twist_cmds话题期望的Twist是在robot_link_command_frame通常是base_link下的。如果你想让机械臂按工具坐标系移动需要把Twist转换到工具坐标系或者把robot_link_command_frame设成tool0。我一般用base_link因为操作员看着机械臂的时候脑子里想的是往前、往左、往上对应基座坐标系更直观。4.2 速度指令的标定与手感调优手柄摇杆推到底机械臂应该动多快这个没有标准答案取决于你的任务。但有一个标定方法先把scale.linear设成0.1override_velocity_scaling_factor设成0.05让机械臂慢慢动。推摇杆到底用秒表测机械臂末端移动10厘米需要几秒。比如用了2秒那实际速度就是0.05m/s。根据任务需求调整。如果是精细装配0.02到0.05m/s比较合适如果是大范围移动0.1到0.2m/s。调low_pass_filter_coeff让启动和停止的时候没有明显的抖动。UR5e的关节减速比比较大滤波系数2.0到3.0之间比较舒服。还有一个容易忽略的点死区。手柄摇杆在中位附近会有漂移如果不设死区机械臂会自己慢慢动。在转换节点里加一个死区判断def apply_deadzone(value, deadzone0.1): if abs(value) deadzone: return 0.0 return (value - deadzone * (1 if value 0 else -1)) / (1 - deadzone)这个函数把死区内的值归零死区外的值重新映射到0到1。死区大小根据手柄质量调一般0.05到0.15。4.3 关节空间Jog与笛卡尔Twist的混合使用Servo支持两种命令delta_twist_cmds笛卡尔速度和delta_joint_cmds关节速度。实际遥操作里我经常把两者混用大部分时间用笛卡尔Twist因为操作员想的是末端怎么动。当机械臂接近奇异点笛卡尔IK解不出来的时候切到关节Jog手动把某个关节转出奇异区。或者在某些任务里比如让机械臂沿着一个固定轴移动用关节Jog更直接。切换的方式很简单发对应的话题就行。Servo内部会处理两种命令的优先级但要注意不要同时发否则会打架。可以在转换节点里加一个模式切换按钮按下时只发一种命令。关节Jog的消息类型是sensor_msgs/JointState里面只需要填velocity字段from sensor_msgs.msg import JointState joint_cmd JointState() joint_cmd.name [shoulder_pan_joint, shoulder_lift_joint, elbow_joint, wrist_1_joint, wrist_2_joint, wrist_3_joint] joint_cmd.velocity [0.1, 0.0, 0.0, 0.0, 0.0, 0.0] pub.publish(joint_cmd)注意关节名称要和URDF里的一致顺序也要对。UR的关节名称是固定的但如果你用的是自定义URDF要确认一下。4.4 在Gazebo里先跑通再上真机如果你没有真机或者不想一上来就在真机上试Gazebo是个不错的过渡。UR的Gazebo仿真在ROS2下已经比较成熟了ur_simulation_gz包提供了完整的配置。启动Gazebo仿真ros2 launch ur_simulation_gz ur_sim_control.launch.py ur_type:ur5e然后在另一个终端启动MoveIt2和Servoros2 launch ur5e_moveit_config moveit_rviz.launch.py ros2 launch your_pkg servo.launch.pyGazebo里的速度控制和真机有差异主要是Gazebo的物理引擎对速度指令的响应有延迟而且摩擦力模型和真机不一样。所以Gazebo里调好的参数上真机后还要微调。但至少可以把整个链路跑通确认话题、控制器、Servo配置都没问题。5. 常见问题与排查技巧实录5.1 Servo节点启动就报错找不到规划场景这是最常见的问题报错信息通常是Planning scene not received或者move_group not available。原因一般是启动顺序不对或者move_group没起来。排查步骤确认move_group节点在跑ros2 node list | grep move_group。确认Servo节点订阅的规划场景话题有数据ros2 topic hz /monitored_planning_scene。如果move_group起来了但Servo还是报错检查is_primary_planning_scene_monitor参数。如果多个节点都设成true会冲突。Servo设成true其他节点设成false。还有一个坑如果你用的是UR的官方ur5e_moveit_config它默认启动的move_group可能没有加载Servo需要的规划场景监控。需要在launch文件里加上monitor_planning_scene参数。5.2 机械臂不动或者动一下就停Servo有一个incoming_command_timeout参数默认0.1秒。如果你发的Twist频率低于10HzServo会认为命令超时自动停止。手柄的joy节点一般是50Hz到100Hz没问题但如果你自己写的转换节点发布频率太低就会触发超时。另外num_outgoing_halt_msgs_to_publish参数控制超时后发多少个停止指令。默认4个够用。如果你发现机械臂在停止时还有惯性滑动可以加大这个值。还有一种情况机械臂收到指令但不动。检查command_out_topic是否和UR控制器的话题一致。UR的scaled_joint_trajectory_controller默认话题是/scaled_joint_trajectory_controller/joint_trajectory但如果你改了控制器名称话题也要改。用ros2 topic list确认一下。5.3 接近奇异点时机械臂剧烈抖动UR5e的腕部奇异点在wrist_2_joint接近0度的时候。Servo在接近奇异点时IK求解会变得不稳定关节速度可能突然变大。Servo内部有奇异点处理但不是万能的。我的做法是在转换节点里加一个奇异点预警当wrist_2_joint的角度小于15度时自动降低scale.linear和scale.rotational。或者干脆在接近奇异点时切到关节Jog手动转出去。如果任务允许重新规划一条避开奇异点的路径或者调整工具坐标系的方向。还有一个参数singularity_threshold在Servo的配置里叫lower_singularity_threshold和hard_stop_singularity_threshold可以调整奇异点的判定范围。默认值对UR5e来说偏保守可以适当放宽但要小心。5.4 碰撞检查拖慢控制频率开启check_collisions后如果规划场景里有很多障碍物Servo的循环时间会变长导致控制频率下降。表现是机械臂动作一顿一顿的。解决办法降低collision_check_rate比如从10Hz降到5Hz。简化规划场景里的碰撞体用包围盒代替精细网格。如果任务本身很安全可以临时关掉碰撞检查但要做好安全防护。UR5e本身有碰撞检测功能通过关节电流监测所以即使Servo这边不查机械臂碰到东西也会停。但Servo的碰撞检查是提前预防两者互补。5.5 常见问题速查表现象可能原因排查方法解决Servo启动报错找不到规划场景move_group未启动或启动顺序错ros2 node list确认先启动move_group再启动servo机械臂不动command_out_topic不匹配ros2 topic list改成UR控制器实际话题动一下就停incoming_command_timeout超时ros2 topic hz看发布频率提高发布频率到50Hz以上接近奇异点抖动IK求解不稳定观察wrist_2_joint角度降速或切关节Jog动作一顿一顿碰撞检查拖慢循环看CPU占用和循环时间降collision_check_rate或关碰撞检查机械臂动得太慢override_velocity_scaling_factor太小检查参数值适当加大但别超过0.3停止时有惯性滑动减速时间不够观察停止过程加大num_outgoing_halt_msgs_to_publish6. 进阶让Servo响应更跟手的几个技巧6.1 用Butterworth滤波器替代默认低通Servo默认的低通滤波是一阶的在快速启停时会有明显的延迟。可以换成Butterworth滤波器它在通带内更平坦相位延迟更小。配置里加smoothing_filter_plugin_name: online_signal_smoothing::ButterworthFilterPlugin然后在参数里设butterworth_filter_order: 2。二阶Butterworth在UR5e上实测比一阶低通跟手很多尤其是做快速往复运动的时候。6.2 动态调整scale根据末端速度自适应固定scale的问题是慢速精细操作时太快快速移动时又太慢。可以写一个自适应节点根据当前末端速度动态调整scale。逻辑是当末端速度低于0.02m/s时scale.linear设成0.2方便精细操作。当末端速度高于0.1m/s时scale.linear设成0.6方便快速移动。中间线性插值。这个节点订阅/servo_node/status里面包含当前末端速度然后通过ros2 param set动态改Servo的参数。注意Servo的参数是启动时加载的动态改需要Servo支持参数回调。如果不支持可以改成在转换节点里直接对Twist做缩放效果类似。6.3 用ROS2的QoS配置降低延迟Servo和UR控制器之间的通信对延迟很敏感。ROS2默认的QoS是RELIABLE会做重传增加延迟。可以改成BEST_EFFORTfrom rclpy.qos import QoSProfile, ReliabilityPolicy qos QoSProfile(depth1, reliabilityReliabilityPolicy.BEST_EFFORT) self.pub self.create_publisher(Twist, /servo_node/delta_twist_cmds, qos)depth1表示只保留最新的一条消息避免队列积压。BEST_EFFORT不重传丢包就丢包对实时控制来说反而更好。6.4 多线程回调组避免阻塞如果你的转换节点里还有其他耗时的回调比如读传感器、做视觉处理要用ROS2的CallbackGroup把Servo的命令发布放到独立的线程里避免被阻塞。配置from rclpy.callback_groups import ReentrantCallbackGroup from rclpy.executors import MultiThreadedExecutor self.cb_group ReentrantCallbackGroup() self.sub self.create_subscription(Joy, /joy, self.joy_cb, 10, callback_groupself.cb_group) self.pub self.create_publisher(Twist, /servo_node/delta_twist_cmds, 10) executor MultiThreadedExecutor() executor.add_node(node) executor.spin()这样即使joy_cb里有耗时操作也不会影响其他回调。但要注意线程安全共享数据要加锁。6.5 真机调试的安全 checklist上真机之前这几件事一定要做限速override_velocity_scaling_factor先设0.05确认没问题再往上加。急停按钮手边一定要有UR的急停按钮或者软件急停。工作空间限制在UR的示教器里设好安全平面防止机械臂撞到桌子或人。空跑测试先不装工具让机械臂空跑几分钟确认没有异常抖动。日志记录用ros2 bag record把/joint_states、/servo_node/status、/servo_node/delta_twist_cmds都录下来出问题可以回放分析。我在第一次上真机的时候因为没设工作空间限制机械臂差点撞到桌子。后来在UR示教器里设了安全平面又加了软件限位才敢放心跑。这个教训希望大家不要重复。7. 从遥操作到动态响应Servo还能怎么用Servo不只是用来做手柄遥操作的。任何需要高频、实时控制机械臂的场景都可以用Servo。比如视觉伺服相机检测到目标偏移算出Twist直接发给Servo机械臂实时跟踪。比传统的检测—规划—执行快一个数量级。力控装配力传感器检测到接触力通过导纳控制算出速度修正量发给Servo做柔顺装配。人机协作操作员直接用手拖动机械臂UR有拖动示教模式Servo可以叠加一个辅助力或者虚拟约束让拖动更省力或者限制在某些方向。移动抓取机械臂装在移动底盘上底盘移动时机械臂用Servo做动态补偿保持末端稳定。这些场景的核心都是高频反馈实时响应Servo提供的正是这个能力。把Servo和ROS2的其他组件比如ros2_control、image_transport、force_torque_sensor_broadcaster结合起来能玩出很多花样。我个人在实际操作中的体会是Servo的调参没有一劳永逸的方案不同的任务、不同的机械臂、甚至不同的操作员适合的参数都不一样。最好的办法是先把基础配置跑通然后根据具体任务慢慢调。调参的时候要有耐心每次只改一个参数改完记录效果不然很容易调乱。另外真机调试一定要把安全放在第一位限速、急停、工作空间限制一个都不能少。
返回列表