4. URDF 核心元素详解:link 与 joint 的底层逻辑

各位同学,欢迎来到第四章。前几章我们把 URDF 的骨架搭起来了,也看到了一个能动的机器人模型。但说实话,那个模型还很“虚”——它没有质量,关节也转得随心所欲。这一章,我们来给机器人注入“物理灵魂”。

URDF 里最核心的两个元素,就是 <link> 和 <joint>。一个负责定义“有什么”,一个负责定义“怎么动”。我见过不少新手,把 link 当成纯视觉模型,把 joint 的坐标系搞得乱七八糟,结果模型在 Gazebo 里要么塌成一团,要么抖得像筛糠。咱们今天就把这些坑一个个填平。

URDF 核心元素:link 与 joint 结构关系 URDF 核心元素:link 与 joint 结构关系 Robot link(刚体) 视觉属性(visual) 碰撞属性(collision) 惯性属性(inertial) 质量、惯性张量、质心 joint(关节) 类型(type) 父子关系(parent/child) 原点(origin) 旋转轴(axis) 一个 joint 连接一个 parent link 和一个 child link,构成运动链

4.1 link 的几何与惯性属性

link 在 URDF 里代表一个刚体。什么叫刚体?就是不会变形的物体。在 Gazebo 的物理引擎里,每个 link 都要有形状、有质量、有碰撞边界。缺了任何一样,仿真都会出问题。

一个完整的 link 定义,通常包含三个子标签:<visual>、<collision> 和 <inertial>。我习惯把它们叫做“三件套”。

4.1.1 visual:看得见的部分

<visual> 定义的是机器人的外观。它只影响显示,不影响物理。你可以把 link 画成一个圆柱体,但碰撞体用长方体——这在视觉上可能有点怪,但物理上完全可行。

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

这里要注意 <origin> 标签。它定义的是几何体相对于 link 自身坐标系的偏移。默认是 (0,0,0),但如果你想让视觉模型偏离 link 的原点,就在这里调。我曾经犯过一个错误:把视觉原点设偏了,结果机器人在 Rviz 里看着是正的,一进 Gazebo 就乱转——因为碰撞体和视觉体没对齐。

4.1.2 collision:物理碰撞的边界

<collision> 的结构和 <visual> 几乎一样,但它决定了物理引擎如何计算接触和碰撞。这里有个关键点:碰撞几何体越简单,仿真越快。

我的经验: 能用 box 就别用 mesh,能用 cylinder 就别用 sphere。复杂的 STL 网格会让碰撞检测慢 10 倍以上。我做过一个六轴机械臂,一开始全用 mesh 做碰撞,Gazebo 跑起来像幻灯片。后来全部换成圆柱和长方体组合,帧率直接翻了三倍。
<collision>
  <geometry>
    <cylinder radius="0.15" length="0.3"/>
  </geometry>
  <origin xyz="0 0 0.15" rpy="0 0 0"/>
</collision>

4.1.3 inertial:物理仿真的灵魂

这是最容易被人忽略的部分。没有 <inertial> 的 link,在 Gazebo 里会被当作质量为零的物体——物理引擎会直接忽略它,或者报错。

惯性属性包含三个要素:

参数 含义 单位 注意事项
mass 质量 kg 不能为 0,建议根据实际零件估算
origin 质心位置 m 相对于 link 坐标系,默认 (0,0,0)
inertia 惯性张量 kg·m² 6 个独立分量:ixx, ixy, ixz, iyy, iyz, izz
<inertial>
  <mass value="2.5"/>
  <origin xyz="0 0 0.05" rpy="0 0 0"/>
  <inertia ixx="0.01" ixy="0" ixz="0"
           iyy="0.01" iyz="0" izz="0.02"/>
</inertial>
避坑指南: 惯性张量不能全填 0!我曾经为了省事,把 ixx、iyy、izz 都设成 0.001,结果机器人一启动就疯狂自旋。物理引擎需要合理的惯性值才能稳定计算。如果你不确定怎么算,可以用在线惯性计算器,或者用 SolidWorks 导出。

4.2 joint 的类型与父子关系

