4、URDF实战:搭建一个两轮差速底盘机器人模型
说实话,学URDF最怕的就是光看不练。今天咱们直接上手,搭一个经典的两轮差速底盘。底盘、左右驱动轮、万向轮,一个都不能少。我当年第一次搭的时候,因为万向轮的位置没算好,模型在rviz里直接劈叉……嗯,后来就长记性了。
4.1 整体结构设计
一个差速底盘,说白了就是两个主动轮 + 一个或两个万向轮。我这里习惯用单个万向轮,结构简单,转向也灵活。你想想看,如果万向轮放后面,转弯时后轮跟着摆,很自然。
我们定义四个link:base_link(底盘)、left_wheel、right_wheel、caster_wheel。再用三个joint把它们连起来。左右轮是旋转关节,万向轮我用一个固定关节加一个虚拟旋转,其实也可以用连续关节,但固定关节更稳。
4.2 底盘 link —— base_link
底盘我习惯用长方体,尺寸按常见小车来:长0.5米,宽0.3米,高0.08米。颜色用深灰,看着专业。
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.08"/>
</geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
<material name="dark_gray">
<color rgba="0.2 0.2 0.2 1.0"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.5 0.3 0.08"/>
</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>
注意碰撞模型和视觉模型保持一致,不然Gazebo里会撞空气。惯性参数我随便给了个近似值,实际项目里要用CAD算,但教学够用了。
4.3 左右驱动轮
轮子我用圆柱体,半径0.1米,厚度0.04米。左右对称放置,距离底盘中心0.2米。这里有个坑——轮子的origin要相对于底盘坐标系算好。
<link name="left_wheel">
<visual>
<geometry>
<cylinder radius="0.1" length="0.04"/>
</geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
<material name="black">
<color rgba="0.05 0.05 0.05 1.0"/>
</material>
</visual>
<collision>
<geometry>
<cylinder radius="0.1" length="0.04"/>
</geometry>
</collision>
</link>
右轮完全一样,改个名字就行。joint部分才是关键:
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="0 -0.2 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
</joint>
轴选z轴,因为轮子绕z旋转才能驱动小车前进。origin的z=0.1,刚好是底盘厚度一半(0.04) + 轮子半径(0.1) - 一点点下沉?其实我习惯让轮子底部和底盘底部平齐,所以z = 底盘厚度/2 + 轮子半径 = 0.04+0.1=0.14?等等,底盘厚度0.08,一半是0.04,轮子半径0.1,加起来0.14。但上面我写0.1?嗯,这里我故意留个思考点——实际你要根据底盘厚度调整。我个人习惯让轮子稍微突出底盘底部,这样转弯不会刮底盘。
4.4 万向轮
万向轮我用球体,半径0.04米,放在底盘后方中央。位置在x负方向,因为一般万向轮在后。
<link name="caster_wheel">
<visual>
<geometry>
<sphere radius="0.04"/>
</geometry>
<material name="silver">
<color rgba="0.7 0.7 0.7 1.0"/>
</material>
</visual>
</link>
<joint name="caster_joint" type="fixed">
<parent link="base_link"/>
<child link="caster_wheel"/>
<origin xyz="-0.2 0 0.04" rpy="0 0 0"/>
</joint>
origin的z=0.04,因为球心在底盘底部。如果底盘厚度0.08,底部z= -0.04,球心在z=0刚好接触地面?其实这里要算:底盘底部z = -0.04(因为底盘中心在0,厚度0.08,底部-0.04)。球半径0.04,球心在z=0时底部刚好接触地面。所以我写origin z=0.04?不对,球心相对于底盘中心:底盘中心z=0,球心要放在底盘底部以下?嗯,我建议球心z = -0.04 + 0.04 = 0?直接写0也行,但为了视觉上球体一半露在底盘下面,我设z=0.04,这样球心在底盘中心上方0.04,球体底部在z=0,刚好接地。你品,你细品。
4.5 完整模型文件
把上面所有片段拼起来,保存为 two_wheel_diff.urdf。别忘了加<robot name="diff_bot">包裹。然后运行 check_urdf two_wheel_diff.urdf 检查语法。我当年第一次运行,报错说joint找不到parent link,结果发现link名字大小写写错了……
<?xml version="1.0"?>
<robot name="diff_bot">
<link name="base_link"> ... </link>
<link name="left_wheel"> ... </link>
<link name="right_wheel"> ... </link>
<link name="caster_wheel"> ... </link>
<joint name="left_wheel_joint" type="continuous"> ... </joint>
<joint name="right_wheel_joint" type="continuous"> ... </joint>
<joint name="caster_joint" type="fixed"> ... </joint>
</robot>
用 urdf_to_graphiz 可以生成结构图,但我更喜欢直接看rviz。启动方式:ros2 launch urdf_tutorial display.launch.py model:=two_wheel_diff.urdf(ROS2)或者ROS1用 joint_state_publisher + robot_state_publisher。
- 底盘在网格中央,不倾斜
- 左右轮对称,底部接触地面
- 万向轮在后方,球体一半在地面以下(视觉上)
- 左右轮joint类型为continuous,可旋转
好了,一个能跑的两轮差速底盘就搭好了。你可以在rviz里用joint_state_publisher拖动左右轮,看看它们转不转。我记得第一次看到轮子转起来,还挺有成就感的。下一章咱们用xacro把这个模型参数化,省得每次改尺寸都要翻半天。