本文参考代码来源:https://github.com/AntoBrandi/Robotics-and-ROS-2-Learn-by-Doing-Manipulators/tree/main

问题一:gazebo界面时间卡顿导致控制无法连接

解决方案:

(1)用meshlab减少stl文件面数

1.下载与安装

  • 官网下载

  • Windows/Mac/Linux 都有对应版本,安装即可。我下的windows版本。

2. 导入模型

  • 打开 MeshLab。

  • 直接把你的 STL 文件(比如 armA_link1.stl)拖进去,或者 File -> Import Mesh。

  • 查看当前面数:看界面底部的信息栏,找到 Faces。

    • 如果显示 75,000,那就是太大了。

    • 我们的目标是把它降到 1,000 ~ 3,000 左右(或者原大小的 5%)。

3. 执行简化操作 (Decimation)

  • 在顶部菜单栏找到:
    Filters (滤镜) -> Remeshing, Simplification and Reconstruction (重网格化与简化) -> Quadric Edge Collapse Decimation (二次误差边折叠简化)。
    (这是最常用的保留形状的简化算法)

4. 设置参数 (关键步骤)
弹出的窗口里有几个参数,只需要关注这几个:

  • Target number of faces (目标面数):你可以直接填一个数字。我设置的都是1000-2000

  • Percentage reduction (百分比):或者填 0.05 (代表保留 5%,即减少 95%)。通常建议先填 0.1 (10%) 试试。

  • Preserve Boundary of the mesh (保留边界):一定要勾选! 这能防止模型边缘破损。

  • Preserve Topology (保留拓扑结构):一定要勾选! 防止模型出现破洞。

5. 应用与检查

  • 点击 Apply。

  • 此时你会发现模型表面的网格变得很稀疏,原来的圆滑曲面变成了有棱角的。

  • 看底部的 Faces 数量,确认已经变小了。

6. 导出文件

  • File -> Export Mesh As...

  • 文件名:建议改为 armA_link1_collision.stl(加上后缀以示区分)。

  • 格式:选择 .stl。

  • 保存时会弹出一个框,取消勾选 "Binary encoding" (可选,文本格式方便调试,二进制更小,通常默认即可),其他保持默认,点击 OK。

每次只调整第一行的数字即可。

把减少面数的文件保存到原本的文件夹路径下,然后更改urdf.xacro文件

<link name="agv_base_link">
    <inertial>
      <origin xyz="0.0057835 0.0046209 -0.0611" rpy="0 0 0" />
      <mass value="36.922" />
      <inertia ixx="1.865" ixy="0.045437" ixz="-6.9959E-06" iyy="3.7561" iyz="-0.00014461" izz="5.5837" />
    </inertial>
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0" />
      <geometry>
        <!-- Visual 使用原始高精度模型 -->
        <mesh filename="${base_mesh_path}agv_base_link.STL" />
      </geometry>
      <material name="white">
        <color rgba="1 1 1 1" />
      </material>
    </visual>
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0" />
      <geometry>
        <!-- Collision 使用 MeshLab 生成的低模 -->
        <mesh filename="${base_mesh_path}agv_base_link_collision.stl" />
      </geometry>
    </collision>
  </link>

(2)controller.launch.py中正确设置较长的控制器加载时长

终端报错提示:spawner: error: unrecognized arguments: --timeout 120。
这意味着你修改 controller.launch.py 时用的参数名不对(ROS 2 版本差异)。

请回到 VS Code,修改 src/three_arms_controller/launch/controller.launch.py。

查找:
"--timeout", "120"

全部替换为:
"--controller-manager-timeout", "120"

joint_state_broadcaster_spawner = Node(
        package="controller_manager",
        executable="spawner",
        arguments=[
            "joint_state_broadcaster",
            "--controller-manager",
            "/controller_manager",
            # 增加超时时间,确保所有控制器都加载完成,我添加的
            "--controller-manager-timeout", "120"
        ],
    )

这条报错消失

(3)gazebo界面物理引擎设置

gazebo版本是Ignition Gazebo (6.16.0)

右下角0.55%意味着仿真世界的时间流逝速度只有现实世界的 0.5%(慢了200倍)

点击右上角菜单:
找到界面最右上角的三个垂直小圆点(⋮)图标(在关闭按钮下方)。

搜索插件:
在弹出的搜索框里输入:Physics。

添加插件:
你会看到一个叫 Physics 的搜索结果。点击它。

调节参数:
现在屏幕右侧应该会出现一个新的面板(或者混在右侧列表中)。找到它,尝试修改以下数值:(1)Max Step Size (最大步长):默认通常是 0.001。请尝试改成 0.004 或 0.01。改完后按回车,观察右下角的百分比是否飙升。

我最后设置为0.01

(2)Real Time Update Rate (实时更新率):默认是 1000。请尝试改成 0(意思是让它全力跑,不限制速度)。

我设置0了但是好像只能设置为0.000001?

如果这一步做完,百分比能上升到 60% 以上,你的机械臂马上就会“硬”起来,控制器也会连接成功!

我操作后确实看到百分比最高接近百分之六十,控制器也连接成功了。

