URDF 代码

  • 痛点 1:重复劳动。你的小车有 4 个轮子,除了位置不一样,形状、颜色、大小完全一样。但你在 URDF 里不得不把 <link><joint> 复制粘贴 4 遍。代码又长又难维护。

  • 痛点 2:参数修改难。如果你想把车身改短一点,你需要手动拿计算器算出轮子的新坐标,然后一个个去改 xyz 的值。

  • 痛点 3:没有数学运算。URDF 不支持加减乘除,所有的坐标都必须是算好的死数字。

1:基本语法

属性定义:

宏定义:

文件包含:

目的:建立一个小车,在rviz中显示

完整代码:

<!--
    使用 xacro 优化 URDF 版的小车底盘实现:

    实现思路:
    1.将一些常量、变量封装为 xacro:property
      比如:PI 值、小车底盘半径、离地间距、车轮半径、宽度 ....
    2.使用 宏 封装驱动轮以及支撑轮实现,调用相关宏生成驱动轮与支撑轮

-->
<!-- 根标签,必须声明 xmlns:xacro -->
<robot name="my_base" xmlns:xacro="http://www.ros.org/wiki/xacro">
    <!-- 封装变量、常量 -->
    <xacro:property name="PI" value="3.141"/>
    <!-- 宏:黑色设置 -->
    <material name="black">
        <color rgba="0.0 0.0 0.0 1.0" />
    </material>
    <!-- 底盘属性 -->
    <xacro:property name="base_footprint_radius" value="0.001" /> <!-- base_footprint 半径  -->
    <xacro:property name="base_link_radius" value="0.1" /> <!-- base_link 半径 -->
    <xacro:property name="base_link_length" value="0.08" /> <!-- base_link 长 -->
    <xacro:property name="earth_space" value="0.015" /> <!-- 离地间距 -->

    <!-- 底盘 -->
    <link name="base_footprint">
      <visual>
        <geometry>
          <sphere radius="${base_footprint_radius}" />
        </geometry>
      </visual>
    </link>

    <link name="base_link">
      <visual>
        <geometry>
          <cylinder radius="${base_link_radius}" length="${base_link_length}" />
        </geometry>
        <origin xyz="0 0 0" rpy="0 0 0" />
        <material name="yellow">
          <color rgba="0.5 0.3 0.0 0.5" />
        </material>
      </visual>
    </link>

    <joint name="base_link2base_footprint" type="fixed">
      <parent link="base_footprint" />
      <child link="base_link" />
      <origin xyz="0 0 ${earth_space + base_link_length / 2 }" />
    </joint>

    <!-- 驱动轮 -->
    <!-- 驱动轮属性 -->
    <xacro:property name="wheel_radius" value="0.0325" /><!-- 半径 -->
    <xacro:property name="wheel_length" value="0.015" /><!-- 宽度 -->
    <!-- 驱动轮宏实现 -->
    <xacro:macro name="add_wheels" params="name flag">
      <link name="${name}_wheel">
        <visual>
          <geometry>
            <cylinder radius="${wheel_radius}" length="${wheel_length}" />
          </geometry>
          <origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" />
          <material name="black" />
        </visual>
      </link>

      <joint name="${name}_wheel2base_link" type="continuous">
        <parent link="base_link" />
        <child link="${name}_wheel" />
        <origin xyz="0 ${flag * base_link_radius} ${-(earth_space + base_link_length / 2 - wheel_radius) }" />
        <axis xyz="0 1 0" />
      </joint>

      
    </xacro:macro>
    <xacro:add_wheels name="left" flag="1" />
    <xacro:add_wheels name="right" flag="-1" />
    <!-- 支撑轮 -->
    <!-- 支撑轮属性 -->
    <xacro:property name="support_wheel_radius" value="0.0075" /> <!-- 支撑轮半径 -->

    <!-- 支撑轮宏 -->
    <xacro:macro name="add_support_wheel" params="name flag" >
      <link name="${name}_wheel">
        <visual>
            <geometry>
                <sphere radius="${support_wheel_radius}" />
            </geometry>
            <origin xyz="0 0 0" rpy="0 0 0" />
            <material name="black" />
        </visual>
      </link>

      <joint name="${name}_wheel2base_link" type="continuous">
          <parent link="base_link" />
          <child link="${name}_wheel" />
          <origin xyz="${flag * (base_link_radius - support_wheel_radius)} 0 ${-(base_link_length / 2 + earth_space / 2)}" />
          <axis xyz="1 1 1" />
      </joint>
    </xacro:macro>

    <xacro:add_support_wheel name="front" flag="1" />
    <xacro:add_support_wheel name="back" flag="-1" />

