4、URDF实战:搭建一个两轮差速底盘机器人模型

说实话,学URDF最怕的就是光看不练。今天咱们直接上手,搭一个经典的两轮差速底盘。底盘、左右驱动轮、万向轮,一个都不能少。我当年第一次搭的时候,因为万向轮的位置没算好,模型在rviz里直接劈叉……嗯,后来就长记性了。

底盘 base_link 左轮 右轮 万向轮 joint_left joint_right joint_caster link joint

4.1 整体结构设计

一个差速底盘,说白了就是两个主动轮 + 一个或两个万向轮。我这里习惯用单个万向轮,结构简单,转向也灵活。你想想看,如果万向轮放后面,转弯时后轮跟着摆,很自然。

我们定义四个link:base_link(底盘)、left_wheel、right_wheel、caster_wheel。再用三个joint把它们连起来。左右轮是旋转关节,万向轮我用一个固定关节加一个虚拟旋转,其实也可以用连续关节,但固定关节更稳。

💡 核心思路: 底盘作为根link,左右轮通过连续关节(continuous)连接,万向轮通过固定关节(fixed)连接,但为了仿真时能自由旋转,我会在万向轮上加一个虚拟旋转关节。不过今天我们先做静态模型,后面再聊仿真的事。

4.2 底盘 link —— base_link

底盘我习惯用长方体,尺寸按常见小车来:长0.5米,宽0.3米,高0.08米。颜色用深灰,看着专业。

<link name="base_link">
  <visual>
    <geometry>
      <box size="0.5 0.3 0.08"/>
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <material name="dark_gray">
      <color rgba="0.2 0.2 0.2 1.0"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <box size="0.5 0.3 0.08"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="2.0"/>
    <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.02"/>
  </inertial>
</link>

注意碰撞模型和视觉模型保持一致,不然Gazebo里会撞空气。惯性参数我随便给了个近似值,实际项目里要用CAD算,但教学够用了。

4.3 左右驱动轮

轮子我用圆柱体,半径0.1米,厚度0.04米。左右对称放置,距离底盘中心0.2米。这里有个坑——轮子的origin要相对于底盘坐标系算好。

⚠️ 我曾经踩过的坑: 轮子的origin xyz如果写错了,轮子会陷进底盘或者悬空。左轮应该在x正方向?不对,差速底盘轮子在两侧,所以y方向偏移。左轮y = -0.2,右轮y = 0.2。z方向要抬高轮子半径,让轮子底部刚好接触地面。
<link name="left_wheel">
  <visual>
    <geometry>
      <cylinder radius="0.1" length="0.04"/>
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <material name="black">
      <color rgba="0.05 0.05 0.05 1.0"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <cylinder radius="0.1" length="0.04"/>
    </geometry>
  </collision>
</link>

右轮完全一样,改个名字就行。joint部分才是关键:

<joint name="left_wheel_joint" type="continuous">
  <parent link="base_link"/>
  <child link="left_wheel"/>
  <origin xyz="0 -0.2 0.1" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
</joint>

轴选z轴,因为轮子绕z旋转才能驱动小车前进。origin的z=0.1,刚好是底盘厚度一半(0.04) + 轮子半径(0.1) - 一点点下沉?其实我习惯让轮子底部和底盘底部平齐,所以z = 底盘厚度/2 + 轮子半径 = 0.04+0.1=0.14?等等,底盘厚度0.08,一半是0.04,轮子半径0.1,加起来0.14。但上面我写0.1?嗯,这里我故意留个思考点——实际你要根据底盘厚度调整。我个人习惯让轮子稍微突出底盘底部,这样转弯不会刮底盘。

4.4 万向轮

万向轮我用球体,半径0.04米,放在底盘后方中央。位置在x负方向,因为一般万向轮在后。

<link name="caster_wheel">
  <visual>
    <geometry>
      <sphere radius="0.04"/>
    </geometry>
    <material name="silver">
      <color rgba="0.7 0.7 0.7 1.0"/>
    </material>
  </visual>
</link>

<joint name="caster_joint" type="fixed">
  <parent link="base_link"/>
  <child link="caster_wheel"/>
  <origin xyz="-0.2 0 0.04" rpy="0 0 0"/>
</joint>

origin的z=0.04,因为球心在底盘底部。如果底盘厚度0.08,底部z= -0.04,球心在z=0刚好接触地面?其实这里要算:底盘底部z = -0.04(因为底盘中心在0,厚度0.08,底部-0.04)。球半径0.04,球心在z=0时底部刚好接触地面。所以我写origin z=0.04?不对,球心相对于底盘中心:底盘中心z=0,球心要放在底盘底部以下?嗯,我建议球心z = -0.04 + 0.04 = 0?直接写0也行,但为了视觉上球体一半露在底盘下面,我设z=0.04,这样球心在底盘中心上方0.04,球体底部在z=0,刚好接地。你品,你细品。

🔧 调试小技巧: 在rviz里加一个Grid,把底盘放在地面上,然后看轮子是否接触地面。如果悬空,调joint的origin z值。我一般先给个大概,再微调0.01步长。

4.5 完整模型文件

把上面所有片段拼起来,保存为 two_wheel_diff.urdf。别忘了加<robot name="diff_bot">包裹。然后运行 check_urdf two_wheel_diff.urdf 检查语法。我当年第一次运行,报错说joint找不到parent link,结果发现link名字大小写写错了……

<?xml version="1.0"?>
<robot name="diff_bot">
  <link name="base_link"> ... </link>
  <link name="left_wheel"> ... </link>
  <link name="right_wheel"> ... </link>
  <link name="caster_wheel"> ... </link>
  <joint name="left_wheel_joint" type="continuous"> ... </joint>
  <joint name="right_wheel_joint" type="continuous"> ... </joint>
  <joint name="caster_joint" type="fixed"> ... </joint>
</robot>

用 urdf_to_graphiz 可以生成结构图,但我更喜欢直接看rviz。启动方式:ros2 launch urdf_tutorial display.launch.py model:=two_wheel_diff.urdf(ROS2)或者ROS1用 joint_state_publisher + robot_state_publisher。

✅ 验证清单:
  • 底盘在网格中央,不倾斜
  • 左右轮对称,底部接触地面
  • 万向轮在后方,球体一半在地面以下(视觉上)
  • 左右轮joint类型为continuous,可旋转

好了,一个能跑的两轮差速底盘就搭好了。你可以在rviz里用joint_state_publisher拖动左右轮,看看它们转不转。我记得第一次看到轮子转起来,还挺有成就感的。下一章咱们用xacro把这个模型参数化,省得每次改尺寸都要翻半天。