这两个数值根据实际仿真效果调节!不固定

有时候不调节只是会连接控制器速度变慢,也能正常连控制器

问题二:solidworks导出惯性参数有问题

解决方案;

全使用default_inertial宏

目前没观察到有什么不良影响

问题三:solidworks 导出urdf 时未勾选的结构也成为了stl文件的一部分

之前曾经对这个三机械臂结构用solidworks导出过一个urdf文件,之前就是一个固定底座连接三个六自由度机械臂,但是后来因为底座结构改变,并且底座下面连接了一个agv小车,所以我用新的solidworks文件生成了一个新的urdf文件,生成urdf文件的时候我以这个agv小车为base_Link,link_components选择的时候我也只勾选了小车的部分,然后我选择固定平台作为base_link的child_link,然后在固定平台下我定义了三个child_link分别为三个六自由度机械臂的第一关节,到此为止我导出了一个新的urdf文件,然后结合我上一次导出urdf文件进行整合,通过meshlab查看的时候发现这个文件几乎包含我的新solidworks文件的所有结构,不仅有小车结构,还有三个机械臂的完整结构甚至包含机械臂末端夹持的结构,但是明明在第二次生成urdf文件的时候没有勾选过机械臂除了第一关节之外的部分...

解决方案:

这是 SolidWorks URDF Exporter 插件的一个常见“坑”:
当你定义 base_link 时,如果你在 Feature Tree(左侧特征树)里选择的是顶层装配体,或者没有显式地排除其他部件,插件有时会默认“该链接包含了当前可见的所有模型”。即便你后续定义了 child links,第一步生成的 STL 往往是个“大杂烩”。

使用 MeshLab 进行“手术切除”

  1. 重置选择:

    • 如果现在画面上一片红,先点击菜单栏 Filters -> Selection -> None(或者按快捷键 Ctrl + Shift + D,不同版本可能不同),确保所有东西都变回灰白色,没有任何东西被选中。

  2. 使用连接选择工具:

    • 再次点击工具栏上的 "Select Connected Components in a region" 图标。

  3. 点选垃圾(机械臂):

    • 直接点击第一条多余的机械臂。

    • 观察:只有那条手臂变红了,小车还是白的。

    • 按 Delete 键(删除)。

  4. 重复操作:

    • 点击第二条机械臂 -> 变红 -> 删除。

    • 点击第三条机械臂 -> 变红 -> 删除。

  5. 清理残余(如果有):

    • 如果删掉手臂后,发现空中还悬浮着一些细碎的小零件或夹爪碎片,继续用这个工具点选并删除。

    • 如果有一些很难点中的小碎渣,可以使用 "Select Vertices in a rectangular region"(矩形框选工具),框住它们,然后删除。

问题四:机械臂无力下垂

解决方案:

(1)修改 URDF 中的力矩限制 (Effort)

我们需要把所有关节的最大力气改大。

请打开 src/three_arms_description/urdf/three_arms.urdf.xacro。

利用 查找/替换 功能:

  1. 查找:effort="100" (或者 SolidWorks 导出的其他默认值,可能是 30, 50 等)

  2. 替换为:effort="10000" (直接给个大数,仿真里不用担心电机烧坏)

  3. 同时建议:把 velocity="1" 改成 velocity="5" 或 10,不然机器人动起来像树懒。

貌似是solidworks生成的urdf里面就是没有这一部分内容的?所以要自己补一下。

因为目前在仿真阶段所以我设置数值很大

<limit effort="10000" velocity="10"/>

这个改完机械臂就不下垂了

(2)在 YAML 文件中添加 PID 增益 (Gains)

我们需要告诉控制器:“请用很大的力气去保持位置”。

请修改 src/three_arms_controller/config/three_arms_controllers.yaml。

我们需要在每个手臂控制器的 ros__parameters 下面,添加一个 gains 模块。

这个改完才可以用指令控制机械臂移动到相应位置

ros2 topic pub --once /armA_controller/joint_trajectory trajectory_msgs/msg/JointTrajectory "{joint_names: [armA_joint1, armA_joint2, armA_joint3, armA_joint4, armA_joint5, armA_joint6], points: [{positions: [0.5, 0.5, 0.5, 0.5, 0.5, 0.5], time_from_start: {sec: 2, nanosec: 0}}]}"

可以source之后用这个检查机械臂能不能按照预期动作

注意区分:effort模式和position模式(controllers.yaml文件里修改)

情况 A:Effort 模式👉 必须要有 PID

  • 原理:

    • Effort 模式 意味着 ROS 端的控制器(JointTrajectoryController)负责计算“为了到达目标位置,电机需要输出多大的力矩”。

    • 计算公式:力矩=P×(目标位置−实际位置)+D×(目标速度−实际速度)+…

  • 如果你在 YAML 里删掉了 gains (PID):

    • 控制器找不到 P, I, D 参数,默认就会把它们当成 0。

    • 计算结果:力矩=0×误差=0。

    • 后果:控制器虽然在运行,但它发给电机的指令永远是 0 牛顿米。你的机械臂会瞬间失去力量,再次像面条一样瘫软倒地。

