4. 机器人模型与URDF:让仿真世界认识你的机器人

好,我们进入第四讲。前面几章我们把仿真环境搭起来了,也跑通了基本的通信。但有个关键问题——仿真器里那个呆呆的方块,它不认识你的机器人长什么样、有几个关节、轮子怎么转。

URDF,就是用来干这个的。它是一份「机器人身份证」。说白了,就是用XML格式告诉Gazebo:我的机器人有几根骨头(link),关节长在哪(joint),每个零件长啥样、多重、什么颜色。

我个人习惯,写URDF之前一定先画个草图。哪怕用纸笔都行。你想想看,一个六轴机械臂,关节顺序搞反了,后面调运动学全得重来。我在项目里吃过这个亏,后来再也不敢偷懒了。

4.1 URDF文件结构:一棵树

URDF本质上是一棵树。根节点是<robot>,下面挂link和joint。每个joint连接两个link,形成父子关系。

举个例子,一个两轮差速底盘:

<robot name="my_robot">
  <link name="base_link">
    <visual>
      <geometry>
        <box size="0.4 0.3 0.1"/>
      </geometry>
    </visual>
  </link>

  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="-0.15 0.2 -0.05" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>

  <link name="left_wheel">
    <visual>
      <geometry>
        <cylinder radius="0.05" length="0.03"/>
      </geometry>
    </visual>
  </link>
</robot>

看到没?base_link是父link,left_wheel是子link,通过left_wheel_joint连起来。这个结构会递归展开,形成一棵完整的树。

核心原则:每个link只能有一个父joint,但可以有多个子joint。不能有环,必须是树。

4.2 link定义:机器人的「骨头」

link就是刚体零件。每个link可以包含三部分:

  • visual:视觉模型,用来显示。Gazebo里你看到的样子就是它。
  • collision:碰撞模型,用来做物理碰撞检测。可以比visual简单,节省计算。
  • inertial:惯性参数,质量、质心、转动惯量。没有这个,物理引擎没法算动力学。

我见过很多新手只写visual,不写collision和inertial。结果呢?仿真里机器人直接穿透地面,或者一动就飞了。嗯,这里要注意——collision和inertial不是可选项,是必选项,除非你只做纯可视化。

来看一个完整的link例子:

<link name="base_link">
  <visual>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <geometry>
      <box size="0.4 0.3 0.1"/>
    </geometry>
    <material name="blue">
      <color rgba="0 0 1 1"/>
    </material>
  </visual>

  <collision>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <geometry>
      <box size="0.4 0.3 0.1"/>
    </geometry>
  </collision>

  <inertial>
    <mass value="2.0"/>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <inertia
      ixx="0.01" ixy="0" ixz="0"
      iyy="0.01" iyz="0"
      izz="0.01"/>
  </inertial>
</link>

小技巧:碰撞模型尽量用简单几何体。比如一个复杂的机械臂外壳,visual用精细的STL,collision用几个box和cylinder拼起来就行。仿真性能会好很多。

4.3 joint定义:机器人的「关节」

joint决定了两个link之间的相对运动。URDF支持6种joint类型:

类型 自由度 典型用途
fixed 0 把两个link焊死,比如摄像头支架
revolute 1(旋转,有限位) 机械臂关节,有角度范围
continuous 1(旋转,无限位) 车轮,可以一直转
prismatic 1(平移,有限位) 升降台、滑轨
planar 2(平面内平移+旋转) 全向轮底盘(不常用)
floating 6(全自由) 无人机、水下机器人

joint里最重要的几个参数:

  • origin:子link相对于父link的位姿。这个最容易搞错,我建议用rviz可视化确认。
  • axis:旋转轴或平移方向。在父link坐标系下定义。
  • limit:对于revolute和prismatic,必须设置lower、upper、effort、velocity。

我曾经帮一个团队排查问题,他们的机械臂仿真总是乱抖。查了两天,发现是joint的origin写反了——子link的坐标原点跑到了父link外面,导致运动学奇异。从那以后,我每写一个joint都会在rviz里看一眼。

4.4 在仿真中显示机器人

URDF写好了,怎么让Gazebo认出来?三步走:

  1. 把URDF文件放到功能包的urdf/目录下。我习惯命名为my_robot.urdf。
  2. 写一个launch文件,用robot_state_publisher发布TF,用spawn_model把机器人放进Gazebo。
  3. 启动rviz,添加RobotModel显示,选择你的URDF。

一个典型的launch文件长这样:

<launch>
  <!-- 加载URDF到参数服务器 -->
  <param name="robot_description"
         textfile="$(find my_robot_description)/urdf/my_robot.urdf"/>

  <!-- 发布TF -->
  <node name="robot_state_publisher"
        pkg="robot_state_publisher"
        type="robot_state_publisher"/>

  <!-- 把机器人放到Gazebo -->
  <node name="spawn_urdf"
        pkg="gazebo_ros"
        type="spawn_model"
        args="-param robot_description -urdf -model my_robot"/>
</launch>

启动之后,你应该能在Gazebo里看到你的机器人,在rviz里看到TF树和模型。如果看不到,先检查robot_state_publisher有没有报错,再检查joint的origin是不是合理。

常见坑:Gazebo和rviz的坐标系可能不一致。Gazebo默认ENU(东-北-天),rviz默认FLU(前-左-天)。如果你的机器人是差速底盘,注意base_link的朝向。我一般统一用ENU,省得后面头疼。

4.5 本章知识体系

下面这张图总结了URDF的核心结构,以及从URDF到仿真显示的完整链路:

URDF知识体系与仿真显示链路 URDF 文件 link(刚体零件) joint(关节) 树形结构 visual(视觉) collision(碰撞) inertial(惯性) origin(位姿) axis(轴) limit(限位) Gazebo 仿真 rviz 可视化 robot_state_publisher

从这张图可以看得很清楚:URDF定义了link和joint,link又包含visual/collision/inertial,joint包含origin/axis/limit。最终通过robot_state_publisher桥接到Gazebo和rviz,完成仿真显示。

我个人建议,刚开始学的时候,先写一个最简单的两轮底盘,只有base_link和两个轮子。在rviz里确认每个joint的origin都对了,再往上加传感器、加机械臂。一点点来,别想一口吃成胖子。

好,这一章就到这。URDF是机器人仿真的基石,后面所有内容——传感器仿真、运动控制、导航算法——都建立在它之上。把这一章吃透,后面的路会顺很多。