joint 是 URDF 里最灵活的部分。它定义了 link 之间的运动关系。一个 joint 连接一个 parent link 和一个 child link,构成运动链。

joint 有六种类型,但常用的只有四种:

类型 自由度 典型用途 运动范围
fixed 0 固定连接,如相机支架 无
revolute 1(旋转) 关节电机,如机械臂肘部 有限角度,需设 limit
continuous 1(旋转) 车轮、无限旋转关节 无限制
prismatic 1(平移) 线性滑轨、升降台 有限位移,需设 limit

你可能会问:revolute 和 continuous 有什么区别?说白了,一个有限位,一个没限位。机械臂的关节不能转 360 度,所以用 revolute;车轮可以一直转,所以用 continuous。

4.2.1 父子关系的设计原则

URDF 的关节是单向的。parent 是靠近基座的 link,child 是远离基座的 link。这个关系决定了运动链的传递方向。

我建议你画一个树状图再写代码。比如一个简单的两轮小车:

  • base_link(父)→ left_wheel_joint(子:left_wheel_link)
  • base_link(父)→ right_wheel_joint(子:right_wheel_link)
  • base_link(父)→ caster_joint(子:caster_link)

这个结构很清晰。但如果你把轮子挂在另一个轮子上,那运动学就会乱套。记住:每个 link 只能有一个父 joint,但可以有多个子 joint。

4.3 <origin> 与 <axis> 标签

这两个标签是 joint 的“定位器”和“方向器”。很多仿真异常,根源都在这里。

4.3.1 <origin>:关节的位置和姿态

<origin> 定义的是 child link 相对于 parent link 的变换。它包含 xyz(平移)和 rpy(旋转,单位弧度)。

<joint name="arm_joint" type="revolute">
  <parent link="base_link"/>
  <child link="arm_link"/>
  <origin xyz="0.2 0 0.1" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
  <limit effort="100" velocity="1.0" lower="-1.57" upper="1.57"/>
</joint>

这里的 xyz="0.2 0 0.1" 表示:arm_link 的原点相对于 base_link 的原点,在 X 方向偏移 0.2 米,Z 方向偏移 0.1 米。rpy="0 0 0" 表示姿态不变。

关键理解: <origin> 不是关节的旋转中心,而是 child link 坐标系的起点。关节的旋转轴穿过 child link 的原点,方向由 <axis> 指定。

4.3.2 <axis>:旋转或平移的方向

<axis> 定义的是关节的运动方向。对于旋转关节,它是旋转轴;对于平移关节,它是移动方向。

轴向量是相对于 child link 的坐标系。默认值是 0 0 1(Z 轴)。

轴向量 运动方向 常见场景
0 0 1 绕 Z 轴旋转 / 沿 Z 平移 腰部旋转、升降台
0 1 0 绕 Y 轴旋转 / 沿 Y 平移 肘部俯仰、横向滑轨
1 0 0 绕 X 轴旋转 / 沿 X 平移 肩部侧摆、纵向滑轨

我记得有一次调试一个六轴机械臂,第五关节怎么转都不对。查了半天,发现 <axis> 写成了 0 1 1——这是个非法向量,因为轴必须是单位向量。Gazebo 不会报错,但运动完全乱套。从那以后,我每次写 <axis> 都会在心里默念:归一化,归一化,归一化。

小技巧: 如果你不确定轴的方向,可以在 Rviz 里把 joint 的 visual 设成细长的圆柱体,沿着轴方向拉伸。这样一眼就能看出关节朝哪转。

4.4 一个完整的 joint 示例

把上面所有知识点串起来,写一个完整的关节定义:

<joint name="shoulder_pan_joint" type="revolute">
  <parent link="base_link"/>
  <child link="shoulder_link"/>
  <origin xyz="0 0 0.2" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
  <limit effort="50" velocity="2.0" lower="-3.14" upper="3.14"/>
  <dynamics damping="0.7" friction="0.1"/>
</joint>

这个关节模拟了一个腰部旋转轴。parent 是基座,child 是肩部连杆。原点在基座上方 0.2 米处,绕 Z 轴旋转,限位 ±180 度。我还加了阻尼和摩擦参数——这在 Gazebo 里能让运动更真实。