结论:只要你的 command_interfaces 写的是 effort,YAML 里的 PID 就是机器人的“灵魂”,绝对不能删。

情况 B:Position 模式(我目前用的模式)👉 YAML 里非必须,但 URDF 里必须有刚度

  • 原理:

    • Position 模式 意味着 ROS 控制器只负责发号施令:“去 30 度!”。

    • 它不负责计算力矩,直接把“30度”这个数字扔给 Gazebo。

    • Gazebo 内部的物理引擎 负责施加力气把关节以此固定住。

  • 为什么 YAML 里的 PID 没用?

    • 因为在 Position 模式下,JointTrajectoryController 只是做轨迹插值,不进行力矩闭环运算,所以它常常忽略 YAML 里的 gains。

  • 为什么之前会下垂?

    • 因为 Gazebo 内部默认的“虚拟弹簧”太软了(或者默认刚度太低)。

    • 如果要解决 Position 模式的下垂,你通常需要去 URDF 文件的 <gazebo> 标签里写底层的 PID 参数(而不是在 YAML 里)。

问题五:关于moveit文件的自动配置

解决方案:

(1)准备与启动

复杂结构不适合手写srdf文件,应该用MoveIt Setup Assistant解决,先安装

sudo apt install ros-humble-moveit-setup-assistant

对应工作空间下source之后

ros2 launch moveit_setup_assistant setup_assistant.launch.py

(2)加载模型

点击左侧 Start。点击 Create New MoveIt Configuration Package。

Browse:找到src/three_arms_description/urdf/three_arms.urdf.xacro。

点击 Load Files。如果加载成功,右侧会出现机器人模型。

(3)生成碰撞矩阵 (Self-Collisions) —— 关键

这一步能大幅提高运行效率。

  1. 点击左侧 Self-Collisions。

  2. Sampling Density (采样密度) 保持默认即可。

  3. 点击 Generate Collision Matrix。会看到进度条在跑,助手正在计算几万次随机碰撞,来确定哪些关节天生就是挨着的。

两个名词

Adjacent Links (相邻连杆)

  • 定义:在 URDF 结构树中,直接通过关节(Joint)连接在一起的两个连杆(父 Link 和子 Link)。例如 armA_link1 和 armA_link2。

  • 状态:因为它们由关节物理连接,所以在数学模型上它们永远是接触的。

  • 处理方式:MoveIt 必须禁用这两者之间的碰撞检测。如果不禁用,MoveIt 会认为机器人一直处于“碰撞”状态,从而导致你永远无法规划任何路径。

  • 可靠性:100% 可靠。这是由 URDF 拓扑结构决定的,助手绝不会搞错。

Never in Collision (永不碰撞)

  • 定义:这两个连杆虽然没有直接相连,但在机器人的物理运动范围内,无论如何扭动,它们都碰不到对方。

  • 例子:

    • armA_link1(Arm A 的根部)和 armB_link1(Arm B 的根部)。因为它们固定在底座的不同位置,距离很远,动不了,所以永远碰不到。

    • 有时候 link1 和 link3 也可能碰不到,因为关节角度有限制。

  • 处理方式:MoveIt 建议禁用这两者之间的检测。

  • 目的:为了省电、省时间。如果不禁用,每次规划路径时,计算机都要傻傻地算一遍 Arm A 的根部有没有撞到 Arm B 的根部,纯属浪费算力。

助手生成矩阵是否百分百可靠?

自动生成的矩阵原理上是随机采样 (Random Sampling)。
它会在后台把你的机器人随机摆成几万个姿势(比如 10,000 次),然后检查每一对连杆是否发生了碰撞。如果 10,000 次随机姿势里,Link A 和 Link B 一次都没碰到过,助手就会大胆地推断:“这俩肯定永远碰不到,标记为 Never in Collision!”

对于复杂结构最好自己再检查一遍,尤其是跨手臂之间的碰撞关系。

检查步骤:

  1. 在 Setup Assistant 界面中:
    点击 Self-Collisions 标签页,生成矩阵后,你会看到一个列表。

  2. 重点关注“跨手臂”的对子:

    • 比如:armA_linkX 和 armB_linkY。

    • 或者:armA_linkX 和 armC_linkY。

  3. 如何判断是否需要修改?
    请运用你的物理直觉: 

    • 问自己:Arm A 的末端如果不小心,能打到 Arm B 的这一节吗?

    • 如果答案是“能打到”:而在列表里这一行被勾选了(即被禁用了检测),这就是错误的!

    • 修改方法:点击复选框,取消勾选(Uncheck)。

      • 选中(Disable) = 不检测碰撞(省性能,有风险)。

      • 未选中(Enable) = 开启检测(安全,费性能)。

    • 原则:宁可错杀,不可放过。 如果你不确定两个部件能不能碰到,那就不要禁用检测(保持未选中状态)。虽然计算慢一点,但至少安全。

特别需要保留检测(不要勾选)的关系:

  • Arm A 所有 Link <-> Arm B 所有 Link (除了根部那些离得十万八千里的)。

  • Arm A 所有 Link <-> Arm C 所有 Link。

  • 机械臂末端 <-> AGV 底座 / Platform (防止手臂打到自己的底座)。