</robot>

先将 xacro 文件解析成 urdf 文件:rosrun xacro xacro xxx.xacro > xxx.urdf

launch文件

<launch>

    <param name="robot_description" textfile="$(find urdf01)/urdf/xacro/demo01_helloworld.urdf" />

    <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />

    <node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" />

    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01)/config/car_stl.rviz" />

</launch>

代码详细解释:

<material name="black">

        <color rgba="0.0 0.0 0.0 1.0" />

    </material>

<material name="black">

定义材质:告诉 ROS 我要创建一个材质。

起名字:name="black" 是这桶油漆的唯一 ID。以后你在做轮子、做摄像头的时候,只要用 black,系统就会自动找到这个配置。

<xacro:add_wheels name="left" flag="1" />
<xacro:add_wheels name="right" flag="-1" />

这一步相当于调用函数,瞬间生成了两个轮子。

add_wheels要跟 <xacro:macro name="add_wheels" params="name flag">写的一样

2:实操

目的:建立一个带激光雷达,摄像头的小车模型,在rivz中显示。

all.xacro 

<!-- 组合小车底盘与摄像头与雷达 -->
<robot name="my_car" xmlns:xacro="http://wiki.ros.org/xacro">
    <xacro:include filename="car.xacro" />
    <xacro:include filename="camera.xacro" />
    <xacro:include filename="laser.xacro" />
</robot>

camera.xacro

<!-- 摄像头相关的 xacro 文件 -->
<robot name="my_camera" xmlns:xacro="http://wiki.ros.org/xacro">
    <!-- 摄像头属性 -->
    <xacro:property name="camera_length" value="0.01" /> <!-- 摄像头长度(x) -->
    <xacro:property name="camera_width" value="0.025" /> <!-- 摄像头宽度(y) -->
    <xacro:property name="camera_height" value="0.025" /> <!-- 摄像头高度(z) -->
    <xacro:property name="camera_x" value="0.08" /> <!-- 摄像头安装的x坐标 -->
    <xacro:property name="camera_y" value="0.0" /> <!-- 摄像头安装的y坐标 -->
    <xacro:property name="camera_z" value="${base_link_length / 2 + camera_height / 2}" /> <!-- 摄像头安装的z坐标:底盘高度 / 2 + 摄像头高度 / 2  -->

    <!-- 摄像头关节以及link -->
    <link name="camera">
        <visual>
            <geometry>
                <box size="${camera_length} ${camera_width} ${camera_height}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="black" />
        </visual>
    </link>

    <joint name="camera2base_link" type="fixed">
        <parent link="base_link" />
        <child link="camera" />
        <origin xyz="${camera_x} ${camera_y} ${camera_z}" />
    </joint>
</robot>

car.xacro

<!--
    使用 xacro 优化 URDF 版的小车底盘实现:

    实现思路:
    1.将一些常量、变量封装为 xacro:property
      比如:PI 值、小车底盘半径、离地间距、车轮半径、宽度 ....
    2.使用 宏 封装驱动轮以及支撑轮实现,调用相关宏生成驱动轮与支撑轮

