2 · 连杆与关节:<link> & <joint> 详解

好,咱们直接进入正题。URDF 里最核心的两个东西,就是 <link> 和 <joint>。你可以把 link 想象成机器人的骨头——每一块刚体,而 joint 就是连接骨头的关节。没有 joint,link 就是一堆散落的零件。我个人习惯先把 link 的视觉和碰撞属性搭好,再调 joint,这样思路特别顺。

<link> <joint> 惯性 碰撞 视觉 固定 / 旋转 连续 / 棱柱 URDF 连杆 + 关节 = 运动链

<link> 标签:骨头的里里外外

每个 link 有三个主要子块:惯性、碰撞、视觉。视觉是给人看的,碰撞是给物理引擎算的,惯性是给动力学用的。嗯,这里要注意:很多人只写视觉,忘了碰撞,结果仿真里两个零件直接穿模——我早期就吃过这个亏。

🔹 视觉 (visual) —— 定义机器人的长相。用 <geometry> 指定形状(box, cylinder, sphere, mesh),<origin> 定位置,<material> 给颜色。
<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.2 0.4 0.9 1.0"/>
    </material>
  </visual>
</link>

你看,一个简单的底盘就出来了。但如果你只写到这儿,仿真器里它轻飘飘的,没有重量。所以必须加惯性。

💡 我的习惯: 视觉几何和碰撞几何通常用同一个形状,省事。但如果你要做精细碰撞检测,碰撞模型可以简化成凸包或基本体,能大幅提升仿真速度。

惯性参数:别让机器人飘起来

<inertial> 里放 <mass> 和 <inertia>。惯性矩阵 ixx, ixy, ixz, iyy, iyz, izz 这六个值,很多人直接抄网上的,但那样你的机器人动力学会怪怪的。我记得有一次帮朋友调一个六轴机械臂,就是惯性参数给错了,仿真里手臂像面条一样甩……后来用 SolidWorks 导出了正确的惯性张量,问题才解决。

<inertial>
  <mass value="2.0"/>
  <inertia ixx="0.01" ixy="0.0" ixz="0.0"
           iyy="0.02" iyz="0.0"
           izz="0.01"/>
</inertial>

对于简单几何,你可以用公式估算。比如一个实心长方体,质量 m,长宽高 a,b,c,ixx = m*(b²+c²)/12。你想想看,如果随便填个 0.001,那机器人转起来就跟陀螺一样,根本没法控制。

⚠️ 避坑: 我曾经见过有人把惯性矩阵全写成 0,结果 Gazebo 直接崩溃。惯性矩阵必须正定,对角元不能为 0。如果你不确定,至少给一个很小的正数,比如 1e-6。

碰撞属性:物理引擎的硬边界

<collision> 的结构和 visual 几乎一样,但不需要 material。它的几何形状决定了物体怎么撞、怎么接触。我一般把碰撞模型做得比视觉稍微大一点点(比如 1-2mm),这样在导航避障时留点余量,不容易卡住。

<collision>
  <origin xyz="0 0 0" rpy="0 0 0"/>
  <geometry>
    <cylinder radius="0.15" length="0.2"/>
  </geometry>
</collision>

说白了,碰撞模型越简单,仿真越快。移动机器人底盘用圆柱或长方体就够了,没必要上高精网格。

<joint> 标签:让骨头动起来

joint 连接两个 link,有父 link 和子 link。URDF 里 joint 的类型决定了运动自由度。我把它分成四大类,也是你最常用的。

类型自由度典型用途注意点
fixed0把传感器、支架焊死在底盘上没有运动,但必须显式声明
revolute1 (旋转)关节臂、舵机轮有角度限位 <limit>
continuous1 (无限旋转)全向轮、万向轮没有限位,但别忘了给摩擦
prismatic1 (平移)升降台、伸缩臂有位置限位,单位米

举个例子,一个旋转关节(revolute)长这样:

<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 lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>
</joint>

这里 <axis> 定义旋转轴,xyz="0 0 1" 就是绕 Z 轴转。限位用弧度,effort 是最大力矩,velocity 是最大速度。我习惯把 effort 设得比实际大 20%,留点余量。

🔧 个人经验: 连续关节(continuous)常用于轮子。但如果你不给轮子加一点摩擦系数,机器人会在原地打滑。在 <dynamics> 里设置 friction="0.1" 和 damping="0.05",跑起来才真实。

固定关节:最容易被忽略的细节

fixed 关节虽然不动,但你必须写。比如激光雷达固定在底盘上,如果不加 fixed joint,TF 树就断掉了。我之前见过一个新手,把雷达直接挂在 base_link 下没写 joint,结果 rviz 里雷达数据死活不显示——其实就是缺了一个固定关节。

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

你看,就这么简单。但少了它,整个导航的传感器数据就飘在外太空。

棱柱关节:直线运动

prismatic 关节用于滑动,比如电动推杆。限位用米,轴方向决定滑动方向。我调试过一个升降平台,因为 <limit> 的 effort 设太小,平台升到一半就卡住了。后来把 effort 从 50 提到 80,问题解决。

<joint name="lift_joint" type="prismatic">
  <parent link="base_link"/>
  <child link="lift_link"/>
  <axis xyz="0 0 1"/>
  <limit lower="0.0" upper="0.5" effort="80" velocity="0.2"/>
</joint>
📌 核心总结: link 是骨头,joint 是关节。视觉给人看,碰撞给物理,惯性给动力学。关节类型选对了,你的机器人才能像预期那样动起来。写 joint 时别忘了 <origin> 和 <axis>,这两个最容易出错。

嗯,这一章的内容差不多就是这些。你只要把 link 的三个属性和 joint 的四种类型吃透,URDF 建模就迈过了一大半门槛。下一章我们会聊坐标变换和 TF 树,到时候你会更清楚 joint 的 <origin> 到底影响了什么。