检查了一下只有一个地方要注意:

助手识别到platform_base_link和armB_link2是collision by default的关系,意思是当你的机器人处于初始姿态(所有关节角度为 0)时,这两个零件的网格(Mesh)就已经穿模(互相重叠)了。

要勾选吗? 必须勾选(Disable)。如果不勾选,MoveIt 会认为机器人一开机就已经“撞车”了,从而拒绝规划任何路径。

出现这个红色警告的原因通常有两个:

结构紧凑:你的机械臂设计非常紧凑,Link 2 在 0 度位置时,物理距离离底座平台太近,导致两个零件的 Collision 模型(哪怕是低模)发生了轻微的重叠。

零位姿态问题:SolidWorks 导出时的“零位”可能是一个折叠姿态,在这个姿态下,机械臂是“缩”在底座里的。

虽然勾选可以解决报错,但你最好在 RViz 里看一眼:

  • 在 Setup Assistant 右侧的 3D 视图里,把关节都设为 0。

  • 看一看 armB_link2 是不是严重插入了 platform_base_link 内部?

    • 如果是轻微蹭到:直接勾选禁用,没问题。(是这种情况)

    • 如果是严重穿模(比如 Link 2 一半都在底座里):说明你的 URDF 原点(Origin) 或者 SolidWorks 装配关系 可能有错。这种情况下,虽然勾选能跑,但仿真效果是不真实的。

如果你已经生成了包,关闭了助手,后来发现不仅需要检测,或者想优化性能禁用某些检测,你可以直接修改文件,不用重跑助手。

文件位置:
src/three_arms_moveit/config/three_arms_description.srdf

修改方法:
打开 .srdf 文件,你会看到底部的 <disable_collisions> 标签:

<disable_collisions link1="armA_link1" link2="armA_link2" reason="Adjacent" />
<disable_collisions link1="armA_link1" link2="armB_link1" reason="Never" />
  • 如果你想开启碰撞检测:直接删除对应的那一行代码。

  • 如果你想禁用碰撞检测:手动添加一行代码。

(4)定义虚拟关节

  1. 点击左侧 Virtual Joints。

  2. Virtual Joint Name: 输入 virtual_joint。

  3. Child Link: 选择 world (如果你上一轮加了 world link) 或者 agv_base_link(用的这个)。

  4. Parent Frame Name: 输入 world。

  5. Joint Type: 选择 fixed (因为你的底座暂时不动)。

  6. 点击 Save。

(5)定义规划组——最核心步骤

建立三个独立的规划组

虽然你的机器人是一个整体,但在 MoveIt 中,我们通常把**每一条可以独立运动的运动链(Kinematic Chain)**定义为一个单独的组。这样你既可以单独控制 Arm A,也可以以后再建一个“大组”把它们合起来。

在 MoveIt Setup Assistant 的 Planning Groups 界面点击下方的 Add Group 按钮:

1. Basic Settings (基本设置)

  • Group Name (组名): 输入 arm_a

    • 这是你在代码里调用它的 ID,建议用小写下划线。

  • Kinematic Solver (运动学解算器): 必须选择!

    • 点击下拉菜单,选择 kdl_kinematics_plugin/KDLKinematicsPlugin。

    • 解释:默认是 None,必须手动选。KDL 是 ROS 默认的数值解算器,支持任意关节数量的机械臂,虽然速度不是最快(相比 IKFast),但兼容性最好,配置最简单,非常适合初次配置。

  • Kin. Search Resolution (搜索分辨率): 保持默认 0.005。

  • Kin. Search Timeout (超时时间): 保持默认 0.005。

  • Kin. Solver Attempts (尝试次数): 保持默认 3。

  • Group Default Planner (默认规划器): 保持 None 即可。

    • 解释:MoveIt 默认使用 OMPL (Open Motion Planning Library) 作为规划后端。如果你选 None,它会自动加载 OMPL 的默认配置(通常是 RRTConnect)。这完全没问题,以后可以在 yaml 文件里改。

2. Add Kinematic Chain (添加运动链) —— 最关键的一步

既然你的结构是 Platform 连接三个手臂,我们需要定义这条链的**“起点”和“终点”**。MoveIt 会自动把中间的所有关节和连杆包含进来。

  • 点击右侧的蓝色按钮 Add Kin. Chain。

  • Base Link (基座连杆):

    • 点击 Choose Selected 按钮,在树状列表里选择。

    • 建议选择:platform_base_link。

    • 原因:这是三条手臂共同的“根”。虽然选 agv_base_link 也可以,但 platform 离手臂更近,作为运动学的参考系更直观。

  • Tip Link (末端连杆):

    • 点击 Choose Selected。

    • 选择:armA_link6 (或者你定义的 Arm A 的最后一个 Link)。

  • 点击 Save。

现在 arm_a 已经建好了,请重复上面的步骤建立另外两个:

配置 Arm B

  1. Add Group。

  2. Name: arm_b。

  3. Solver: kdl_kinematics_plugin/KDLKinematicsPlugin。

  4. Add Kin. Chain:

    • Base Link: platform_base_link。

    • Tip Link: armB_link6。

  5. Save。

