4. 构建第一个机器人模型:两轮差速底盘

好,从这一章开始,我们真正动手写代码了。你要构建的,是机器人世界里最经典的结构——两轮差速底盘。说白了,就是像扫地机器人那样,靠两个独立驱动的轮子实现前进、后退和转弯。

我个人习惯,每做一个新机器人,都会先从 URDF 文件开始。为什么?因为它是 ROS 里描述机器人最底层、最标准的方式。你后面做仿真、做导航、做控制,全都得靠它。

4.1 理解两轮差速底盘的结构

先别急着写代码,咱们得搞清楚这个底盘长什么样。一个标准的差速底盘,包含这几个核心部件:

  • 底盘本体:承载所有东西的骨架,通常是个矩形或圆柱体
  • 左驱动轮:独立电机控制,负责左侧动力
  • 右驱动轮:独立电机控制,负责右侧动力
  • 万向轮(可选):起支撑作用,不提供驱动力

差速驱动的原理其实很简单:两个轮子转速相同,车就走直线;转速不同,车就转弯。我在项目中遇到过不少新手,一上来就把轮子位置搞反了,结果仿真里机器人原地打转——嗯,这种错误犯一次就记住了。

核心概念:URDF 里用 link 表示刚体部件(底盘、轮子),用 joint 表示部件之间的连接关系(轮子怎么装在底盘上)。

下面这张图,帮你理清本章的知识脉络:

两轮差速底盘 URDF 构建流程 底盘 link 定义 joint(连接关系) 左轮 link + joint 右轮 link + joint 万向轮 link + joint urdf_to_graphiz 可视化验证 每个轮子都需要一个 link(几何+惯性)和一个 joint(位置+类型)

4.2 定义底盘 link

底盘是整个机器人的根。在 URDF 里,我们通常把它作为 base_link。你想想看,所有其他部件都是直接或间接挂在这个 link 上的。

定义 link 需要做两件事:视觉表现(让机器人看起来像那么回事)和碰撞检测(让物理引擎知道它的边界)。

<!-- 底盘 link -->
<link name="base_link">
  <visual>
    <geometry>
      <box size="0.4 0.3 0.1"/>  <!-- 长0.4m,宽0.3m,高0.1m -->
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <material name="blue">
      <color rgba="0.2 0.4 0.8 1.0"/>
    </material>
  </visual>

  <collision>
    <geometry>
      <box size="0.4 0.3 0.1"/>
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
  </collision>

  <inertial>
    <mass value="2.0"/>  <!-- 2kg -->
    <inertia ixx="0.01" ixy="0" ixz="0"
              iyy="0.02" iyz="0"
              izz="0.01"/>
  </inertial>
</link>

小技巧:inertial 里的惯性矩阵,如果你懒得算,可以用 1/12 * m * (w² + h²) 这种公式估算。我刚开始做的时候直接抄了别人的数值,结果仿真里底盘飘得像纸片——后来老老实实算了一遍。

4.3 定义车轮 link

车轮和底盘不一样。底盘是方的,车轮是圆的。而且车轮需要旋转,所以它的 origin 要放在轮子中心。

<!-- 左轮 link -->
<link name="left_wheel_link">
  <visual>
    <geometry>
      <cylinder radius="0.05" length="0.04"/>  <!-- 半径5cm,厚度4cm -->
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <material name="black">
      <color rgba="0.1 0.1 0.1 1.0"/>
    </material>
  </visual>

  <collision>
    <geometry>
      <cylinder radius="0.05" length="0.04"/>
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
  </collision>

  <inertial>
    <mass value="0.2"/>
    <inertia ixx="0.0001" ixy="0" ixz="0"
              iyy="0.0001" iyz="0"
              izz="0.0002"/>
  </inertial>
</link>

右轮的定义完全一样,只是名字改成 right_wheel_link。你完全可以复制粘贴,但记得改名字——我曾经见过有人忘了改,结果两个轮子共用同一个 link,仿真直接报错。

4.4 定义车轮 joint

link 定义好了,怎么把它们装到底盘上?靠 joint。对于差速底盘,轮子需要绕 Y 轴旋转,所以用 continuous 类型的 joint——它没有角度限制,可以一直转。

<!-- 左轮 joint -->
<joint name="left_wheel_joint" type="continuous">
  <parent link="base_link"/>
  <child link="left_wheel_link"/>
  <origin xyz="-0.15 0.2 -0.05" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>  <!-- 绕Y轴旋转 -->
</joint>

<!-- 右轮 joint -->
<joint name="right_wheel_joint" type="continuous">
  <parent link="base_link"/>
  <child link="right_wheel_link"/>
  <origin xyz="-0.15 -0.2 -0.05" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>
