4. 机器人模型URDF/SDF:编写差速驱动机器人URDF、添加激光雷达与IMU

好,我们正式开始动手写机器人模型了。这一章,说白了就是给机器人“画骨”和“装器官”。URDF 就是骨架,激光雷达和 IMU 就是眼睛和耳朵。我当年刚接触 ROS 时,觉得 URDF 就是个 XML 文件,随便写写就行。结果在仿真里机器人要么轮子悬空,要么雷达装反了方向……嗯,后来才明白,这里面的门道其实不少。

4.1 差速驱动底盘的核心结构

差速驱动,顾名思义,靠两个轮子的速度差来实现转向。结构上其实很简单:一个车身、两个驱动轮、一个或两个万向轮。我个人习惯把万向轮放在前后,这样机器人转弯时更稳。

先看一个典型的差速底盘参数:

部件 尺寸/参数 说明
车身 长 0.4m × 宽 0.3m × 高 0.15m 内部放电池和主控
驱动轮 半径 0.05m,宽度 0.03m 左右各一个
万向轮 半径 0.02m 前后各一个,或只放后面
轮距 0.25m 左右轮中心距离

为什么轮距这么重要?因为它直接影响你的转弯半径和里程计精度。我在项目里遇到过,轮距写错了 1 厘米,结果机器人原地转圈时,实际转了 360 度,里程计只报了 350 度。这种误差积累起来,导航根本没法用。

4.2 编写差速驱动机器人 URDF

URDF 的核心就两个东西:link 和 joint。link 是刚体,joint 是连接关系。我们一步步来。

4.2.1 车身 link

车身用 box 形状,颜色随便选,但建议用亮色,仿真里看得清楚。

<link name="base_footprint">
    <visual>
        <geometry>
            <box size="0.4 0.3 0.15"/>
        </geometry>
        <material name="blue">
            <color rgba="0 0 0.8 1"/>
        </material>
    </visual>
    <collision>
        <geometry>
            <box size="0.4 0.3 0.15"/>
        </geometry>
    </collision>
    <inertial>
        <mass value="2.0"/>
        <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
    </inertial>
</link>
小技巧: collision 和 visual 可以不一样。比如 visual 用复杂模型,collision 用简化 box,能大幅提升仿真性能。我早期做多机器人仿真时,就是因为 collision 太精细,导致仿真跑不动。

4.2.2 驱动轮 link 与 joint

轮子要能转,所以 joint 类型必须是 continuous。注意坐标轴方向:

<link name="left_wheel">
    <visual>
        <geometry>
            <cylinder radius="0.05" length="0.03"/>
        </geometry>
        <material name="black"/>
    </visual>
    <collision>
        <cylinder radius="0.05" length="0.03"/>
    </collision>
</link>

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

这里 axis xyz="0 1 0" 表示绕 Y 轴旋转。为什么?因为轮子前进方向是 X 轴,旋转轴自然就是 Y 轴。你想想看,如果写成绕 X 轴,轮子就变成侧着滚了——我见过有人犯过这个错,仿真里机器人直接“漂移”。

4.2.3 万向轮

万向轮用 fixed 关节就行,因为它不主动驱动。但注意,万向轮其实应该能自由旋转,不过 URDF 里模拟不了那么细,我们一般用球状轮子代替。

<link name="caster_wheel">
    <visual>
        <geometry>
            <sphere radius="0.02"/>
        </geometry>
    </visual>
</link>

<joint name="caster_joint" type="fixed">
    <parent link="base_footprint"/>
    <child link="caster_wheel"/>
    <origin xyz="0.15 0 -0.085"/>
</joint>

4.3 添加激光雷达

激光雷达一般装在车顶或车头。我个人习惯装在车顶中心,这样视野最开阔。但要注意,雷达不能和车身碰撞,所以高度要留够。

<link name="laser">
    <visual>
        <geometry>
            <cylinder radius="0.03" length="0.05"/>
        </geometry>
        <material name="red"/>
    </visual>
</link>

<joint name="laser_joint" type="fixed">
    <parent link="base_footprint"/>
    <child link="laser"/>
    <origin xyz="0 0 0.12" rpy="0 0 0"/>
</joint>
避坑指南: 我曾经把雷达装在车身侧面,结果导航时机器人总往一边偏。后来发现是雷达坐标系和基座坐标系没对齐。记住:雷达的 Z 轴要朝上,X 轴朝前。如果装歪了,Nav2 的 costmap 会生成错误。

4.4 添加 IMU

IMU 通常放在机器人重心附近,这样测量到的加速度和角速度最准确。URDF 里 IMU 其实不需要 visual,但为了调试方便,我还是会画一个小方块表示。

<link name="imu_link">
    <visual>
        <geometry>
            <box size="0.02 0.02 0.02"/>
        </geometry>
        <material name="green"/>
    </visual>
</link>

<joint name="imu_joint" type="fixed">
    <parent link="base_footprint"/>
    <child link="imu_link"/>
    <origin xyz="0 0 0.02" rpy="0 0 0"/>
</joint>

IMU 的坐标系约定:X 朝前,Y 朝左,Z 朝上。这和 ROS 的标准一致。如果你用其他方向的 IMU,记得在 launch 文件里做旋转矩阵变换。

4.5 整体结构图

下面这张图展示了我们刚才搭建的机器人模型结构。你可以看到各个部件之间的层级关系:

差速驱动机器人 URDF 结构图 base_footprint left_wheel continuous 关节 right_wheel continuous 关节 caster_wheel fixed 关节 laser fixed 关节,高度 0.12m imu_link

4.6 验证 URDF

写完之后,一定要验证。ROS 提供了 check_urdf 工具:

check_urdf my_robot.urdf

如果输出类似这样,就说明语法没问题:

robot name is: my_robot
---------- Successfully Parsed XML ---------------
root link: base_footprint has 4 child(ren)
    child(1):  left_wheel
    child(2):  right_wheel
    child(3):  caster_wheel
    child(4):  laser
    child(5):  imu_link
核心要点:
  • 每个 link 必须有 visual、collision、inertial 三部分
  • 驱动轮用 continuous 关节,万向轮用 fixed
  • 激光雷达和 IMU 的坐标系方向必须和 ROS 标准一致
  • 写完一定要用 check_urdf 验证,别偷懒

嗯,到这里,一个完整的差速驱动机器人 URDF 就写好了。你可以在 Rviz 里加载看看,如果轮子能转、雷达能扫出点云,那就说明模型没问题。下一章我们会把这个模型用起来,配合 Gazebo 做仿真。