3. 机器人建模与URDF:URDF语法详解、link与joint配置、使用xacro优化模型、在Rviz中显示机器人模型

好,咱们进入第三章。这一章可以说是整个底盘驱动开发的「地基」——机器人建模。你想想看,没有模型,你让机器人怎么知道自己长什么样?胳膊多长、轮子多大、传感器装在哪?这些信息,全得靠URDF来描述。

我个人习惯把URDF比作机器人的「身份证」。它用XML格式,把机器人的每一个零件(link)和每一个关节(joint)都交代得清清楚楚。今天咱们就把这张身份证的写法彻底搞明白。

3.1 URDF语法详解

URDF的全称是Unified Robot Description Format,统一机器人描述格式。说白了,就是一个纯文本文件,里面用标签来定义机器人的结构。

它的核心就两个东西:linkjoint。link是刚体部件,joint是连接两个link的关节。我刚开始学的时候,总把这两个搞混。后来想了个笨办法:link就是骨头,joint就是关节。骨头不会动,关节才能动。

3.1.1 link标签

每个link代表一个刚体部件。比如底盘、轮子、激光雷达支架,这些都是link。一个link标签里,主要包含三部分信息:

  • 视觉属性(visual):模型长什么样,用什么几何体,什么颜色
  • 碰撞属性(collision):用于物理碰撞检测的简化模型
  • 惯性属性(inertial):质量、质心、惯性张量,用于动力学仿真

来看一个最简单的link例子:

<link name="base_link">
  <visual>
    <geometry>
      <cylinder length="0.15" radius="0.10"/>
    </geometry>
    <material name="blue">
      <color rgba="0 0 0.8 1"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <cylinder length="0.15" radius="0.10"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="2.0"/>
    <inertia ixx="0.01" ixy="0" ixz="0"
             iyy="0.01" iyz="0"
             izz="0.02"/>
  </inertial>
</link>
我的小技巧: 碰撞模型尽量用简单几何体。圆柱、长方体、球体就够了。我在项目里见过有人把碰撞模型做得跟视觉模型一样精细,结果仿真卡成PPT。记住,碰撞模型只用来算碰撞,越简单越快。

3.1.2 joint标签

joint连接两个link,定义了它们之间的相对运动关系。joint有六种类型,但咱们做移动机器人底盘,常用的就两种:

类型 说明 自由度 典型应用
fixed 固定关节,两个link之间没有相对运动 0 传感器支架、外壳
continuous 连续旋转关节,没有角度限制 1 轮子、转台
revolute 有限旋转关节,有角度范围限制 1 机械臂关节
prismatic 平移关节,沿一个轴滑动 1 升降机构
planar 平面关节,在平面内移动和旋转 3 全向轮底盘(较少用)
floating 浮动关节,6自由度全自由 6 无人机、水下机器人

写joint的时候,最关键的是搞清楚originaxis。origin定义了子link相对于父link的位置和姿态。axis定义了关节的旋转轴或平移轴。

来看一个轮子joint的例子:

<joint name="left_wheel_joint" type="continuous">
  <parent link="base_link"/>
  <child link="left_wheel_link"/>
  <origin xyz="0.15 0.12 -0.05" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>
  <limit effort="10" velocity="5"/>
</joint>
我曾经踩过的坑: 写joint时,origin的rpy(翻滚-俯仰-偏航)角度特别容易搞反。ROS里用的是右手坐标系,绕X轴转是roll,绕Y轴转是pitch,绕Z轴转是yaw。我有一回把轮子装反了,模型在Rviz里看着是正的,一仿真轮子直接往天上转。查了半天才发现是rpy写成了"0 0 1.57",应该是"1.57 0 0"。

3.2 使用xacro优化模型

URDF写多了你就会发现,很多重复的内容。比如四个轮子,每个轮子的link和joint结构几乎一样,只是位置不同。这时候,xacro就派上用场了。

xacro是XML Macros的缩写,说白了就是给URDF加了个「宏」的功能。你可以定义变量、写函数、做数学运算。我个人觉得,xacro是URDF建模的「生产力工具」。

3.2.1 定义属性

用xacro,你可以把尺寸、颜色、质量这些参数提取出来,统一管理:

<?xml version="1.0"?>
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <xacro:property name="wheel_radius" value="0.05"/>
  <xacro:property name="wheel_width" value="0.03"/>
  <xacro:property name="base_length" value="0.30"/>
  <xacro:property name="base_width" value="0.20"/>
  <xacro:property name="base_height" value="0.10"/>
  <xacro:property name="material_blue" value="0 0 0.8 1"/>

这样改参数的时候,只需要改最上面几行,不用满文件找数字。我在做项目时,经常要调轮距、轴距这些参数,用xacro属性改起来特别方便。

3.2.2 定义宏

宏是xacro最强大的功能。你可以把重复的结构封装起来,像函数一样调用:

<xacro:macro name="wheel" params="prefix x y">
  <link name="${prefix}_wheel_link">
    <visual>
      <geometry>
        <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
      </geometry>
      <material name="gray">
        <color rgba="0.5 0.5 0.5 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
      </geometry>
    </collision>
  </link>

  <joint name="${prefix}_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="${prefix}_wheel_link"/>
    <origin xyz="${x} ${y} -${base_height/2 - wheel_radius}" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>
</xacro:macro>

然后调用的时候,一行就搞定一个轮子:

