4. 创建第一个URDF模型:从零编写一个简单的二连杆机械臂URDF
好,咱们进入正题。上一章我们把ROS 2和MoveIt的环境都折腾好了,这一章,我们来亲手造一个机器人——准确地说,是造它的“骨架”。
URDF,全称是Unified Robot Description Format,统一机器人描述格式。说白了,它就是用一个XML文件,告诉计算机你的机器人长什么样、关节在哪、能怎么动。我刚开始学的时候,觉得这东西不就是个配置文件嘛,有啥难的?结果自己动手写的时候,连杆长度写反了,关节轴方向搞错了,折腾了一下午才让一个方块在Rviz里转起来。
嗯,咱们今天就从零开始,写一个最简单的二连杆机械臂。别小看它,麻雀虽小五脏俱全。你把这个搞明白了,后面加再多连杆也只是复制粘贴的体力活。
4.1 URDF的核心概念:连杆与关节
URDF里就两个核心元素:link(连杆)和joint(关节)。
- link:描述机器人的一个刚体部分。比如一个基座、一个臂杆、一个夹爪。它定义了形状、尺寸、颜色、惯性参数等。
- joint:描述两个link之间的连接关系。它定义了关节类型(旋转、滑动、固定等)、父连杆、子连杆、原点位置、旋转轴方向。
你可以把link想象成骨头,joint想象成关节。骨头本身不会动,是关节让骨头动起来的。我在项目里见过有人把joint的父子关系搞反了,结果机器人一启动,基座绕着末端执行器转——那画面,简直像在跳机械舞。
关键原则:URDF是一个树形结构。每个link只能有一个父joint,但可以有多个子joint。根节点通常是base_link(基座)。
4.2 二连杆机械臂的结构设计
咱们要做的二连杆,结构很简单:
- 一个固定的基座(base_link)
- 一个绕Z轴旋转的腰关节(joint1),连接基座和第一段臂杆(link1)
- 一个绕Y轴旋转的肘关节(joint2),连接第一段臂杆和第二段臂杆(link2)
说白了,就是一个能转腰、能抬胳膊的简化版机械臂。每个连杆长度我习惯设成0.5米,这样在Rviz里看起来比例舒服。
下面这张图,是我画的这个二连杆的结构示意:
4.3 手写URDF文件
好,现在咱们打开编辑器,创建一个叫 two_link_arm.urdf 的文件。我个人习惯把URDF文件放在机器人功能包里的 urdf/ 目录下,这样好管理。
先写根节点和基座:
<?xml version="1.0"?>
<robot name="two_link_arm">
<!-- 基座:固定在地面上的一个方块 -->
<link name="base_link">
<visual>
<geometry>
<box size="0.2 0.2 0.1"/>
</geometry>
<origin rpy="0 0 0" xyz="0 0 0"/>
<material name="blue">
<color rgba="0.2 0.4 0.8 1.0"/>
</material>
</visual>
</link>
这里要注意,origin 里的 rpy 是绕固定轴的旋转角(roll, pitch, yaw),xyz 是位置偏移。基座我放在原点,所以都是0。
接下来是第一个关节和第一段连杆:
<!-- 腰关节:绕Z轴旋转 -->
<joint name="joint1" type="revolute">
<parent link="base_link"/>
<child link="link1"/>
<origin rpy="0 0 0" xyz="0 0 0.1"/>
<axis xyz="0 0 1"/>
<limit lower="-3.14" upper="3.14" effort="10" velocity="1.0"/>
</joint>
<link name="link1">
<visual>
<geometry>
<cylinder length="0.5" radius="0.05"/>
</geometry>
<origin rpy="0 0 0" xyz="0 0.25 0"/>
<material name="red">
<color rgba="0.8 0.2 0.2 1.0"/>
</material>
</visual>
</link>
嗯,这里有个坑。你看 joint1 的 origin 的 xyz 是 0 0 0.1,意思是关节在基座上方0.1米处。而 link1 的 origin 的 xyz 是 0 0.25 0,这个是在link1自身坐标系下,圆柱的中心往上偏移0.25米——因为圆柱默认中心在几何中心,我要让圆柱从关节位置往上长,所以偏移半个长度。
我曾经在这个偏移量上栽过跟头。当时想当然地以为 origin 是相对于父连杆的,结果圆柱直接穿模了。记住:link的origin是相对于link自身坐标系的,joint的origin才是相对于父连杆的。
接着写肘关节和第二段连杆:
<!-- 肘关节:绕Y轴旋转 -->
<joint name="joint2" type="revolute">
<parent link="link1"/>
<child link="link2"/>
<origin rpy="0 0 0" xyz="0 0.5 0"/>
<axis xyz="0 1 0"/>
<limit lower="-2.0" upper="2.0" effort="10" velocity="1.0"/>
</joint>
<link name="link2">
<visual>
<geometry>
<cylinder length="0.5" radius="0.04"/>
</geometry>
<origin rpy="0 0 0" xyz="0 0.25 0"/>
<material name="green">
<color rgba="0.2 0.8 0.2 1.0"/>
</material>
</visual>
</link>
</robot>
注意 joint2 的 origin 的 xyz 是 0 0.5 0,因为link1的长度是0.5米,所以肘关节正好在link1的顶端。关节类型 revolute 表示旋转关节,limit 里定义了角度范围(弧度)、最大力矩和最大速度。
小技巧:如果你不确定关节轴的方向,可以用右手定则。拇指指向轴的正方向,四指弯曲的方向就是正旋转方向。我每次写URDF都会在脑子里过一遍这个,防止轴方向搞反。
4.4 使用Rviz2可视化验证
URDF写完了,怎么知道对不对?别急着用MoveIt,先用Rviz2看一眼。ROS 2里有个叫 robot_state_publisher 的节点,它负责读取URDF并发布TF变换。我们还需要一个 joint_state_publisher_gui 来手动拖动关节。
先创建一个启动文件,我习惯叫 display.launch.py:
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
output='screen',
arguments=['two_link_arm.urdf']
),
Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
name='joint_state_publisher_gui',
output='screen'
),
Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen'
)
])
然后运行:
ros2 launch display.launch.py
Rviz2启动后,记得在左侧的Displays面板里:
- 把Fixed Frame设为
base_link - 点击左下角的Add按钮,添加RobotModel显示
- 再添加一个TF显示,看看坐标系对不对
你应该能看到一个蓝色的基座,上面立着一根红色的圆柱(link1),再上面是一根绿色的圆柱(link2)。拖动GUI界面上的滑块,关节应该能顺畅旋转。
常见问题:如果Rviz里看不到模型,先检查终端有没有报错。最常见的是URDF路径不对,或者joint的父子关系写错了。我曾经因为少写了一个斜杠,让robot_state_publisher找了半天文件没找到。
4.5 验证模型是否正确
光看还不够,咱们用命令行检查一下:
# 检查URDF语法
check_urdf two_link_arm.urdf
# 查看TF树
ros2 run tf2_tools view_frames.py
check_urdf 会输出模型的拓扑结构,告诉你哪个关节连哪个连杆。如果输出里没有报错,说明语法没问题。view_frames.py 会生成一个PDF,展示TF树的父子关系——这个在调试多连杆时特别有用。
我个人习惯在写完URDF后,先用 check_urdf 扫一遍,再在Rviz里手动拖拽关节,看看运动学对不对。比如joint1旋转时,link1和link2应该一起转;joint2旋转时,只有link2动。如果link1也跟着link2动了,那肯定是joint2的父子关系写反了。
4.6 本章小结
好,咱们来捋一捋今天干了啥:
- 理解了URDF里link和joint的关系——骨头和关节
- 手写了一个二连杆机械臂的URDF,包含基座、腰关节、肘关节
- 用Rviz2和robot_state_publisher做了可视化验证
- 学会了用命令行工具检查URDF的正确性
这个二连杆虽然简单,但它是你迈向复杂机器人的第一步。后面我们要加夹爪、加传感器、加MoveIt的配置,都是在这个基础上堆叠。你想想看,如果连两个连杆都搞不定,后面六个轴的工业机械臂岂不是要抓瞎?
嗯,下一章我们会在这个URDF里加入碰撞检测和惯性参数,让模型更接近真实机器人。但今天先到这里,去喝杯咖啡,让脑子歇一歇。