-->
<!-- 根标签,必须声明 xmlns:xacro -->
<robot name="my_base" xmlns:xacro="http://www.ros.org/wiki/xacro">
    <!-- 封装变量、常量 -->
    <xacro:property name="PI" value="3.141"/>
    <!-- 宏:黑色设置 -->
    <material name="black">
        <color rgba="0.0 0.0 0.0 1.0" />
    </material>
    <!-- 底盘属性 -->
    <xacro:property name="base_footprint_radius" value="0.001" /> <!-- base_footprint 半径  -->
    <xacro:property name="base_link_radius" value="0.1" /> <!-- base_link 半径 -->
    <xacro:property name="base_link_length" value="0.08" /> <!-- base_link 长 -->
    <xacro:property name="earth_space" value="0.015" /> <!-- 离地间距 -->

    <!-- 底盘 -->
    <link name="base_footprint">
      <visual>
        <geometry>
          <sphere radius="${base_footprint_radius}" />
        </geometry>
      </visual>
    </link>

    <link name="base_link">
      <visual>
        <geometry>
          <cylinder radius="${base_link_radius}" length="${base_link_length}" />
        </geometry>
        <origin xyz="0 0 0" rpy="0 0 0" />
        <material name="yellow">
          <color rgba="0.5 0.3 0.0 0.5" />
        </material>
      </visual>
    </link>

    <joint name="base_link2base_footprint" type="fixed">
      <parent link="base_footprint" />
      <child link="base_link" />
      <origin xyz="0 0 ${earth_space + base_link_length / 2 }" />
    </joint>

    <!-- 驱动轮 -->
    <!-- 驱动轮属性 -->
    <xacro:property name="wheel_radius" value="0.0325" /><!-- 半径 -->
    <xacro:property name="wheel_length" value="0.015" /><!-- 宽度 -->
    <!-- 驱动轮宏实现 -->
    <xacro:macro name="add_wheels" params="name flag">
      <link name="${name}_wheel">
        <visual>
          <geometry>
            <cylinder radius="${wheel_radius}" length="${wheel_length}" />
          </geometry>
          <origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" />
          <material name="black" />
        </visual>
      </link>

      <joint name="${name}_wheel2base_link" type="continuous">
        <parent link="base_link" />
        <child link="${name}_wheel" />
        <origin xyz="0 ${flag * base_link_radius} ${-(earth_space + base_link_length / 2 - wheel_radius) }" />
        <axis xyz="0 1 0" />
      </joint>

      
    </xacro:macro>
    <xacro:add_wheels name="left" flag="1" />
    <xacro:add_wheels name="right" flag="-1" />
    <!-- 支撑轮 -->
    <!-- 支撑轮属性 -->
    <xacro:property name="support_wheel_radius" value="0.0075" /> <!-- 支撑轮半径 -->

    <!-- 支撑轮宏 -->
    <xacro:macro name="add_support_wheel" params="name flag" >
      <link name="${name}_wheel">
        <visual>
            <geometry>
                <sphere radius="${support_wheel_radius}" />
            </geometry>
            <origin xyz="0 0 0" rpy="0 0 0" />
            <material name="black" />
        </visual>
      </link>

      <joint name="${name}_wheel2base_link" type="continuous">
          <parent link="base_link" />
          <child link="${name}_wheel" />
          <origin xyz="${flag * (base_link_radius - support_wheel_radius)} 0 ${-(base_link_length / 2 + earth_space / 2)}" />
          <axis xyz="1 1 1" />
      </joint>
    </xacro:macro>

    <xacro:add_support_wheel name="front" flag="1" />
    <xacro:add_support_wheel name="back" flag="-1" />

</robot>

laser.xacro

<!--
    小车底盘添加雷达
