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 是坐标,结果关节一转动,整个子链全跑偏。
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>
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。这就是树结构的本质。
3.5 知识结构图
下面这张图把 joint 的核心知识点串起来了。你可以把它当作本章的「思维导图」:
3.6 避坑指南
最后,分享几个我实际项目中踩过的坑:
- origin 的 rpy 顺序:URDF 的 rpy 是 ZYX 顺序(先绕 Z,再绕 Y,最后绕 X)。如果你用其他顺序算出来的数值,直接填进去会出错。
- axis 不是单位向量:ROS 不会报错,但关节运动会异常。写完后用
check_urdf工具检查一下。 - fixed 关节的 origin 写错:fixed 关节虽然不动,但它的 origin 决定了子 link 的初始位姿。写错了,整个子树的姿态都歪了。
- 运动学树有环:URDF 不允许循环依赖。比如 A 是 B 的父级,B 是 C 的父级,C 不能又是 A 的父级。ROS 会直接崩溃。
0.707 0.707 0,不是单位向量。从那以后,我每次写完 URDF 都会用 urdf_to_graphiz 生成运动学树图,肉眼检查一遍。