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里看起来比例舒服。

下面这张图,是我画的这个二连杆的结构示意:

base_link J1 link1 (0.5m) J2 link2 (0.5m) 末端 二连杆机械臂结构示意 J1: 绕Z轴旋转 (腰关节) J2: 绕Y轴旋转 (肘关节) X Y Z (出纸面)

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面板里:

  1. 把Fixed Frame设为 base_link
  2. 点击左下角的Add按钮,添加RobotModel显示
  3. 再添加一个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里加入碰撞检测和惯性参数,让模型更接近真实机器人。但今天先到这里,去喝杯咖啡,让脑子歇一歇。