-->
<robot name="my_laser" xmlns:xacro="http://wiki.ros.org/xacro">

    <!-- 雷达支架 -->
    <xacro:property name="support_length" value="0.15" /> <!-- 支架长度 -->
    <xacro:property name="support_radius" value="0.01" /> <!-- 支架半径 -->
    <xacro:property name="support_x" value="0.0" /> <!-- 支架安装的x坐标 -->
    <xacro:property name="support_y" value="0.0" /> <!-- 支架安装的y坐标 -->
    <xacro:property name="support_z" value="${base_link_length / 2 + support_length / 2}" /> <!-- 支架安装的z坐标:底盘高度 / 2 + 支架高度 / 2  -->

    <link name="support">
        <visual>
            <geometry>
                <cylinder radius="${support_radius}" length="${support_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="red">
                <color rgba="0.8 0.2 0.0 0.8" />
            </material>
        </visual>
    </link>

    <joint name="support2base_link" type="fixed">
        <parent link="base_link" />
        <child link="support" />
        <origin xyz="${support_x} ${support_y} ${support_z}" />
    </joint>


    <!-- 雷达属性 -->
    <xacro:property name="laser_length" value="0.05" /> <!-- 雷达长度 -->
    <xacro:property name="laser_radius" value="0.03" /> <!-- 雷达半径 -->
    <xacro:property name="laser_x" value="0.0" /> <!-- 雷达安装的x坐标 -->
    <xacro:property name="laser_y" value="0.0" /> <!-- 雷达安装的y坐标 -->
    <xacro:property name="laser_z" value="${support_length / 2 + laser_length / 2}" /> <!-- 雷达安装的z坐标:支架高度 / 2 + 雷达高度 / 2  -->

    <!-- 雷达关节以及link -->
    <link name="laser">
        <visual>
            <geometry>
                <cylinder radius="${laser_radius}" length="${laser_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="black" />
        </visual>
    </link>

    <joint name="laser2support" type="fixed">
        <parent link="support" /> 
        <child link="laser" />
        <origin xyz="${laser_x} ${laser_y} ${laser_z}" />
    </joint>
</robot>

launch文件:

<launch>

    <param name="robot_description" command="$(find xacro)/xacro $(find urdf01)/urdf/xacro/all.xacro" />

    <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />

    <node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" />

    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01)/config/car_stl.rviz" />

</launch>

运行:

roslaunch urdf01 all_car.launch

在camer.xacro里面可以使用car.xacro的定义的变量属性,因为

  • ros看到all文件里 <xacro:include filename="car.xacro" />:于是它先把 car.xacro 里的所有内容(包括 base_link_length 的定义)复制粘贴过来。此时,内存里已经有了 base_link_length 这个变量。

  • 接着它看到 <xacro:include filename="camera.xacro" />:它把摄像头的代码也复制粘贴过来。

  • 当解析器处理到摄像头的代码时,发现需要计算 ${base_link_length / 2 ...}

  • 它会去当前的“内存”里找有没有 base_link_length

  • 找到了!(因为刚刚在第2步已经加载了)。

  • 于是计算成功,生成最终的纯数字 URDF。

运行结果:

3:Arbotix

目的:让小车子rivz中运动起来

安装软件:

sudo apt install ros-noetic-arbotix

增加配置文件control.yaml

# 该文件是控制器配置,一个机器人模型可能有多个控制器,比如: 底盘、机械臂、夹持器(机械手)....
# 因此,根 name 是 controller
controllers: {
   # 单控制器设置
   base_controller: {
          #类型: 差速控制器
       type: diff_controller,
       #参考坐标
       base_frame_id: base_footprint, 
       #两个轮子之间的间距
       base_width: 0.2,
       #控制频率
       ticks_meter: 2000, 
       #PID控制参数,使机器人车轮快速达到预期速度
       Kp: 12, 
       Kd: 12, 
       Ki: 0, 
       Ko: 50, 
       #加速限制
       accel_limit: 1.0 
    }
}

当你配置好这个文件并运行 Launch 后:

  1. 发布速度:你给了一个 0.2 m/s 的指令。

  2. Arbotix 查表:

    • base_width -> 算出左右轮该转多快。

    • accel_limit -> 决定花几秒钟加速到这个速度。

    • ticks_meter -> 算出这一秒钟车身在地图上移动了几个像素。

  3. 最终效果:RViz 里的小车平滑、准确、符合物理规律地动了起来。