配置 Arm C

  1. Add Group。

  2. Name: arm_c。

  3. Solver: kdl_kinematics_plugin/KDLKinematicsPlugin。

  4. Add Kin. Chain:

    • Base Link: platform_base_link。

    • Tip Link: armC_link6。

  5. Save。

Q: 我的 AGV 小车需要建一个组吗?

  • A: 暂时不需要。 因为你的 AGV 是固定在 world 上的(通过 fixed joint),它是不可移动的基座。如果你以后给 AGV 加了轮子控制(Diff Drive),那时候再单独处理。现在只关注机械臂即可。

Q: 为什么不选 armA_link1 作为 Base Link?

  • A: 也可以选。但是通常建议 Base Link 选择不动的那个父物体(Platform)。这样规划的时候,MoveIt 知道 Link 1 是相对于 Platform 转动的。如果选 Link 1 做 Base,有时候坐标系变换会少一截(虽然 KDL 也能处理,但 Platform 更符合逻辑)。

Q: 为什么 Planner 选 None?

  • A: 这里的 Planner 指的是特定的算法配置。选 None 并不代表没有规划器,而是代表使用 MoveIt 默认的 OMPL 管道。这是最标准、最稳妥的做法。

做完这一步后,你的 Planning Groups 列表里应该有三行:

  1. arm_a | KDL | platform_base_link -> armA_link6

  2. arm_b | KDL | platform_base_link -> armB_link6

  3. arm_c | KDL | platform_base_link -> armC_link6

(6)预设姿态 (Robot Poses) —— 可选(这步我设置了)

定义一些常用的姿势,方便调试。

  1. 点击左侧 Robot Poses。

  2. 点击 Add Pose。

  3. Name: home_a。

  4. Planning Group: 选择 arm_a。

  5. 拖动滑块设置一个姿势(或者全 0)。

  6. 点击 Save。
    (建议为三条手臂都设置一个 home 姿势)

(7)配置 ROS 2 控制器 (ROS 2 Controllers) —— 连接仿真的关键

  1. 点击左侧 ROS 2 Controllers。

  2. 点击 Auto Add FollowJointTrajectoryControllers (自动添加)。助手通常会自动读取你的 URDF 结构,尝试帮你生成。

  3. 检查生成结果:应该看到类似 armA_controller, armB_controller, armC_controller 的项。

  4. 如果自动添加失败或不全,请手动编辑 (点击 Edit Selected):

    Name: 必须填 armA_controller (必须和你 controllers.yaml 里的名字一模一样!)。Type: 选择 FollowJointTrajectory。Action Namespace: 输入 follow_joint_trajectory (这非常重要,如果不填或填错,MoveIt 发不出指令)。Joints: 在右侧列表里,选中属于 Arm A 的所有关节(对 Arm B 和 C 重复此检查)

我看到生成arm_a_controller之类,类别都是joint_trajectory_controller/JointTrajectoryController

改名字的时候出现报错invalid controller name。原因可能是:格式限制:某些版本的 Setup Assistant 对名称检查非常严格(可能不喜欢大写字母,虽然 ROS 2 支持)。类型不匹配:自动生成的 Type 填的是 joint_trajectory_controller/JointTrajectoryController,这其实是控制器的插件类型,而不是 MoveIt 需要的接口类型(MoveIt 需要的是 FollowJointTrajectory)。

关于接口,我的MoveIt Setup Assistant 版本较新,所以接口类型选择joint_trajectory_controller/JointTrajectoryController就可以

接下来操作:

第一步:在助手里“骗”过它 (填小写)

请按照这个配置添加控制器(以 Arm A 为例):

  • Controller Name: arma_controller

    • (注意:这里故意填全小写,为了让助手不报错,能顺利生成文件)

  • Type: joint_trajectory_controller/JointTrajectoryController

    • (选你看到的那个长名字)

  • Action Namespace: follow_joint_trajectory

    • (这个必须填对,全小写)

我的界面没有这一项,这一项在moveit controllers里

  • Joints: 选中 Arm A 的所有关节。

(请同样用小写 armb_controller 和 armc_controller 把另外两个加进去)

第二步:完成后续步骤并生成包

  1. 配置 MoveIt Controllers(自动添加即可,它会映射刚才的小写名字)。

  2. 填写作者信息。

  3. 点击 Generate Package 生成代码到 src/three_arms_moveit。

  4. 关闭 Setup Assistant。


第三步:手动修正名字 (连接的关键!)

文件生成后,我们需要去修改生成出来的配置文件,把名字改回大写,这样 MoveIt 才能找到 Gazebo 里的真身。

请用 VS Code 打开新生成的 src/three_arms_moveit/config/ros2_controllers.yaml。

你会看到类似这样的内容:

# MoveIt 生成的(错误的,因为是小写)
controller_manager:
  ros__parameters:
    update_rate: 100  # 或者其他频率

    arma_controller:  # <--- 这里是小写,要改!
      type: joint_trajectory_controller/JointTrajectoryController

    armb_controller:  # <--- 要改!
      type: joint_trajectory_controller/JointTrajectoryController
    
    # ...

