4. 四足机器人URDF建模(上):URDF语法详解、连杆与关节定义、坐标变换基础

好,咱们进入正题。URDF,全称是Unified Robot Description Format,说白了就是一套用XML写机器人模型的“说明书”。你给Gazebo或者Rviz看这个文件,它们就知道你的机器人长什么样、关节怎么动、传感器装在哪。

我个人习惯把URDF比作机器人的“骨架+关节+皮肤”的完整描述。没有它,仿真就无从谈起。今天咱们先搞定最核心的两个元素:link(连杆)和joint(关节),再聊聊背后的坐标变换(TF)基础。

本章核心目标: 掌握URDF的XML语法结构,能独立编写一个包含至少3个连杆、2个关节的简单机器人模型,并理解TF树是如何从URDF自动生成的。

4.1 URDF文件结构:从根到叶

一个标准的URDF文件,最外层是<robot>标签,里面包裹着所有的link和joint。我刚开始学的时候,总把link和joint搞混——记住一句话:link是刚体,joint是约束。

<?xml version="1.0"?>
<robot name="my_quadruped">
  <!-- 这里放link和joint -->
</robot>

嗯,就这么简单。但实际写起来,细节都在每个标签的属性里。咱们先看link。

4.2 连杆(link)定义:视觉、碰撞与惯性

一个link通常包含三个子块:visual(视觉)、collision(碰撞)和inertial(惯性)。你可能会问:“visual和collision看起来差不多,能不能只写一个?”

我在项目里踩过这个坑——如果只写visual,Gazebo的物理引擎会默认用visual的几何体做碰撞检测,但visual的网格往往太精细,导致仿真卡成PPT。所以,collision一定要用简化几何体。

子标签 作用 常用几何体 避坑提示
<visual> 显示外观,给Rviz看 box, cylinder, sphere, mesh mesh文件路径用package://引用
<collision> 物理碰撞检测,给Gazebo用 box, cylinder, sphere(尽量用简单形状) 形状和尺寸尽量接近visual,但可以更粗糙
<inertial> 质量、惯性矩阵,影响动力学 必须包含<mass>和<inertia> 惯性矩阵用ixx到izz六个分量

举个例子,一个简单的腿部连杆(大腿)可以这样写:

<link name="thigh_link">
  <visual>
    <geometry>
      <box size="0.05 0.05 0.25"/>
    </geometry>
    <origin xyz="0 0 0.125" rpy="0 0 0"/>
    <material name="blue">
      <color rgba="0.2 0.4 0.8 1.0"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <box size="0.05 0.05 0.25"/>
    </geometry>
    <origin xyz="0 0 0.125" rpy="0 0 0"/>
  </collision>
  <inertial>
    <mass value="0.5"/>
    <inertia ixx="0.01" ixy="0.0" ixz="0.0"
             iyy="0.01" iyz="0.0"
             izz="0.001"/>
  </inertial>
</link>

小技巧: <origin>里的xyz是相对于link自身坐标系的偏移。我习惯把link的坐标系原点放在关节旋转轴上,这样后续写joint的<origin>会省很多事。

4.3 关节(joint)定义:连接与运动

joint把两个link连起来,并定义它们之间的相对运动。URDF支持6种关节类型,但做四足机器人,最常用的是revolute(旋转关节)和continuous(连续旋转关节)。

关节类型 自由度 运动范围 四足机器人应用场景
revolute 1(旋转) 有限角度(如 -90° ~ +90°) 髋关节、膝关节
continuous 1(旋转) 无限制(可连续转圈) 轮式关节(四足一般不常用)
prismatic 1(平移) 有限距离 伸缩腿(少见)
fixed 0(固定) 无 机身与传感器支架
floating 6(全自由) 无限制 基座与世界的连接(仿真中常用)
planar 3(平面运动) 有限 很少用

写joint时,有四个属性必须搞清楚:

  1. name:关节名称,全局唯一。
  2. type:关节类型,上面表格里的六种之一。
  3. parent:父连杆,运动学树的上层。
  4. child:子连杆,运动学树的下层。

我曾经犯过一个低级错误:把parent和child写反了,结果在Rviz里看到机器人的腿“长”到了天上。记住,parent是靠近基座的那一端。

<joint name="hip_joint" type="revolute">
  <parent link="base_link"/>
  <child link="thigh_link"/>
  <origin xyz="0.1 0.0 0.0" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>  <!-- 绕Y轴旋转,即侧摆 -->
  <limit lower="-1.57" upper="1.57" effort="10" velocity="5"/>
</joint>

注意: <limit>里的effort和velocity是给Gazebo的电机控制器看的。如果你不写,Gazebo会默认用无穷大,但实际仿真时电机可能“软绵绵”的。我一般根据实际舵机参数来填。

