3、URDF基础语法(下):joint标签详解、连续型与旋转型关节、固定关节、父子坐标系关系、运动学树结构

上一章我们把 link 标签聊透了。今天来啃 joint——URDF 里真正决定机器人「能不能动」的核心。

我刚开始学 URDF 时,觉得 joint 不就是两个零件之间的连接嘛,写几个参数就行了。结果第一次搭六轴机械臂,运动学树死活对不上,末端执行器飞到天上去。后来才发现,是 joint 的坐标系关系搞反了。

所以这一章,咱们把 joint 的每个属性、每种类型、以及它背后的坐标系逻辑,一次性讲清楚。

3.1 joint 标签的结构

一个完整的 joint 定义长这样:

<joint name="joint_1" type="revolute">
    <parent link="base_link"/>
    <child link="arm_link"/>
    <origin xyz="0 0 0.2" rpy="0 0 0"/>
    <axis xyz="0 0 1"/>
    <limit effort="100" velocity="1.0" lower="-3.14" upper="3.14"/>
    <dynamics damping="0.1" friction="0.01"/>
</joint>

每个字段都有它的使命。咱们逐个拆解。

3.1.1 parent 和 child:谁是谁的爹

joint 定义的是两个 link 之间的约束关系。parent 是父级,child 是子级。这个父子关系决定了运动学树的拓扑结构。

我见过有人把 parent 和 child 写反,结果整个机器人模型在 RViz 里像被拧麻花一样。记住:parent 是靠近基座的那一端,child 是远离基座的那一端。

3.1.2 origin:关节坐标系的位置

origin 定义的是 child link 的坐标系相对于 parent link 坐标系的偏移和旋转。

  • xyz:平移量,单位米
  • rpy:旋转量,单位弧度(roll, pitch, yaw)

这里有个坑:origin 是相对于 parent 的坐标系,不是相对于世界坐标系。我早期做项目时,总以为 xyz 是坐标,结果关节一转动,整个子链全跑偏。

我的习惯:每次写完 joint 的 origin,我都会在脑子里过一遍——「如果 parent 坐标系在这里,child 坐标系应该在哪里?」想清楚了再填数值。

3.1.3 axis:关节的旋转轴

对于旋转关节和连续关节,axis 指定了旋转轴的方向。xyz 是单位向量,表示在 child 坐标系下的旋转轴方向。

举个例子:axis xyz="0 0 1" 表示绕 Z 轴旋转。如果是 1 0 0,就是绕 X 轴。

我曾经犯过一个错:把 axis 写成了 0 0 2,结果关节转起来角度翻倍。axis 必须是单位向量,否则 ROS 会给你一个警告,但不会报错——这才是最坑的。

3.1.4 limit 和 dynamics:关节的物理约束

参数 含义 单位
effort 关节最大力矩 N·m
velocity 关节最大速度 rad/s
lower 关节下限角度 rad
upper 关节上限角度 rad

dynamics 里的 damping 和 friction 用于仿真中的动力学计算。如果你只做运动学,这两个可以省略。但做仿真时,阻尼设太小,关节会像抹了油一样晃个不停。

3.2 关节类型详解

URDF 定义了 6 种关节类型。实际项目中,90% 的情况只用其中 3 种。

3.2.1 revolute(旋转型关节)

最常见的关节类型。绕一个轴旋转,有角度限制。比如机械臂的肩关节、肘关节。

关键点:必须设置 limit 的 lower 和 upper。否则 ROS 会报错。

<joint name="shoulder_joint" type="revolute">
    <parent link="base_link"/>
    <child link="shoulder_link"/>
    <origin xyz="0 0 0.1" rpy="0 0 0"/>
    <axis xyz="0 0 1"/>
    <limit effort="200" velocity="2.0" lower="-2.09" upper="2.09"/>
</joint>

3.2.2 continuous(连续型关节)

和 revolute 类似,但没有角度限制。可以无限旋转。常用于轮式机器人的车轮、或者某些特殊机构的末端。

注意:continuous 关节不需要 lower 和 upper。如果你写了,ROS 会忽略。

<joint name="wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="wheel_link"/>
    <origin xyz="0.2 -0.1 0" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit effort="50" velocity="5.0"/>
</joint>
我曾经踩过的坑:做 AGV 小车时,把车轮关节设成了 revolute,结果轮子转到限位就卡住,小车原地打转。换成 continuous 就好了。所以,能转圈的就用 continuous,有角度限制的才用 revolute。

