4、URDF实战:搭建一个两轮差速底盘机器人模型(含驱动轮与万向轮)
好,咱们直接动手。这一章我带你从零搭一个两轮差速底盘,带驱动轮和万向轮。你想想看,差速底盘几乎是移动机器人的“Hello World”,但真正把URDF写对、写规范,还是有不少细节的。我个人习惯先把整体结构理清楚,再一行一行写代码。
4.1 底盘整体设计思路
差速底盘的核心就三个轮子:两个主动驱动轮,一个万向支撑轮。驱动轮负责前进、后退和转向,万向轮只跟着跑,不提供动力。嗯,这里要注意:万向轮在URDF里一般用连续关节(continuous)或者固定关节表示,但为了仿真更真实,我习惯用连续关节加一个小的旋转轴。
我记得第一次做差速底盘时,把万向轮做成了固定关节,结果仿真里一转弯就卡死。后来才明白,万向轮必须能自由旋转,否则运动学会出问题。
4.2 定义 base_link 与底盘尺寸
base_link 是所有轮子的父坐标系。我一般把底盘简化为一个扁长方体,方便碰撞检测。下面是我常用的参数:
| 部件 | 几何形状 | 尺寸 (m) | 质量 (kg) |
|---|---|---|---|
| 底盘 (base_link) | box | 0.4 × 0.3 × 0.1 | 2.0 |
| 驱动轮 (左右) | cylinder | 半径0.06, 长度0.04 | 0.3 |
| 万向轮 | sphere | 半径0.03 | 0.1 |
你可能会问,为什么驱动轮用圆柱,万向轮用球体?说白了,球体在仿真里滚动最自然,而且碰撞计算简单。驱动轮用圆柱才能体现轮子宽度。
4.3 驱动轮关节配置
驱动轮必须用连续关节(continuous),这样才能无限旋转。左右轮分别命名为 left_wheel_joint 和 right_wheel_joint。我个人习惯把左轮放在 x 轴负方向,右轮放在正方向,y 轴对齐轮轴。
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel_link"/>
<origin xyz="0.0 -0.18 -0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/> <!-- 绕 y 轴旋转 -->
</joint>
<joint name="right_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="right_wheel_link"/>
<origin xyz="0.0 0.18 -0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
注意轴的方向。差速底盘两个轮子绕 y 轴旋转,但方向相反才能转弯。这个在后续的控制器里处理,URDF 只负责运动学描述。
4.4 万向轮关节与避坑
万向轮我推荐用 continuous 类型,并设置一个垂直的旋转轴(绕 z 轴)。这样它就能像真实万向轮一样自由摆动。我曾经踩过一个坑:把万向轮做成 fixed 关节,结果在 Gazebo 里一推就翻车。
<joint name="caster_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="caster_wheel_link"/>
<origin xyz="-0.12 0.0 -0.08" rpy="0 0 0"/>
<axis xyz="0 0 1"/> <!-- 绕 z 轴自由旋转 -->
</joint>
万向轮本身是一个小球体,用 sphere 几何体。如果你希望更逼真,也可以用圆柱加一个小滚轮,但初学者用球体最省事。
4.5 完整 URDF 文件结构
下面我把整个底盘模型串起来。你直接复制就能用,但记得根据你的机器人实际尺寸微调 origin。
<robot name="two_wheel_diff_drive">
<!-- 底盘 -->
<link name="base_link">
<visual>
<geometry><box size="0.4 0.3 0.1"/></geometry>
<material name="blue"><color rgba="0 0.4 0.8 1"/></material>
</visual>
<collision>
<geometry><box size="0.4 0.3 0.1"/></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>
<!-- 左驱动轮 -->
<link name="left_wheel_link">
<visual>
<geometry><cylinder radius="0.06" length="0.04"/></geometry>
<material name="black"><color rgba="0.1 0.1 0.1 1"/></material>
</visual>
<collision>
<geometry><cylinder radius="0.06" length="0.04"/></geometry>
</collision>
<inertial>
<mass value="0.3"/>
<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.0 -0.18 -0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- 右驱动轮 -->
<link name="right_wheel_link">
<visual>
<geometry><cylinder radius="0.06" length="0.04"/></geometry>
<material name="black"/>
</visual>
<collision>
<geometry><cylinder radius="0.06" length="0.04"/></geometry>
</collision>
<inertial>
<mass value="0.3"/>
<inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0002"/>
</inertial>
</link>
<joint name="right_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="right_wheel_link"/>
<origin xyz="0.0 0.18 -0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- 万向轮 -->
<link name="caster_wheel_link">
<visual>
<geometry><sphere radius="0.03"/></geometry>
<material name="gray"><color rgba="0.5 0.5 0.5 1"/></material>
</visual>
<collision>
<geometry><sphere radius="0.03"/></geometry>
</collision>
<inertial>
<mass value="0.1"/>
<inertia ixx="0.00001" ixy="0" ixz="0" iyy="0.00001" iyz="0" izz="0.00001"/>
</inertial>
</link>
<joint name="caster_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="caster_wheel_link"/>
<origin xyz="-0.12 0.0 -0.08" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
</joint>
</robot>
check_urdf 命令验证你的模型。我每次写完都会跑一遍 check_urdf my_robot.urdf,它能快速发现缺失的惯性参数或关节循环。
4.6 可视化与调试
在终端运行 urdf_to_graphiz my_robot.urdf 可以生成结构图。我个人习惯用 rviz 实时查看,加上 robot_state_publisher,就能看到轮子是否在正确位置。
嗯,这里要注意:如果轮子显示穿透底盘,多半是 origin 的 z 值没算对。底盘厚度 0.1,轮子半径 0.06,所以轮子中心应该在 z = -0.05 左右(底盘下表面)。