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建模核心流程 1. 定义link visual(视觉) collision(碰撞) inertial(惯性) material(材质) 2. 定义joint parent / child origin(位置) axis(旋转轴) type(关节类型) 3. 添加传感器 gazebo插件配置 话题名称与帧ID 扫描参数设置 4. 启动Gazebo仿真 💡 使用xacro代替纯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传感器,看看效果有什么不同。