<xacro:wheel prefix="left_front" x="0.12" y="0.10"/>
<xacro:wheel prefix="right_front" x="0.12" y="-0.10"/>
<xacro:wheel prefix="left_back" x="-0.12" y="0.10"/>
<xacro:wheel prefix="right_back" x="-0.12" y="-0.10"/>
为什么我推荐用xacro? 你想想看,如果不用宏,四个轮子你得写四遍link和joint,加起来至少80行代码。用宏之后,20行搞定。而且改轮子半径的时候,改一个属性值就行,不用改四个地方。这就是工程效率。

3.3 在Rviz中显示机器人模型

模型写好了,怎么看到它?Rviz是ROS里最常用的可视化工具。要在Rviz里显示你的机器人模型,需要做两件事:

  1. 启动一个发布机器人状态的节点(robot_state_publisher)
  2. 在Rviz中添加RobotModel显示插件

3.3.1 创建launch文件

我习惯写一个launch文件,一键启动所有东西:

<launch>
  <!-- 加载URDF模型 -->
  <param name="robot_description" 
         command="$(find xacro)/xacro '$(find my_robot_description)/urdf/my_robot.xacro'" />

  <!-- 启动robot_state_publisher -->
  <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher">
    <param name="publish_frequency" value="50.0"/>
  </node>

  <!-- 启动joint_state_publisher(用于手动调节关节) -->
  <node name="joint_state_publisher_gui" pkg="joint_state_publisher_gui" 
        type="joint_state_publisher_gui" />

  <!-- 启动Rviz -->
  <node name="rviz" pkg="rviz" type="rviz" 
        args="-d $(find my_robot_description)/config/display.rviz"/>
</launch>
一个小细节: robot_state_publisher的publish_frequency我一般设50Hz。设太低,模型在Rviz里看着会卡顿。设太高,CPU占用会上去。50Hz是个不错的平衡点。

3.3.2 在Rviz中配置显示

启动launch文件后,Rviz会打开。你需要手动添加RobotModel显示:

  • 点击左下角的「Add」按钮
  • 在列表中找到「RobotModel」
  • 在「Description Topic」中选择「/robot_description」

如果一切正常,你的机器人模型就会出现在Rviz的3D视图中。你可以用鼠标拖拽旋转视角,从各个角度观察模型。

我曾经遇到的问题: 模型在Rviz里显示不出来,最常见的原因是TF树没连上。你可以在Rviz中打开「TF」显示,看看各个link之间的坐标变换是否正常。如果某个joint的parent和child之间没有TF变换,那肯定是joint的origin或者axis写错了。我有一回排查了半小时,最后发现是joint的parent写成了另一个link的名字。

3.4 实战:搭建一个四轮差速底盘模型

理论说完了,咱们来点实际的。下面是一个完整的四轮差速底盘xacro文件的核心结构:

<?xml version="1.0"?>
<robot name="diff_drive_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <xacro:property name="base_length" value="0.40"/>
  <xacro:property name="base_width" value="0.30"/>
  <xacro:property name="base_height" value="0.12"/>
  <xacro:property name="wheel_radius" value="0.06"/>
  <xacro:property name="wheel_width" value="0.04"/>

  <!-- 底盘link -->
  <link name="base_link">
    <visual>
      <geometry>
        <box size="${base_length} ${base_width} ${base_height}"/>
      </geometry>
      <material name="blue">
        <color rgba="0.2 0.2 0.8 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <box size="${base_length} ${base_width} ${base_height}"/>
      </geometry>
    </collision>
  </link>

  <!-- 轮子宏定义 -->
  <xacro:macro name="wheel" params="prefix side x y">
    <link name="${prefix}_wheel">
      <visual>
        <geometry>
          <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
        </geometry>
        <material name="gray">
          <color rgba="0.5 0.5 0.5 1"/>
        </material>
      </visual>
      <collision>
        <geometry>
          <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
        </geometry>
      </collision>
    </link>

    <joint name="${prefix}_wheel_joint" type="continuous">
      <parent link="base_link"/>
      <child link="${prefix}_wheel"/>
      <origin xyz="${x} ${y} -${base_height/2}" rpy="0 0 0"/>
      <axis xyz="0 1 0"/>
    </joint>
  </xacro:macro>

  <!-- 四个轮子 -->
  <xacro:wheel prefix="left_front" side="left" 
               x="${base_length/2 - 0.05}" y="${base_width/2 + 0.02}"/>
  <xacro:wheel prefix="right_front" side="right" 
               x="${base_length/2 - 0.05}" y="-${base_width/2 + 0.02}"/>
  <xacro:wheel prefix="left_back" side="left" 
               x="-${base_length/2 + 0.05}" y="${base_width/2 + 0.02}"/>
  <xacro:wheel prefix="right_back" side="right" 
               x="-${base_length/2 + 0.05}" y="-${base_width/2 + 0.02}"/>
</robot>

这个模型虽然简单,但已经包含了差速底盘的核心结构。你可以在Rviz里看到它,并且通过joint_state_publisher_gui手动转动轮子,观察它们是否按预期旋转。

嗯,这一章的内容就到这。URDF建模看起来是体力活,但掌握了xacro之后,你会发现它其实很有规律。下一章咱们要聊的是「机器人运动学模型」,到时候你会用到今天建的底盘模型来做运动学解算。所以,今天的模型一定要跑通,别偷懒。


公众号:蓝海资料掘金营,微信deep3321