arma_controller:      # <--- 这里也是小写,要改!
  ros__parameters:
    # ...

相应的地方改一下

(8)MoveIt 控制器 (MoveIt Controllers)

  1. 点击左侧 MoveIt Controllers。

  2. 点击 Auto Add FollowJointTrajectoryControllers。

    • 这会自动把刚才定义的 ROS 2 控制器映射到 MoveIt 内部。

    • 确保 arm_a 规划组 对应 armA_controller。

我这一步的界面长的和上一步差不多。

(9)作者信息 (Author Information)

  1. 点击左侧 Author Information。

  2. 随便填个名字和邮箱 (比如 xhn, xhn@todo.com),不填不让生成。

(10)生成文件 (Configuration Files)

  1. 点击左侧 Configuration Files。

  2. Configuration Package Save Path:

    • 点击 Browse。

    • 进入你的 src 文件夹。

    • 新建一个文件夹,命名为 three_arms_moveit。

    • 点击 Open。

  3. 点击 Generate Package。

  4. 等待生成完毕,点击 OK,退出助手。

选择文件的时候所有文件都保持勾选

(11)添加一个launch文件联合仿真

手动创建文件src/three_arms_moveit/launch/moveit_gazebo.launch.py

内容如下:(后面有更新)

import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
    # 获取包路径
    moveit_config_pkg = get_package_share_directory('three_arms_moveit')
    description_pkg = get_package_share_directory('three_arms_description')

    # 声明仿真时间参数 (关键!让 MoveIt 和 RViz 与 Gazebo 同步)
    use_sim_time = LaunchConfiguration('use_sim_time', default='true')

    # 1. 启动 Gazebo 环境 (包含模型加载 + 控制器启动)
    # 注意:这里假设你的 gazebo.launch.py 里已经包含了 controller.launch.py 的调用
    # 如果没有,你需要在这里额外 include controller.launch.py
    gazebo_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(description_pkg, 'launch', 'gazebo.launch.py')
        )
    )

    # 2. 启动 MoveIt 的核心节点 (move_group)
    # MoveIt Setup Assistant 自动生成的 launch 文件通常叫 move_group.launch.py
    move_group_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(moveit_config_pkg, 'launch', 'move_group.launch.py')
        ),
        launch_arguments={'use_sim_time': use_sim_time}.items()
    )

    # 3. 启动 RViz (可视化界面)
    # MoveIt 自动生成的 RViz 启动文件
    rviz_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(moveit_config_pkg, 'launch', 'moveit_rviz.launch.py')
        ),
        launch_arguments={'use_sim_time': use_sim_time}.items()
    )

    return LaunchDescription([
        gazebo_launch,
        move_group_launch,
        rviz_launch
    ])

build和source之后运行ros2 launch three_arms_moveit moveit_gazebo.launch.py

发现报错

parameter 'robot_description_planning.joint_limits.armA_joint1.max_velocity' has invalid type: expected [double] got [integer]

MoveIt配置文件中关节限制参数类型错误

同时

Could not parse the SRDF XML File. Error=XML_ERROR_EMPTY_DOCUMENT
Unable to parse SRDF
Robot model not loaded

说srdf文件为空,无法读取,但是检查发现这个文件就存在于对应路径下。

解决第一个问题src/three_arms_moveit/config/joint_limits.yaml这个文件是自动生成的,手动把整数都改成小数再运行不报错,成功。

问题六:gazebo仿真时间没对齐

点击rviz界面的plan可以看到机械臂动作了,但是点击execute看不到gazebo同步动作。终端看到下面报错:

[moveit_ros.current_state_monitor]: Didn't receive robot state (joint angles) with recent timestamp within 1.000000 seconds.
[moveit_ros.trajectory_execution_manager]: Failed to validate trajectory: couldn't receive full current joint state within 1s

MoveIt 发现收到的关节状态数据(从 /joint_states 话题来的)的时间戳,比当前系统时间慢了太多(或者因为仿真时间同步问题,MoveIt 觉得那是“过期的”旧数据),所以为了安全,它拒绝执行。

这在 Gazebo 仿真中非常常见,因为仿真时间通常比真实时间慢,或者两者没对齐。

解决方案:

一开始添加了一些原版代码没有的东西,比如controller_manager_node_name插件节点、手动指定 use_sim_time、IMU 插件等。但是始终没有解决问题。

后来仍然使用原版代码思想:

three_arms_gazebo.xacro这个代码保持原版结构不动

gazebo.launch.py虽然原版是分开启动的。但我的代码进行了一些修改:

(1)新增:自动包含并启动 three_arms_controller/launch/controller.launch.py。这样你只需要运行这一个文件,Gazebo、机器人模型、控制器就会全部跑起来

(2)参数传递:在启动控制器时,自动传入 is_sim:=True,确保控制器知道现在是仿真环境

src/three_arms_controller/config/three_arms_controllers.yaml也是和原版保持一致

然后运行发现问题还是没有被解决。

最终决定强制重启Broadcaster 