3.2.3 fixed(固定关节)

两个 link 之间没有相对运动。比如传感器支架、装饰件。

fixed 关节不需要 axis 和 limit。只需要 parent、child 和 origin。

<joint name="camera_mount" type="fixed">
    <parent link="head_link"/>
    <child link="camera_link"/>
    <origin xyz="0.05 0 0.1" rpy="0 -0.3 0"/>
</joint>

fixed 关节虽然简单,但它的 origin 同样重要。我见过有人把相机装反了,就是因为 rpy 写成了 0 0 0,而实际上相机需要向下倾斜 30 度。

3.2.4 其他关节类型

类型 说明 使用场景
prismatic 直线移动关节 滑轨、升降台
planar 平面运动关节 全向移动平台(很少用)
floating 6 自由度浮动关节 无人机、水下机器人(很少用)

prismatic 在工业机器人里偶尔见到,planar 和 floating 我做了这么多年项目,一次都没用过。你如果做移动机器人,重点关注 continuous 和 fixed 就够了。

3.3 父子坐标系关系

这是 joint 最核心的概念,也是最容易出错的地方。

每个 joint 定义了一个坐标系变换。这个变换是从 parent 坐标系到 child 坐标系的。换句话说:

  • parent 坐标系是参考系
  • origin 是 child 坐标系在 parent 坐标系下的位姿
  • axis 是 child 坐标系下的旋转轴

为什么 axis 是在 child 坐标系下?因为关节转动时,是 child link 相对于 parent link 在转。轴固定在 child 上,跟着 child 一起动。

我刚开始理解这个时,总觉得别扭。后来画了个图才明白:

一句话总结:parent 是「不动的那一端」,child 是「动的那一端」。origin 告诉 child 相对于 parent 在哪,axis 告诉 child 怎么转。

3.4 运动学树结构

多个 joint 和 link 串联起来,就形成了运动学树。树的根节点是 base_link,叶子节点是末端执行器或传感器。

运动学树有几个特点:

  • 有向无环:从根到叶子只有一条路径,不能有环路
  • 单根多叶:只有一个 base_link,可以有多个末端
  • 层级传递:父关节的运动会影响所有子关节

举个例子,一个简单的机械臂运动学树:

base_link
 └── joint_1 (revolute)
      └── link_1
           └── joint_2 (revolute)
                └── link_2
                     └── joint_3 (revolute)
                          └── link_3
                               └── joint_4 (fixed)
                                    └── gripper_link

每个 joint 只连接两个 link,每个 link 可以有多个子 joint。这就是树结构的本质。

我的经验:写 URDF 之前,先在纸上画运动学树。画清楚了再写代码。我见过太多人直接上手写,写到一半发现关节顺序错了,回头改 origin 改到崩溃。

3.5 知识结构图

下面这张图把 joint 的核心知识点串起来了。你可以把它当作本章的「思维导图」:

joint 标签知识结构 joint 标签 属性 parent / child origin (xyz / rpy) axis (旋转轴) limit / dynamics 关节类型 revolute(有限旋转) continuous(无限旋转) fixed(固定) prismatic / planar / floating 坐标系关系 parent 坐标系(参考系) origin 定义 child 位姿 axis 在 child 坐标系下 核心:joint 定义了两个 link 之间的坐标系变换 运动学树由 joint 串联 link 形成有向无环结构

3.6 避坑指南

最后,分享几个我实际项目中踩过的坑:

  1. origin 的 rpy 顺序:URDF 的 rpy 是 ZYX 顺序(先绕 Z,再绕 Y,最后绕 X)。如果你用其他顺序算出来的数值,直接填进去会出错。
  2. axis 不是单位向量:ROS 不会报错,但关节运动会异常。写完后用 check_urdf 工具检查一下。
  3. fixed 关节的 origin 写错:fixed 关节虽然不动,但它的 origin 决定了子 link 的初始位姿。写错了,整个子树的姿态都歪了。
  4. 运动学树有环:URDF 不允许循环依赖。比如 A 是 B 的父级,B 是 C 的父级,C 不能又是 A 的父级。ROS 会直接崩溃。
我曾经遇到一个最隐蔽的 bug:一个六轴机械臂,前五个关节都正常,第六个关节一转,末端执行器画出一个奇怪的椭圆。查了两天,发现是第五个关节的 axis 写成了 0.707 0.707 0,不是单位向量。从那以后,我每次写完 URDF 都会用 urdf_to_graphiz 生成运动学树图,肉眼检查一遍。