4.4 坐标变换(TF)基础:从URDF到TF树

URDF文件写好后,ROS会自动解析它,生成一棵TF树。TF(Transform)就是坐标系之间的变换关系。你想想看,机器人有那么多连杆,每个连杆都有自己的坐标系,怎么知道它们之间的相对位置?

答案就是:URDF里的joint定义了父坐标系到子坐标系的变换。具体来说,joint的<origin>给出了父link坐标系到子link坐标系的平移和旋转,<axis>定义了关节的运动方向。

我画了一张图,帮你理清link、joint和TF之间的关系:

URDF与TF树关系图 URDF → TF树 映射关系 URDF 文件 base_link (根) hip_joint (revolute) thigh_link knee_joint (revolute) ROS解析 TF 树 base_link TF: hip_joint thigh_link TF: knee_joint shank_link 每个 joint 对应 TF 树中的一条边,origin 和 axis 共同决定了变换矩阵

在ROS中,你可以用tf2_tools包里的view_frames工具,直接看到URDF生成的TF树。我每次写完URDF,第一件事就是跑这个命令,检查树的结构对不对。

# 启动机器人状态发布器
roslaunch my_quadruped_description display.launch

# 在另一个终端查看TF树
rosrun tf2_tools view_frames.py
evince frames.pdf  # 打开生成的PDF

个人经验: 如果TF树里出现了“断链”(某个link没有父节点),十有八九是joint的parent或child名字拼错了。URDF对大小写敏感,base_link和Base_Link是两个完全不同的东西。

4.5 实战:搭建一个单腿模型

光说不练假把式。咱们用刚才学的知识,搭一个最简单的单腿模型:基座(base_link)→ 髋关节(hip_joint)→ 大腿(thigh_link)→ 膝关节(knee_joint)→ 小腿(shank_link)。

这个模型虽然只有3个link和2个joint,但它是四足机器人单腿的缩影。我当年做第一个四足仿真时,就是先调通这条腿,再复制粘贴成四条腿的。

<?xml version="1.0"?>
<robot name="single_leg">
  <!-- 基座 -->
  <link name="base_link">
    <visual>
      <geometry><box size="0.1 0.1 0.05"/></geometry>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <material name="gray">
        <color rgba="0.5 0.5 0.5 1.0"/>
      </material>
    </visual>
  </link>

  <!-- 髋关节(侧摆) -->
  <joint name="hip_joint" type="revolute">
    <parent link="base_link"/>
    <child link="thigh_link"/>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10" velocity="5"/>
  </joint>

  <!-- 大腿 -->
  <link name="thigh_link">
    <visual>
      <geometry><box size="0.04 0.04 0.2"/></geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="blue">
        <color rgba="0.2 0.4 0.8 1.0"/>
      </material>
    </visual>
    <inertial>
      <mass value="0.3"/>
      <inertia ixx="0.001" ixy="0" ixz="0"
               iyy="0.001" iyz="0"
               izz="0.0001"/>
    </inertial>
  </link>

  <!-- 膝关节(前后摆动) -->
  <joint name="knee_joint" type="revolute">
    <parent link="thigh_link"/>
    <child link="shank_link"/>
    <origin xyz="0 0 0.2" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.0" upper="0.0" effort="8" velocity="5"/>
  </joint>

  <!-- 小腿 -->
  <link name="shank_link">
    <visual>
      <geometry><box size="0.03 0.03 0.2"/></geometry>
      <origin xyz="0 0 0.1" rpy="0 0 0"/>
      <material name="red">
        <color rgba="0.8 0.2 0.2 1.0"/>
      </material>
    </visual>
    <inertial>
      <mass value="0.2"/>
      <inertia ixx="0.0005" ixy="0" ixz="0"
               iyy="0.0005" iyz="0"
               izz="0.00005"/>
    </inertial>
  </link>
</robot>

避坑指南: 膝关节的lower和upper我设成了-2.0到0.0,这意味着小腿只能向后摆(弯曲),不能向前伸直超过180度。这是模仿真实四足动物的膝关节结构。如果你设成对称范围,仿真时腿可能会“反关节”运动,看着很诡异。

4.6 本章小结

咱们今天把URDF的link和joint讲透了。link负责“长什么样、多重、怎么撞”,joint负责“怎么动、动多少”。TF树就是这些关系的图形化表达。你写URDF时,心里要时刻想着那棵TF树——每个joint就是一条边,每个link就是一个节点。

下一章,咱们会把这个单腿模型扩展成完整的四足机器人,并加入传感器和传动装置。到时候你会发现,今天的单腿模型就是四足机器人的“细胞”,复制四份,调整一下位置,一个四足机器人就出来了。

嗯,今天就到这里。记得动手写一个自己的URDF文件,用check_urdf工具验证一下语法。有问题咱们下节课聊。