当 Gazebo 启动时,有时候硬件接口(Hardware Interface)还没完全准备好,或者仿真时间(Clock)**还没开始走,joint_state_broadcaster 就已经急匆匆地启动了。结果它读不到数据,就进入了“假死”状态。

我们要在仿真正在运行的时候,手动把它关掉再重开。

仿真运行且已点击播放后,另开一个终端:

(1)检查目前话题:

ros2 control list_controllers

可以看到

armC_controller         joint_trajectory_controller/JointTrajectoryController  active
armB_controller         joint_trajectory_controller/JointTrajectoryController  active
joint_state_broadcaster joint_state_broadcaster/JointStateBroadcaster          active
armA_controller         joint_trajectory_controller/JointTrajectoryController  active

(2)依次输入:停用——卸载——重新加载并激活

ros2 control set_controller_state joint_state_broadcaster inactive

看到:Successfully deactivated joint_state_broadcaster

ros2 control unload_controller joint_state_broadcaster

看到:

[INFO] [1764229589.977113969] [_ros2cli_1675422]: waiting for service /controller_manager/unload_controller to become available...
Successfully unloaded controller joint_state_broadcaster

ros2 control load_controller joint_state_broadcaster --set-state active

看到:Successfully loaded controller joint_state_broadcaster into state active

(3)检查话题是否正常发布:

ros2 topic echo /joint_states --once

看到输出:

header:
  stamp:
    sec: 9
    nanosec: 680000000
  frame_id: base_link
name:
- armA_joint1
- armA_joint3
- armA_joint4
- armA_joint5
- armC_joint6
- armC_joint5
- armC_joint4
- armC_joint1
- armA_joint2
- armB_joint2
- armB_joint6
- armC_joint3
- armB_joint5
- armC_joint2
- armA_joint6
- armB_joint4
- armB_joint3
- armB_joint1
position:
- -6.7657326955668565e-06
- -4.051983736426617e-06
- -1.2807300280586095e-05
- -1.2537599979046168e-05
- 7.172104635510067e-07
- 1.0104492905360621e-05
- 9.56276739529717e-06
- -9.632513181245359e-06
- 7.975296515841719e-06
- 0.009614815019778164
- -8.025437121469618e-07
- 6.249623959058608e-06
- -2.620704939434351e-06
- -6.7408790610594205e-06
- 1.2940628347339058e-05
- 1.0861867749836579e-07
- -0.01395508984821419
- -0.007131254300364624
velocity:
- -1.734723475976807e-18
- 0.0
- -3.469446951953614e-18
- 1.734723475976807e-18
- -7.589415207398531e-19
- -1.734723475976807e-18
- 0.0
- 0.0
- 0.0
- 0.00098464360475543
- -3.0938793194046355e-15
- -8.673617379884035e-19
- 4.331170838645093e-15
- 0.0
- 0.0
- 5.9380417249954576e-12
- -0.001387882402360572
- -0.0007445472029915141
effort:
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
- .nan
---

这次看到header.stamp.sec > 0即时间戳不再是 0 了

两个小问题:

(1)这此,点击plan之后rviz里面的机械臂按照预期动作了,然后点击execute之后gazebo界面迟迟不动作,execute按钮也一直处于被按下的灰色。然后我看到gazebo界面右下角的百分比是百分之五以下,手动调节了右侧栏中的physics中的step size使得右下角百分比接近百分之百,gazebo里机械臂就相应动作了。

(2)切换到arm_b之后

第一次execute没有反应,又重复plan+execute之后gazebo里机械臂有反应了

第一次日志报错:
Invalid Trajectory: start point deviates from current robot state more than 0.01
joint 'armB_joint1': expected: -0.0124765, current: -0.124776

原因如下:

初始抖动:Gazebo 刚启动时,机械臂可能会因为重力或物理碰撞稍微“下沉”一点点(从完美的 0.0 变成 -0.12)。

状态不同步:当你点击 Plan 时,MoveIt 是基于那一瞬间的状态规划的。但在你点击 Execute 之前,机械臂可能又轻微晃动了一点点。

结果:MoveIt 发现规划的起点和当前实际位置差了 0.1弧度,为了安全,它拒绝执行。

解决方法:

重新 Plan 一次:这会基于最新的稳定位置重新规划,然后 Execute 就能成功了。

或者放宽容忍度:在 moveit_controllers.yaml 里把 allowed_start_tolerance 设大一点(比如 0.05 或 0.1)。

避免每次都手动输入这三条指令所以直接写入src/three_arms_moveit/launch/moveit_gazebo.launch.py

在 Launch 文件启动后,等待一段足够长的时间(比如 20 秒,等 Gazebo 彻底稳定),然后自动在后台执行你刚才那三条命令。

import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, ExecuteProcess, TimerAction # <--- 引入了 ExecuteProcess 和 TimerAction
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
from launch_ros.actions import Node
from moveit_configs_utils import MoveItConfigsBuilder