</joint>

这里 origin 的 xyz 值怎么来的?我习惯把底盘中心放在 (0,0,0),轮子装在底盘两侧偏后的位置。你想想看:x=-0.15 表示轮子靠后,y=±0.2 表示左右对称,z=-0.05 表示轮子比底盘底面低一点——这样轮子才能接触地面。

注意:axis xyz="0 1 0" 表示绕 Y 轴旋转。如果你的轮子转起来方向不对,检查一下这个轴。我曾经在项目里把 Y 和 Z 搞混了,轮子在地上画圈而不是滚动——调试了半小时才发现。

4.5 完整的 URDF 文件

把上面这些拼起来,再加上一个万向轮(可选),就是一个完整的差速底盘 URDF:

<?xml version="1.0"?>
<robot name="my_diff_drive_robot">

  <!-- 底盘 -->
  <link name="base_link">
    <visual>
      <geometry><box size="0.4 0.3 0.1"/></geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="blue">
        <color rgba="0.2 0.4 0.8 1.0"/>
      </material>
    </visual>
    <collision>
      <geometry><box size="0.4 0.3 0.1"/></geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </collision>
    <inertial>
      <mass value="2.0"/>
      <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.02" iyz="0" izz="0.01"/>
    </inertial>
  </link>

  <!-- 左轮 -->
  <link name="left_wheel_link">
    <visual>
      <geometry><cylinder radius="0.05" length="0.04"/></geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="black">
        <color rgba="0.1 0.1 0.1 1.0"/>
      </material>
    </visual>
    <collision>
      <geometry><cylinder radius="0.05" length="0.04"/></geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
    </collision>
    <inertial>
      <mass value="0.2"/>
      <inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0002"/>
    </inertial>
  </link>

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

  <!-- 右轮(结构同左轮,位置对称) -->
  <link name="right_wheel_link">
    <!-- 内容同 left_wheel_link -->
  </link>

  <joint name="right_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel_link"/>
    <origin xyz="-0.15 -0.2 -0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>

  <!-- 万向轮(可选,用于支撑) -->
  <link name="caster_wheel_link">
    <visual>
      <geometry><sphere radius="0.03"/></geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="gray">
        <color rgba="0.5 0.5 0.5 1.0"/>
      </material>
    </visual>
  </link>

  <joint name="caster_wheel_joint" type="fixed">
    <parent link="base_link"/>
    <child link="caster_wheel_link"/>
    <origin xyz="0.15 0 -0.08" rpy="0 0 0"/>
  </joint>

</robot>

4.6 使用 urdf_to_graphiz 可视化

URDF 写完了,怎么知道对不对?用 urdf_to_graphiz 工具。它能生成一张树状图,清晰展示 link 和 joint 的层级关系。

运行命令:

# 安装工具(如果还没装)
sudo apt install liburdfdom-tools

# 生成可视化图
urdf_to_graphiz my_robot.urdf

运行后会生成两个文件:my_robot.pdf 和 my_robot.gv。PDF 就是关系图,.gv 是 Graphviz 的源文件。

我的习惯:每写完一个 URDF,第一件事就是跑 urdf_to_graphiz。看一眼生成的树状图,就能发现 link 有没有孤立、joint 有没有连错。比盯着 XML 代码找 bug 快多了。

正常输出的结构应该是这样的:

节点 类型 父节点 子节点
base_link link — left_wheel_joint, right_wheel_joint, caster_wheel_joint
left_wheel_joint continuous base_link left_wheel_link
right_wheel_joint continuous base_link right_wheel_link
caster_wheel_joint fixed base_link caster_wheel_link

如果生成的图里出现孤立节点(没有父节点的 link),或者 joint 方向反了,赶紧回去改 URDF。嗯,这一步省不了。

4.7 常见问题与避坑

  • 轮子不转:检查 joint 的 type 是不是 continuous,fixed 类型的 joint 不会旋转。
  • 轮子位置不对:检查 origin 的 xyz 值,尤其是 z 轴——轮子必须接触地面。
  • 可视化报错:URDF 文件里不能有中文注释,ROS 的解析器对中文支持不好。我吃过这个亏。
  • 惯性矩阵报错:所有 inertia 的值必须大于 0,不能为 0 或负数。

重要提醒:URDF 是纯文本文件,编码必须是 UTF-8。如果你在 Windows 下编辑然后传到 Linux,记得检查换行符——\r\n 会导致解析失败。

好了,到这里你已经完成了第一个完整的机器人 URDF 模型。从底盘到轮子,从 link 到 joint,再到可视化验证——这一套流程,后面做任何机器人都用得上。