4. 机器人模型与URDF:从零搭建你的差速底盘
URDF,全称是Unified Robot Description Format。说白了,它就是ROS世界里描述机器人长什么样的“身份证”。你想想看,要让机器人动起来,总得告诉仿真器它有几个轮子、激光雷达装在哪、车身多重吧?URDF就是干这个的。
我个人习惯,每做一个新项目,第一件事就是先把URDF搭好。模型不对,后面所有仿真都是白搭。今天我们就从最经典的差速底盘开始,一步步把模型建起来,再把它请进Gazebo里。
本章核心目标:掌握URDF文件结构,能独立创建差速底盘模型,正确添加激光雷达link与joint,并在Gazebo中成功加载显示。
4.1 URDF文件结构:骨架与血肉
URDF本质上是一个XML文件。它用两种基本元素来描述机器人:link(刚体部件)和joint(连接关系)。
- link:描述一个部件的物理属性,比如形状、尺寸、颜色、质量、惯性矩阵。
- joint:描述两个link之间的连接方式,比如固定、旋转、滑动等。
我刚开始学的时候,总把link和joint搞混。后来记住一句话就再也没错过:link是骨头,joint是关节。骨头不能动,关节才能动。
一个最简单的URDF文件结构长这样:
<robot name="my_robot">
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.1"/>
</geometry>
</visual>
</link>
<joint name="base_to_wheel" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="0 0.2 0" rpy="0 0 0"/>
</joint>
</robot>
注意看,每个joint必须指定parent和child。这个父子关系决定了机器人的运动学树。ROS里不允许出现闭环,也就是说,不能有两个joint指向同一个child。
小技巧:写URDF时,尽量把base_link放在树根。所有其他link都直接或间接挂在它下面。这样后续做TF变换会省很多事。
4.2 创建差速底盘模型:从车身到轮子
差速底盘,就是靠左右轮速度差来实现转向的底盘。它通常包含:一个车身、两个驱动轮、一到两个万向轮。
我们先定义车身。我习惯用圆柱体,因为圆形底盘在狭窄空间里更好转弯。
<link name="base_link">
<visual>
<geometry>
<cylinder radius="0.15" length="0.08"/>
</geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder radius="0.15" length="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>
这里我特别强调一下collision和inertial。很多新手只写visual,结果模型在Gazebo里要么穿模,要么乱飘。collision用于物理碰撞检测,inertial用于动力学计算。这两个不写,仿真就没法用。
接下来加两个驱动轮。左右对称,位置在车身两侧:
<link name="left_wheel">
<visual>
<geometry>
<cylinder radius="0.04" length="0.03"/>
</geometry>
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<material name="black"/>
</visual>
</link>
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="0 0.18 0" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
</joint>
注意axis标签。它定义了轮子的旋转轴。对于差速底盘,轮子绕Z轴旋转。如果你发现轮子转的方向不对,检查一下这里。
我曾经犯过一个低级错误:左右轮的axis写反了,结果机器人原地打转,死活走不直。排查了半小时才发现。
4.3 添加激光雷达link与joint
激光雷达是导航的眼睛。它通常安装在车顶中央,位置要能360度无遮挡。
我们先定义一个雷达link:
<link name="laser_link">
<visual>
<geometry>
<cylinder radius="0.03" length="0.05"/>
</geometry>
<origin xyz="0 0 0" rpy="0 0 0"/>
<material name="red"/>
</visual>
</link>
然后用固定joint把它装到车身上:
<joint name="laser_joint" type="fixed">
<parent link="base_link"/>
<child link="laser_link"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
</joint>
这里xyz的z值设为0.1,表示雷达比车身高出10厘米。为什么?因为车身本身高度是8厘米,雷达装在顶部,中心点自然要抬高。
避坑指南:雷达的安装高度不能太低。我曾经把雷达装在离地5厘米的位置,结果地面反射噪声直接把数据淹没了。建议至少离地10厘米以上。
完整的模型还需要添加gazebo插件,让雷达真正能发出激光数据:
<gazebo reference="laser_link">
<sensor type="ray" name="laser_sensor">
<pose>0 0 0 0 0 0</pose>
<visualize>true</visualize>
<update_rate>10</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1</resolution>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.1</min>
<max>10.0</max>
<resolution>0.01</resolution>
</range>
</ray>
<plugin name="laser_controller" filename="libgazebo_ros_laser.so">
<topicName>/scan</topicName>
<frameName>laser_link</frameName>
</plugin>
</sensor>
</gazebo>
这个插件配置了360度扫描、10米探测距离、10Hz更新频率。这些参数可以根据你实际用的雷达型号调整。
4.4 在Gazebo中加载模型
模型写好了,怎么让它跑起来?我们需要一个launch文件来启动Gazebo并加载URDF。
我个人习惯用spawn_model命令,它可以把URDF模型“生”到Gazebo世界里:
<launch>
<!-- 启动Gazebo空世界 -->
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
</include>
<!-- 加载URDF到参数服务器 -->
<param name="robot_description"
command="$(find xacro)/xacro $(find my_robot)/urdf/my_robot.urdf"/>
<!-- 生成机器人模型 -->
<node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model"
args="-param robot_description -urdf -model my_robot" />
</launch>
这里用了xacro工具。xacro是URDF的增强版,支持宏定义和数学运算。我强烈建议你用xacro代替纯URDF,尤其是当模型复杂时。
启动后,你应该能在Gazebo里看到你的机器人。如果看不到,检查以下几点:
- URDF文件路径是否正确
- 所有link的collision是否定义
- 机器人初始位置是否在地面以上(别埋在地里)
调试技巧:用check_urdf命令检查URDF语法。用urdf_to_graphiz生成模型树状图,直观看到link和joint的层级关系。
4.5 本章知识体系总览
下面这张图总结了URDF建模的核心流程。你可以把它当作一个检查清单,每次建模型时对照着来:
这张图把建模过程分成了四个阶段。每个阶段都有对应的标签和子项。你可以把它截图贴在工位旁边,写URDF时对照着检查,不容易漏掉东西。
4.6 常见问题与避坑
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| Gazebo中模型显示为白色 | material未定义或名称错误 | 在URDF中显式定义<material>标签 |
| 机器人掉到地面以下 | collision未定义或尺寸不对 | 确保collision与visual尺寸一致 |
| 轮子不转 | joint的axis设置错误 | 检查axis方向,差速轮通常绕Z轴 |
| 激光雷达无数据 | gazebo插件未加载 | 检查<plugin>标签和库路径 |
| 模型加载时报错 | URDF语法错误 | 用check_urdf工具检查 |
嗯,这张表基本覆盖了我这些年踩过的坑。尤其是轮子不转那个问题,我至少遇到过三次。每次都是因为axis写成了1 0 0(绕X轴),但差速轮明明应该绕Z轴转。
好了,到这里你已经掌握了URDF建模的核心技能。从link到joint,从车身到雷达,再到Gazebo加载,整个流程都走了一遍。接下来你可以试着修改参数,比如把底盘改成矩形,或者加一个IMU传感器,看看效果有什么不同。