launch文件

<launch>

    <param name="robot_description" command="$(find xacro)/xacro $(find urdf01)/urdf/xacro/all.xacro" />

    <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />

    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01)/config/car_stl.rviz" />
<node name="arbotix" pkg="arbotix_python" type="arbotix_driver" output="screen">
     <rosparam file="$(find urdf01)/config/control.yaml" command="load" />
     <param name="sim" value="true" />
</node>

</launch>

<param name="robot_description" ... />

作用:在 ROS 参数服务器中创建一个叫 robot_description 的变量。这是 ROS 的“公用黑板”,所有节点(RViz, 驱动等)启动时都会先看黑板,确认机器人长什么样

command="$(find xacro)/xacro ..."调用 xacro 工具,把你的 all.xacro(包含变量、宏、引用的复杂文件)实时转换成纯净的 URDF XML 代码,然后存到参数服务器里。

<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />

输入:从参数服务器读取机器人模型(车轮在哪、车身多高)。

           从 Arbotix 节点接收 /joint_states(当前车轮转了多少度)。

输出:TF (Transform) 坐标变换树。

<node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf01)/config/car_stl.rviz" />

pkg="rviz" type="rviz":启动 RViz 软件。

args="-d ...":加载配置文件,它会自动加载你之前保存好的设置(比如背景色、视角、已添加的模块),一打开就能用。

pkg="arbotix_python" type="arbotix_driver"

启动 Arbotix 驱动程序。

<rosparam file="..." command="load" />

读取配置:加载 control.yaml 文件。这里面存着你的 PID 参数、轮子间距等数据。

为什么需要它?:Arbotix 需要这些数据来计算:“为了达到 1米/秒 的速度,我的两个电机分别需要转多快?”

<param name="sim" value="true" />

Arbotix 不会去扫描 USB 端口找电机,而是在内部虚拟计算。它会根据你发的速度指令,利用数学公式算出一个“假的”里程计和关节角度,发给 ROS 系统。

发布速度指令 

rostopic  pub -r 10 /cmd_vel geometry_msgs/Twist "linear:
  x: 1.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 1.0" 

运行结果:

逻辑思路:

1:系统把你的 .xacro 文件翻译成标准 .urdf,然后贴在 ROS 的公告栏(参数服务器)上。

2:Arbotix 启动:读取 control.yaml(物理规则),戴上墨镜准备演戏。

3:Robot State Publisher 启动:盯着公告栏的模型数据,准备计算骨骼。

4:Rviz 启动:打开屏幕,此时看到的小车是静止的。

  1. 你发指令

    1. 你在终端输入命令(rostopic pub /cmd_vel),“向前走,速度 1米/秒!

  2. Arbotix 开始

    1. 它是唯一的“大脑”。它收到指令后,查阅 control.yaml(发现是差速车,轮距 0.2米)。

    2. 算转速:它算出左轮要转多快,右轮要转多快。

    3. 算位移:它假装跑了 0.1 秒,算出“这一瞬间车身应该向前挪了 10 厘米”。

  3. Arbotix 广播数据

    1. 喊关节:“左轮、右轮现在转到 30 度了!”(发布 /joint_states)       

    2. 喊位置:“车身现在移动到了坐标 (0.1, 0)!”(发布 /odom 和 TF 变换)

  4. Publisher干活 

    • 它听到了 Arbotix 喊的“轮子转到 30 度”,立刻结合模型算出来:“哦,那轮子上的螺丝现在应该在空间中的这个位置。”

    • 它把这些具体的 3D 姿态广播出去(TF 树)。

  5. 屏幕刷新 (Rviz)

    • Rviz 只会画画的。它不管物理,只听指挥。

    • 它收到“车身位置变了” -> 把底盘画到新位置

    • 它收到“轮子角度变了” -> 把轮子画成旋转后的样子

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