4. 构建第一个机器人模型:两轮差速底盘
好,从这一章开始,我们真正动手写代码了。你要构建的,是机器人世界里最经典的结构——两轮差速底盘。说白了,就是像扫地机器人那样,靠两个独立驱动的轮子实现前进、后退和转弯。
我个人习惯,每做一个新机器人,都会先从 URDF 文件开始。为什么?因为它是 ROS 里描述机器人最底层、最标准的方式。你后面做仿真、做导航、做控制,全都得靠它。
4.1 理解两轮差速底盘的结构
先别急着写代码,咱们得搞清楚这个底盘长什么样。一个标准的差速底盘,包含这几个核心部件:
- 底盘本体:承载所有东西的骨架,通常是个矩形或圆柱体
- 左驱动轮:独立电机控制,负责左侧动力
- 右驱动轮:独立电机控制,负责右侧动力
- 万向轮(可选):起支撑作用,不提供驱动力
差速驱动的原理其实很简单:两个轮子转速相同,车就走直线;转速不同,车就转弯。我在项目中遇到过不少新手,一上来就把轮子位置搞反了,结果仿真里机器人原地打转——嗯,这种错误犯一次就记住了。
核心概念:URDF 里用 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,再到可视化验证——这一套流程,后面做任何机器人都用得上。