第二讲:机器人建模与URDF——从零搭建你的第一个机器人

大家好,欢迎来到第二讲。

上一讲我们搭好了环境,这一讲我们来干点实在的——给机器人建模。说白了,就是告诉计算机:你的机器人长什么样?有几个轮子?传感器装在哪?

我刚开始学ROS的时候,觉得URDF就是个XML文件,随便写写就行。后来在Gazebo里跑仿真,机器人直接翻了个跟头……嗯,从那以后我再也不敢小看这个文件了。

URDF是什么?为什么非学不可?

URDF,全称Unified Robot Description Format,统一机器人描述格式。它是ROS里描述机器人几何、运动学、传感器布局的标准语言。

你可以把它想象成机器人的「身份证」+「骨架图」。没有它,Nav2不知道你的机器人多大、激光雷达装在哪、轮子怎么转。说白了,URDF是导航一切计算的基础。

核心要点:URDF只描述刚性连杆和旋转/平移关节。如果你要做柔性机器人(比如机械臂带弹簧),URDF搞不定,得用其他格式。但做轮式移动机器人,URDF绰绰有余。

连杆与关节:机器人的骨头和关节

URDF里两个最核心的概念:link(连杆)和joint(关节)。

  • link:机器人的刚体部件。比如底盘、轮子、激光雷达支架。每个link有几何形状、尺寸、颜色、惯性参数。
  • joint:连接两个link的约束。比如轮子绕轴旋转(revolute)、底盘和雷达之间的固定连接(fixed)。

我个人的习惯是:先画一张草图,标出所有link和joint,再写代码。你想想看,如果连机器人有几个零件都没想清楚,直接写URDF,大概率要返工。

一个最简单的URDF例子

下面这个例子描述了一个底盘加两个轮子的差速驱动机器人:

<robot name="my_robot">
  <!-- 底盘 -->
  <link name="base_link">
    <visual>
      <geometry>
        <box size="0.4 0.3 0.1"/>
      </geometry>
      <material name="blue">
        <color rgba="0 0 1 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.01"/>
    </inertial>
  </link>

  <!-- 左轮 -->
  <link name="left_wheel">
    <visual>
      <geometry>
        <cylinder radius="0.05" length="0.04"/>
      </geometry>
      <material name="black">
        <color rgba="0 0 0 1"/>
      </material>
    </visual>
  </link>

  <!-- 左轮到底盘的关节 -->
  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="-0.15 -0.2 0" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>
</robot>

避坑指南:我曾经在写joint的origin时,把xyz和rpy搞反了,结果轮子悬在半空中。记住:xyz是位置偏移,rpy是旋转角度(弧度制)。

添加传感器:给机器人装上眼睛和耳朵

导航需要感知环境。最常用的传感器:激光雷达、IMU、里程计。在URDF里,传感器也是link,只不过我们给它加上一些特殊的标签。

激光雷达

激光雷达通常用一个圆柱体表示,装在底盘上方。我习惯把它放在一个单独的link里,通过固定关节连接到底盘。这样方便调整高度。

<link name="laser_frame">
  <visual>
    <geometry>
      <cylinder radius="0.03" length="0.05"/>
    </geometry>
    <material name="red">
      <color rgba="1 0 0 1"/>
    </material>
  </visual>
</link>

<joint name="laser_joint" type="fixed">
  <parent link="base_link"/>
  <child link="laser_frame"/>
  <origin xyz="0 0 0.15" rpy="0 0 0"/>
</joint>

IMU

IMU通常放在机器人质心附近。注意:IMU的坐标系约定是X向前、Y向左、Z向上(ROS标准)。

<link name="imu_link">
  <visual>
    <geometry>
      <box size="0.02 0.02 0.02"/>
    </geometry>
  </visual>
</link>

<joint name="imu_joint" type="fixed">
  <parent link="base_link"/>
  <child link="imu_link"/>
  <origin xyz="0 0 0.05" rpy="0 0 0"/>
</joint>

里程计

里程计通常不单独建模,而是通过轮子编码器推算。但我们需要一个虚拟的里程计坐标系(odom),这个在后续的TF树里定义。URDF里只需要保证轮子关节正确即可。

注意:IMU和激光雷达的坐标系必须与真实硬件一致。我见过有人把IMU的Z轴朝下装,结果融合出来的姿态全是反的。检查坐标系!检查坐标系!检查坐标系!

在Gazebo中加载机器人模型

URDF写好了,怎么在Gazebo里看到它?你需要一个launch文件,把URDF加载到参数服务器,然后启动Gazebo。

我常用的做法是:

  1. 把URDF文件放在urdf/目录下
  2. 写一个launch文件,用robot_state_publisher发布TF
  3. 用spawn_entity把机器人放到Gazebo里
<launch>
  <!-- 加载URDF到参数服务器 -->
  <param name="robot_description" 
         command="$(find xacro) $(find my_robot)/urdf/my_robot.urdf"/>

  <!-- 启动机器人状态发布器 -->
  <node name="robot_state_publisher" 
        pkg="robot_state_publisher" 
        type="robot_state_publisher"/>

  <!-- 启动Gazebo -->
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
  </include>

  <!-- 在Gazebo中生成机器人 -->
  <node name="spawn_urdf" 
        pkg="gazebo_ros" 
        type="spawn_model" 
        args="-param robot_description -urdf -model my_robot"/>
</launch>

小技巧:我建议用xacro代替纯URDF。xacro支持宏定义、数学运算、条件判断。比如轮子参数可以定义成变量,改尺寸时只改一处就行。后面课程我们会专门讲xacro。

本章知识体系

下面这张图帮你理清URDF建模的核心逻辑:

URDF机器人建模核心流程 1. 概念设计 画草图:确定link和joint 2. 编写URDF 定义几何、惯性、传感器 3. 加载到ROS robot_state_publisher 4. Gazebo仿真 spawn_model + 物理引擎 4. 实车部署 检查TF树 + 校准传感器 关键检查点 ✓ 所有link都有collision和inertial ✓ 关节类型正确 ✓ 传感器坐标系与硬件一致 ✓ 在Gazebo中能稳定站立 ✓ TF树无断链

常见问题与避坑

问题 原因 解决方案
Gazebo里机器人沉入地面 collision几何与visual不一致,或惯性参数缺失 确保每个link都有collision和inertial标签
激光雷达扫描数据全为零 雷达坐标系与Gazebo中传感器插件不匹配 检查<origin>的rpy是否正确
轮子不转 joint类型错误或未添加传动 差速轮用continuous类型,并添加<transmission>
TF树报错"no transform from ..." URDF中link名称拼写错误 用tf2_tools view_frames检查TF树

重要提醒:URDF里的单位是米和千克。我见过有人把尺寸写成厘米,结果Gazebo里出现一个巨无霸机器人。所有长度单位用米,质量单位用千克。

好了,这一讲的内容就是这些。URDF是机器人建模的基石,看似简单,但细节很多。我建议你亲自动手写一个完整的URDF,从底盘到传感器,再到Gazebo里跑起来。遇到问题很正常,对照上面的表格排查就行。

下一讲我们会深入xacro,让URDF变得更优雅、更可维护。到时候见。

可可精品资料库