第3章 URDF建模实战:两轮差速底盘 + 传感器 + xacro
这一章咱们直接动手。前面两章讲了 URDF 的标签和语法,说实话,光看理论容易犯困。我个人习惯是边写模型边理解,所以这一章我们拿一个真实的两轮差速底盘开刀,加上激光雷达和摄像头,最后用 xacro 把模型收拾得干干净净。
3.1 底盘结构设计
两轮差速底盘,说白了就是两个主动轮 + 一个或两个万向轮。我最早做机器人时,以为底盘越复杂越好,后来发现差速驱动是最皮实、最直观的方案。你想想看,控制左右轮速度差就能转弯,连 PID 都省了一半力气。
我们先定义底盘主体(base_link),然后挂两个驱动轮(left_wheel_link, right_wheel_link),再加一个后部支撑轮(caster_link)。
<!-- 底盘主体 -->
<link name="base_link">
<visual>
<geometry><box size="0.4 0.3 0.12"/></geometry>
<material name="blue"><color rgba="0.2 0.4 0.8 1.0"/></material>
</visual>
<collision>
<geometry><box size="0.4 0.3 0.12"/></geometry>
</collision>
</link>
嗯,这里要注意:碰撞模型尽量和视觉模型一致,否则 Gazebo 或者 MoveIt 会“撞空气”。我遇到过好几次,视觉上看着好好的,一仿真轮子直接陷到地里……后来发现碰撞 box 比视觉小了一圈。
3.2 驱动轮与支撑轮
左右轮分别偏移 y 轴 ±0.2 m,使用 continuous 关节。支撑轮放在后方,用 fixed 关节,但为了滚动效果,可以加一个虚拟旋转关节——不过初学者先固定住也行。
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel_link"/>
<origin xyz="-0.1 0.22 -0.06" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<link name="left_wheel_link">
<visual>
<geometry><cylinder radius="0.05" length="0.04"/></geometry>
<material name="black"/>
</visual>
</link>
右轮类似,把 y 改成 -0.22 就行。支撑轮我一般用一个球体,简单省事。
3.3 添加激光雷达
激光雷达我习惯放在底盘前上方,这样视野好。用固定关节把它固定在 base_link 上,z 轴抬高 0.15 m,x 轴前移 0.05 m。
<link name="laser_link">
<visual>
<geometry><cylinder radius="0.03" length="0.04"/></geometry>
<material name="red"/>
</visual>
</link>
<joint name="laser_joint" type="fixed">
<parent link="base_link"/>
<child link="laser_link"/>
<origin xyz="0.12 0.0 0.15" rpy="0 0 0"/>
</joint>
你可能会问:为什么不用旋转关节?因为激光雷达一般自己带旋转,我们只提供固定安装座就行。在 MoveIt 里,它就是一个感知数据源,不需要运动学。
3.4 添加摄像头
摄像头我习惯装在底盘前部,稍微俯视地面。用 fixed 关节,加一个俯仰角 pitch 约 0.2 rad(大概 11°)。
<link name="camera_link">
<visual>
<geometry><box size="0.03 0.05 0.02"/></geometry>
<material name="dark_gray"/>
</visual>
</link>
<joint name="camera_joint" type="fixed">
<parent link="base_link"/>
<child link="camera_link"/>
<origin xyz="0.18 0.0 0.08" rpy="0 0.2 0"/>
</joint>
3.5 用 xacro 优化模型
写到这里,你会发现很多数字重复出现。比如轮子半径、底盘尺寸、关节偏移。如果每个文件都手写,改一个参数得改七八个地方。xacro 就是干这个的。
我们把常用参数定义成 xacro 属性:
<?xml version="1.0"?>
<robot name="diff_drive_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:property name="wheel_radius" value="0.05"/>
<xacro:property name="wheel_width" value="0.04"/>
<xacro:property name="base_length" value="0.4"/>
<xacro:property name="base_width" value="0.3"/>
<xacro:property name="base_height" value="0.12"/>
<!-- 然后所有 link/joint 里用 ${wheel_radius} 代替 -->
<link name="left_wheel_link">
<visual>
<geometry><cylinder radius="${wheel_radius}" length="${wheel_width}"/></geometry>
</visual>
</link>
</robot>
这样改起来就舒服多了。我一般还会把传感器安装位置也参数化,比如 laser_z_offset、camera_pitch,方便不同车型复用。
3.6 完整模型结构一览
| 部件 | link 名称 | 关节类型 | 父 link | 备注 |
|---|---|---|---|---|
| 底盘主体 | base_link | — | — | 根 link |
| 左驱动轮 | left_wheel_link | continuous | base_link | 绕 y 轴旋转 |
| 右驱动轮 | right_wheel_link | continuous | base_link | 绕 y 轴旋转 |
| 支撑轮 | caster_link | fixed | base_link | 球体,被动 |
| 激光雷达 | laser_link | fixed | base_link | 前上方 |
| 摄像头 | camera_link | fixed | base_link | 前部,俯仰角 0.2 rad |
有了这个表格,你基本可以照着搭积木。我个人建议先写纯 URDF 验证每个关节,再迁移到 xacro。别一上来就上宏,容易 debug 到怀疑人生。
最后,把整个模型保存为 diff_drive.xacro,用 roslaunch urdf_tutorial display.launch model:=diff_drive.xacro 就能看到。嗯,看到轮子转起来的那一刻,还是挺有成就感的。