ROS系统xacro建模优化rviz显示
本文介绍了使用Xacro优化URDF机器人模型的方法。首先分析了URDF的三个痛点:重复劳动、参数修改困难和缺乏数学运算。然后展示了如何使用Xacro的property定义常量、macro宏定义和文件包含功能,构建包含底盘、轮子、摄像头和激光雷达的小车模型。通过参数化设计和宏封装,实现了代码复用和便捷修改。最后结合Arbotix控制器实现小车运动控制,详细解释了从Xacro文件解析到RViz显示的
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 后:
-
发布速度:你给了一个
0.2 m/s的指令。 -
Arbotix 查表:
-
查
base_width-> 算出左右轮该转多快。 -
查
accel_limit-> 决定花几秒钟加速到这个速度。 -
查
ticks_meter-> 算出这一秒钟车身在地图上移动了几个像素。
-
-
最终效果: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 启动:打开屏幕,此时看到的小车是静止的。
-
你发指令
-
你在终端输入命令(
rostopic pub /cmd_vel),“向前走,速度 1米/秒!”
-
-
Arbotix 开始
-
它是唯一的“大脑”。它收到指令后,查阅
control.yaml(发现是差速车,轮距 0.2米)。 -
算转速:它算出左轮要转多快,右轮要转多快。
-
算位移:它假装跑了 0.1 秒,算出“这一瞬间车身应该向前挪了 10 厘米”。
-
-
Arbotix 广播数据
-
喊关节:“左轮、右轮现在转到 30 度了!”(发布
/joint_states) -
喊位置:“车身现在移动到了坐标 (0.1, 0)!”(发布
/odom和 TF 变换)
-
-
Publisher干活
-
它听到了 Arbotix 喊的“轮子转到 30 度”,立刻结合模型算出来:“哦,那轮子上的螺丝现在应该在空间中的这个位置。”
-
它把这些具体的 3D 姿态广播出去(TF 树)。
-
-
屏幕刷新 (Rviz):
-
Rviz 只会画画的。它不管物理,只听指挥。
-
它收到“车身位置变了” -> 把底盘画到新位置。
-
它收到“轮子角度变了” -> 把轮子画成旋转后的样子。
-
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)