def generate_launch_description():
    # ========================================================================
    # 1. 加载 MoveIt 配置
    # ========================================================================
    moveit_config = MoveItConfigsBuilder("three_arms_description", package_name="three_arms_moveit").to_moveit_configs()

    # ========================================================================
    # 2. 启动 Gazebo 环境
    # ========================================================================
    gazebo_launch = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(get_package_share_directory('three_arms_description'), 'launch', 'gazebo.launch.py')
        )
    )

    # ========================================================================
    # 3. 手动定义 Move Group 节点
    # ========================================================================
    run_move_group_node = Node(
        package="moveit_ros_move_group",
        executable="move_group",
        output="screen",
        parameters=[
            moveit_config.to_dict(),
            {'use_sim_time': True},
            {'trajectory_execution.allowed_execution_duration_scaling': 2.0},
            {'trajectory_execution.allowed_goal_duration_margin': 0.5},
            {'publish_robot_description_semantic': True}
        ],
    )

    # ========================================================================
    # 4. 手动定义 RViz 节点
    # ========================================================================
    rviz_config_file = os.path.join(
        get_package_share_directory("three_arms_moveit"), "config", "moveit.rviz"
    )

    rviz_node = Node(
        package="rviz2",
        executable="rviz2",
        name="rviz2",
        output="log",
        arguments=["-d", rviz_config_file],
        parameters=[
            moveit_config.robot_description,
            moveit_config.robot_description_semantic,
            moveit_config.planning_pipelines,
            moveit_config.robot_description_kinematics,
            {'use_sim_time': True}
        ],
    )

    # ========================================================================
    # 5. 静态 TF 发布
    # ========================================================================
    static_tf = Node(
        package="tf2_ros",
        executable="static_transform_publisher",
        name="static_transform_publisher",
        output="log",
        arguments=["0.0", "0.0", "0.0", "0.0", "0.0", "0.0", "world", "agv_base_link"],
        parameters=[{'use_sim_time': True}]
    )

    # ========================================================================
    # 6. [新增] 自动重启 joint_state_broadcaster (你的手动操作自动化版)
    # ========================================================================
    # 逻辑:等待 25 秒 (给足时间让 Gazebo 启动并变卡),然后自动执行“重启三连”
    auto_fix_joint_state = TimerAction(
        period=25.0,  # 如果你的电脑启动 Gazebo 很慢,可以把这个时间改得更长,比如 30.0 或 40.0
        actions=[
            ExecuteProcess(
                cmd=[
                    'bash', '-c',
                    # 这就是你刚才手动输的那三行命令,用分号隔开
                    'echo ">>> 开始自动修复 joint_state_broadcaster..."; ' +
                    'ros2 control set_controller_state joint_state_broadcaster inactive; ' +
                    'ros2 control unload_controller joint_state_broadcaster; ' +
                    'ros2 control load_controller joint_state_broadcaster --set-state active; ' +
                    'echo ">>> 修复完成!"'
                ],
                output='screen',
                name='joint_state_fixer'
            )
        ]
    )

    return LaunchDescription([
        gazebo_launch,
        run_move_group_node,
        rviz_node,
        static_tf,
        auto_fix_joint_state # <--- 别忘了把这个加进去
    ])

问题七:gazebo里physics里的step size不调节就看不到gazebo界面机械臂同步动作

其实是如果不调节,机械臂其实也在动,但它处于“慢动作回放”模式,慢到以为它静止了。

现象解析:

  • 默认状态:你的 Gazebo 右下角显示 RTF (Real Time Factor) 是 0.55% 左右(或者更低)。这意味着现实世界过 100秒,仿真世界才过 0.55秒。

  • 你的错觉:你点击 Execute 后,MoveIt 确实把指令发过去了,电机也开始转了。但是因为时间流逝太慢,机械臂可能每分钟才转动 0.01 度。肉眼看起来就像“不动”。

  • 手动调节后:你把 Step Size 调大,或者把 Update Rate 调成 0,RTF 飙升到了 60% 甚至 100%。这时候仿真速度接近现实速度,你自然就看到机械臂“正常”运动了。

为什么 RTF 这么低?

尽管我们做了这么多优化(低模、惯性重置),但对于你的 18 轴机器人 加上 三条手臂互相连接的复杂结构,默认的物理步长(Step Size = 0.001s)对你的 CPU 来说依然太吃力了。

  • 原因:物理引擎需要在每个 0.001 秒内,解算 18 个关节的动力学方程 + 碰撞检测。如果你的 CPU 算这一步需要 0.1 秒,那么仿真速度就只能是现实速度的 1% (0.001 / 0.1)。

终极解决方案:把你的“手动调节”固化到代码里(我没改)

可以修改 Gazebo 启动参数,打开 src/three_arms_description/launch/gazebo.launch.py

找到启动 Gazebo 的部分,把 -r (run) 后面加上物理参数的配置。
Ignition Gazebo 允许通过 --physics-engine 参数或者直接加载包含物理配置的 SDF 文件。但最简单的办法是指定更新频率。

或者,更推荐的方法是创建一个 empty.sdf 配置文件,放在你的包里。

问题八:如何

  1. 同时规划三条手臂(建立一个 all_arms 规划组)。

  2. 或者编写 Python 脚本,让三条手臂自动执行复杂的协作任务。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