机器人描述文件 xacro(urdf 扩展)
- 2025-06-15 四川
本文字数:9321 字
阅读完需:约 31 分钟

一、定义
通俗来说,xacro 就是 urdf 文件的一种“进阶版”,它是用来简化和优化机器人的描述文件,使得多个机器人可以共享同样的部件和结构,避免重复编写相同的代码。
假设你有很多机器人,它们的结构相似,比如都有轮子、传感器、臂部等部件。每次你需要为这些机器人写 urdf 文件时,都要重复描述每个部件(比如轮子、关节、传感器等)。这样写非常繁琐且容易出错。
xacro 的作用 :就是通过封装和参数化这些部件,使得你可以更方便地重用已有的结构,只需要少量的修改就可以生成不同机器人的 urdf 文件。类似 C 语言中函数的封装。
二、xacro
文件常见组成部分
1. 命名空间声明
该命名空间的定义使得文件中的 xacro
特性能够正常工作,让你在模型中使用宏和其他 xacro
相关的功能。
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="robot">
2. 定义宏
宏用于封装可复用的结构,例如机器人关节、链接等。你可以定义一个宏并在多个地方调用它,宏可以带有参数,这样就可以创建不同配置的部件。
<xacro:macro name="wheel" params="name x_offset y_offset">
<!-- 定义轮子的 link 和 joint -->
</xacro:macro>
<xacro:macro name="wheel" params="name x_offset y_offset">
<!-- 定义轮子的link和joint -->
</xacro:macro>
3. 调用宏
宏定义完成后,可以在 xacro
文件的任意位置调用宏来生成相应的部件。调用时,可以传递参数值来定制生成的部件。
<xacro:wheel name="right_wheel" x_offset="0.1" y_offset="-0.09"/>
<xacro:wheel name="left_wheel" x_offset="0.1" y_offset="0.09"/>
4. 定义参数
你可以在 xacro 文件中定义变量(参数),这些变量可以在宏中使用,也可以传递给其它部分。这使得配置变得更加灵活和动态。
在这个例子中,wheel_radius
是一个参数,表示轮子的半径。在后续的代码中,可以直接使用这个参数。
<xacro:property name="wheel_radius" value="0.025"/>
5. 条件语句
xacro
支持条件语句,你可以根据不同的条件生成不同的机器人模型或某些部件。例如,如果你希望某个部件只有在某个参数为 true
时才创建,你可以使用 xacro:if
和 xacro:else
。
<xacro:if value="${robot_type == 'mobile'}">
<!-- 如果是移动机器人,添加特定部件 -->
</xacro:if>
6. 转换 xacro 文件为 urdf
你可以使用 ROS 提供的工具来将 xacro
文件转换为 URDF 文件,生成的 URDF 文件可以直接用于机器人仿真、控制等。
rosrun xacro xacro mybot.xacro > mybot.urdf
7. gazebo 标签
在 XACRO 文件中,<gazebo> 标签用于定义与 Gazebo 仿真环境 相关的设置和插件。Gazebo 是一个强大的仿真工具,用于模拟机器人、传感器以及与环境的交互。当你在 XACRO 中使用 gazebo 标签时,通常是为了控制仿真中的物理属性、传感器和控制插件。
三、代码示例
1. gazebo 标签使用(仿真参数配置)
功能:用于描述一些物体以及传感器在 gazebo 仿真中的一些数据,例如摩擦系数、速度等等。
mybot_gazebo.xacro
<?xml version="1.0"?>
<robot name="mybot" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- 定义了3个参数:激光、相机和IMU的可视化开关 -->
<xacro:arg name="laser_visual" default="false"/>
<xacro:arg name="camera_visual" default="false"/>
<xacro:arg name="imu_visual" default="false"/>
<!-- Gazebo仿真环境中为 base_link 设置物理材质 -->
<gazebo reference="base_link">
<material>Gazebo/DarkGrey</material> <!-- 使用 DarkGrey 材质 -->
</gazebo>
<!-- 为左轮设置物理属性,包括摩擦系数、刚度等 -->
<gazebo reference="left_wheel_link">
<mu1>0.5</mu1> <!-- 摩擦系数 -->
<mu2>0.5</mu2> <!-- 摩擦系数 -->
<kp>500000.0</kp> <!-- 刚度 -->
<kd>10.0</kd> <!-- 阻尼 -->
<minDepth>0.001</minDepth> <!-- 最小深度 -->
<maxVel>1.0</maxVel> <!-- 最大速度 -->
<fdir1>1 0 0</fdir1> <!-- 摩擦方向 -->
<material>Gazebo/DarkGrey</material> <!-- 使用 DarkGrey 材质 -->
</gazebo>
<!-- 为右轮设置物理属性 -->
<gazebo reference="right_wheel_link">
<mu1>0.5</mu1> <!-- 摩擦系数 -->
<mu2>0.5</mu2> <!-- 摩擦系数 -->
<kp>500000.0</kp> <!-- 刚度 -->
<kd>10.0</kd> <!-- 阻尼 -->
<minDepth>0.001</minDepth> <!-- 最小深度 -->
<maxVel>1.0</maxVel> <!-- 最大速度 -->
<fdir1>1 0 0</fdir1> <!-- 摩擦方向 -->
<material>Gazebo/FlatBlack</material> <!-- 使用 FlatBlack 材质 -->
</gazebo>
<!-- 为球形支撑轮设置物理属性 -->
<gazebo reference="ball_wheel_link">
<mu1>0.1</mu1> <!-- 摩擦系数 -->
<mu2>0.1</mu2> <!-- 摩擦系数 -->
<kp>500000.0</kp> <!-- 刚度 -->
<kd>100.0</kd> <!-- 阻尼 -->
<minDepth>0.001</minDepth> <!-- 最小深度 -->
<maxVel>1.0</maxVel> <!-- 最大速度 -->
<material>Gazebo/FlatBlack</material> <!-- 使用 FlatBlack 材质 -->
</gazebo>
<!-- 定义 IMU 传感器的Gazebo设置 -->
<gazebo reference="imu">
<sensor type="imu" name="imu">
<always_on>true</always_on> <!-- IMU 始终开启 -->
<visualize>$(arg imu_visual)</visualize> <!-- 根据 imu_visual 参数来显示/隐藏 IMU -->
</sensor>
<material>Gazebo/FlatBlack</material> <!-- 使用 FlatBlack 材质 -->
</gazebo>
<!-- 定义机器人控制插件,用于ROS控制 -->
<gazebo>
<plugin name="mybot_controller" filename="libgazebo_ros_diff_drive.so">
<commandTopic>cmd_vel</commandTopic> <!-- 控制命令话题 -->
<odometryTopic>odom</odometryTopic> <!-- 里程计话题 -->
<odometryFrame>odom</odometryFrame> <!-- 里程计坐标系 -->
<odometrySource>world</odometrySource> <!-- 里程计数据来源 -->
<publishOdomTF>true</publishOdomTF> <!-- 是否发布里程计TF -->
<robotBaseFrame>base_footprint</robotBaseFrame> <!-- 机器人基座坐标系 -->
<publishWheelTF>false</publishWheelTF> <!-- 是否发布轮子坐标系 -->
<publishTf>true</publishTf> <!-- 是否发布TF -->
<publishWheelJointState>true</publishWheelJointState> <!-- 是否发布轮子的关节状态 -->
<legacyMode>false</legacyMode> <!-- 是否使用旧版控制模式 -->
<updateRate>30</updateRate> <!-- 控制更新率(30Hz) -->
<leftJoint>left_wheel_joint</leftJoint> <!-- 左轮的关节 -->
<rightJoint>right_wheel_joint</rightJoint> <!-- 右轮的关节 -->
<wheelSeparation>0.180</wheelSeparation> <!-- 轮子间距 -->
<wheelDiameter>0.05</wheelDiameter> <!-- 轮子直径 -->
<wheelAcceleration>10</wheelAcceleration> <!-- 轮子的加速度 -->
<wheelTorque>100</wheelTorque> <!-- 轮子的最大扭矩 -->
<rosDebugLevel>na</rosDebugLevel> <!-- 不使用ROS调试 -->
</plugin>
</gazebo>
<!-- 定义IMU插件 -->
<gazebo>
<plugin name="imu_plugin" filename="libgazebo_ros_imu.so">
<alwaysOn>true</alwaysOn> <!-- 始终开启 -->
<bodyName>imu</bodyName> <!-- 传感器所在的身体名称 -->
<frameName>imu</frameName> <!-- 传感器的框架名称 -->
<topicName>imu</topicName> <!-- 传感器数据发布话题 -->
<serviceName>imu_service</serviceName> <!-- 服务名称 -->
<gaussianNoise>0.0</gaussianNoise> <!-- 高斯噪声 -->
<updateRate>0</updateRate> <!-- 更新频率 -->
<imu>
<noise>
<type>gaussian</type> <!-- 噪声类型:高斯噪声 -->
<rate>
<mean>0.0</mean> <!-- 速率噪声均值 -->
<stddev>2e-4</stddev> <!-- 速率噪声标准差 -->
<bias_mean>0.0000075</bias_mean> <!-- 偏置均值 -->
<bias_stddev>0.0000008</bias_stddev> <!-- 偏置标准差 -->
</rate>
<accel>
<mean>0.0</mean> <!-- 加速度噪声均值 -->
<stddev>1.7e-2</stddev> <!-- 加速度噪声标准差 -->
<bias_mean>0.1</bias_mean> <!-- 加速度偏置均值 -->
<bias_stddev>0.001</bias_stddev> <!-- 加速度偏置标准差 -->
</accel>
</noise>
</imu>
</plugin>
</gazebo>
<!-- 激光传感器设置 -->
<gazebo reference="base_laser_link">
<material>Gazebo/FlatBlack</material> <!-- 使用 FlatBlack 材质 -->
<sensor type="ray" name="rplidar_sensor">
<pose>0 0 0 0 0 0</pose> <!-- 激光传感器的姿态 -->
<visualize>$(arg laser_visual)</visualize> <!-- 根据 laser_visual 参数来显示/隐藏激光传感器 -->
<update_rate>7</update_rate> <!-- 更新频率(7Hz) -->
<ray>
<scan>
<horizontal>
<samples>720</samples> <!-- 扫描样本数 -->
<resolution>0.5</resolution> <!-- 扫描分辨率 -->
<min_angle>0.0</min_angle> <!-- 最小角度 -->
<max_angle>6.28319</max_angle> <!-- 最大角度 -->
</horizontal>
</scan>
<range>
<min>0.120</min> <!-- 最小测距 -->
<max>12.0</max> <!-- 最大测距 -->
<resolution>0.015</resolution> <!-- 距离分辨率 -->
</range>
<noise>
<type>gaussian</type> <!-- 噪声类型:高斯噪声 -->
<mean>0.0</mean> <!-- 噪声均值 -->
<stddev>0.01</stddev> <!-- 噪声标准差 -->
</noise>
</ray>
<!-- 激光控制插件 -->
<plugin name="gazebo_ros_rplidar_controller" filename="libgazebo_ros_laser.so">
<topicName>scan</topicName> <!-- 激光扫描数据话题 -->
<frameName>base_laser_link</frameName> <!-- 激光传感器的坐标框架 -->
</plugin>
</sensor>
</gazebo>
<!-- 摄像头传感器设置 -->
<gazebo reference="base_camera_link">
<sensor type="camera" name="csi Camera">
<always_on>true</always_on> <!-- 摄像头始终开启 -->
<visualize>$(arg camera_visual)</visualize> <!-- 根据 camera_visual 参数来显示/隐藏相机 -->
<camera>
<horizontal_fov>1.085595</horizontal_fov> <!-- 水平视场角 -->
<image>
<width>640</width> <!-- 图像宽度 -->
<height>480</height> <!-- 图像高度 -->
<format>R8G8B8</format> <!-- 图像格式 -->
</image>
<clip>
<near>0.03</near> <!-- 最近剪裁距离 -->
<far>100</far> <!-- 最远剪裁距离 -->
</clip>
</camera>
<!-- 摄像头控制插件 -->
<plugin name="camera_controller" filename="libgazebo_ros_camera.so">
<alwaysOn>true</alwaysOn> <!-- 始终开启 -->
<updateRate>30.0</updateRate> <!-- 更新频率(30Hz) -->
<cameraName>/</cameraName> <!-- 摄像头名称 -->
<frameName>base_camera_link</frameName> <!-- 相机坐标框架 -->
<imageTopicName>image_raw</imageTopicName> <!-- 图像话题 -->
<cameraInfoTopicName>camera_info</cameraInfoTopicName> <!-- 相机信息话题 -->
<hackBaseline>0.07</hackBaseline> <!-- 基线距离 -->
<distortionK1>0.0</distortionK1> <!-- 畸变系数 -->
<distortionK2>0.0</distortionK2>
<distortionK3>0.0</distortionK3>
<distortionT1>0.0</distortionT1>
<distortionT2>0.0</distortionT2>
</plugin>
</sensor>
</gazebo>
</robot>
2. 引用仿真配置并定义机器人模型(结构)
功能:这段代码描述了一个机器人(mybot)的 物理模型和结构,代码通过定义多个链接(link)和关节(joint)来描述机器人的组成部分,包括主体、轮子、支撑轮、激光雷达等。当我们构建不同的机器人模型时,都可以引用之前的仿真配置。
注意:这两个文件中的 <robot name="mybot" xmlns:xacro="http://ros.org/wiki/xacro">
中的 name
字段应该一致。
mybot1.xacro
<?xml version="1.0"?>
<!-- 定义机器人模型文件 -->
<robot name="mybot" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- 引入外部Xacro文件,包含 Gazebo 模拟器的配置 -->
<xacro:include filename="$(find my_package)/xacro/mybot_gazebo.xacro" />
<!-- 定义 base_footprint 链接,通常用于表示机器人的地面接触点 -->
<link name="base_footprint"/>
<!-- 定义机器人基座的固定关节,连接 base_footprint 和 base_link -->
<joint name="base_joint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin rpy="0 0 0" xyz="0 0 0"/> <!-- 关节的位置和方向 -->
</joint>
<!-- 定义 base_link 链接,表示机器人的基座部分 -->
<link name="base_link">
<inertial> <!-- 惯性属性 -->
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 惯性坐标原点 -->
<mass value="0.1"/> <!-- 质量 -->
<inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.001" /> <!-- 惯性矩阵 -->
</inertial>
<visual> <!-- 可视化属性 -->
<geometry>
<box size="0.25 0.16 0.05"/> <!-- 形状为长方体,定义机器人的基座大小 -->
</geometry> <!-- 几何形状 -->
<origin rpy="0 0 0" xyz="0 0 0"/> <!-- 可视化原点 -->
<material name="blue">
<color rgba="0 0 0.8 1"/> <!-- 设置颜色为蓝色 -->
</material>
</visual>
<collision> <!-- 碰撞检测属性 -->
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 碰撞检测的原点 -->
<geometry> <!-- 几何形状 -->
<box size="0.25 0.16 0.05"/> <!-- 碰撞体形状为长方体,大小与可视化相同 -->
</geometry>
</collision>
</link>
<!-- 定义右侧车轮的链接 -->
<link name="right_wheel_link">
<inertial> <!-- 惯性属性 -->
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 惯性坐标原点 -->
<mass value="0.1"/> <!-- 质量 -->
<inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001" /> <!-- 惯性矩阵 -->
</inertial>
<visual> <!-- 可视化属性 -->
<geometry> <!-- 几何形状 -->
<cylinder length="0.02" radius="0.025"/> <!-- 车轮的形状为圆柱 -->
</geometry>
<material name="black">
<color rgba="0 0 0 1"/> <!-- 设置车轮颜色为黑色 -->
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 碰撞检测的原点 -->
<geometry>
<cylinder length="0.02" radius="0.025"/> <!-- 碰撞体为圆柱 -->
</geometry>
</collision>
</link>
<!-- 定义右车轮的旋转关节,允许车轮持续旋转 -->
<joint name="right_wheel_joint" type="continuous">
<axis xyz="0 0 -1"/> <!-- 旋转轴是 Z 轴负方向 -->
<parent link="base_link"/> <!-- 父链接是基座链接 -->
<child link="right_wheel_link"/> <!-- 子链接是右车轮链接 -->
<origin rpy="1.5707 0 0" xyz=" 0.1 -0.09 -0.03"/> <!-- 关节的位置和方向 -->
</joint>
<!-- 定义左侧车轮的链接 -->
<link name="left_wheel_link">
<inertial> <!-- 惯性属性 -->
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 惯性坐标原点 -->
<mass value="0.1"/> <!-- 质量 -->
<inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0001" /> <!-- 惯性矩阵 -->
</inertial>
<visual> <!-- 可视化属性 -->
<geometry> <!-- 几何形状 -->
<cylinder length="0.02" radius="0.025"/> <!-- 车轮的形状为圆柱 -->
</geometry>
<material name="black">
<color rgba="0 0 0 1"/> <!-- 设置车轮颜色为黑色 -->
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.02" radius="0.025"/> <!-- 碰撞体为圆柱 -->
</geometry>
</collision>
</link>
<!-- 定义左车轮的旋转关节 -->
<joint name="left_wheel_joint" type="continuous">
<axis xyz="0 0 -1"/> <!-- 旋转轴是 Z 轴负方向 -->
<parent link="base_link"/> <!-- 父链接是基座链接 -->
<child link="left_wheel_link"/> <!-- 子链接是左车轮链接 -->
<origin rpy="1.5707 0 0" xyz="0.1 0.09 -0.03"/> <!-- 关节的位置和方向 -->
</joint>
<!-- 定义一个球形支撑轮 -->
<link name="ball_wheel_link">
<inertial> <!-- 惯性属性 -->
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 惯性坐标原点 -->
<mass value="0.1"/> <!-- 质量 -->
<inertia ixx="0" ixy="0" ixz="0" iyy="0" iyz="0" izz="0" /> <!-- 惯性矩阵 -->
</inertial>
<visual> <!-- 可视化属性 -->
<geometry> <!-- 几何形状 -->
<sphere radius="0.025"/> <!-- 球形轮子 -->
</geometry>
<material name="black">
<color rgba="0 0 0 1"/> <!-- 设置轮子颜色为黑色 -->
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/> <!-- 碰撞检测的原点 -->
<geometry>
<sphere radius="0.025"/> <!-- 碰撞体为球形 -->
</geometry>
</collision>
</link>
<!-- 定义球形支撑轮的固定关节 -->
<joint name="ball_wheel_joint" type="fixed">
<axis xyz="0 0 1"/> <!-- 旋转轴是 Z 轴 -->
<parent link="base_link"/>
<child link="ball_wheel_link"/>
<origin rpy="0 0 0" xyz="-0.10 0 -0.03"/> <!-- 关节的位置和方向 -->
</joint>
<!-- 定义IMU传感器 -->
<link name="imu">
<visual>
<geometry>
<box size="0.01 0.01 0.01"/> <!-- 小方块表示IMU传感器 -->
</geometry>
<material name="white">
<color rgba="1 1 1 1"/> <!-- 设置颜色为白色 -->
</material>
</visual>
</link>
<!-- 定义IMU传感器的固定关节 -->
<joint name="imu_joint" type="fixed">
<parent link="base_link"/>
<child link="imu"/>
<origin xyz="0.08 0 0.025"/> <!-- 关节的位置和方向 -->
</joint>
<!-- 定义摄像头 -->
<link name="base_camera_link">
<visual> <!-- 可视化属性 -->
<geometry> <!-- 几何形状 -->
<box size="0.02 0.03 0.03"/> <!-- 以方块形式表示摄像头 -->
</geometry>
<material name="white">
<color rgba="1 1 1 1"/> <!-- 设置颜色为白色 -->
</material>
</visual>
</link>
<!-- 定义摄像头的固定关节 -->
<joint name="camera_joint" type="fixed">
<parent link="base_link"/>
<child link="base_camera_link"/>
<origin xyz="0.1 0 0.025"/> <!-- 关节的位置和方向 -->
</joint>
<!-- 定义激光雷达 -->
<link name="base_laser_link">
<visual> <!-- 可视化属性 -->
<geometry> <!-- 几何形状 -->
<cylinder length="0.06" radius="0.04"/> <!-- 激光雷达为圆柱形状 -->
</geometry>
<material name="white">
<color rgba="1 1 1 1"/> <!-- 设置颜色为白色 -->
</material>
</visual>
</link>
<!-- 定义激光雷达的固定关节 -->
<joint name="laser_joint" type="fixed">
<parent link="base_link"/>
<child link="base_laser_link"/>
<origin xyz="0 0.0 0.06"/> <!-- 位置在机器人顶部 -->
</joint>
</robot>
四、加载仿真模型(含传感器的机器人)
1. 编写 launch 文件
最终我们运行的 mybot1.xacro ,里面包含了机器人的模型和仿真配置文件。编写 launch 文件来启动仿真模型。
simulation_robot.launch
<launch>
<!-- 定义机器人模型位置的参数:x_pos、y_pos 和 z_pos,默认值为0 -->
<arg name="x_pos" default="0.0"/> <!-- 机器人在 x 轴上的位置 -->
<arg name="y_pos" default="0.0"/> <!-- 机器人在 y 轴上的位置 -->
<arg name="z_pos" default="0.0"/> <!-- 机器人在 z 轴上的位置 -->
<!-- 设置仿真时间使用真实时间(/use_sim_time 为 true),通常用于 Gazebo 仿真 -->
<param name="/use_sim_time" value="true" />
<!-- 引入 Gazebo 仿真世界启动文件 (gazebo_world.launch),该文件定义了Gazebo世界环境 -->
<include file="$(find my_package)/launch/gazebo_world.launch"/>
<!-- 通过 xacro 文件生成机器人的 URDF 描述,并加载到 ROS 参数服务器中 -->
<param name="robot_description" command="$(find xacro)/xacro --inorder $(find my_package)/xacro/mybot1.xacro" />
<!-- 启动 Gazebo 插件,将机器人模型(URDF)添加到 Gazebo 仿真环境中 -->
<node pkg="gazebo_ros" type="spawn_model" name="spawn_urdf"
args="-urdf -model mybot.xacro -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos) -param robot_description" />
<!-- 启动 robot_state_publisher 节点,用于发布机器人状态(例如各个关节的位置) -->
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
</launch>
注意:

2. 实际效果

版权声明: 本文为 InfoQ 作者【芯动大师】的原创文章。
原文链接:【http://xie.infoq.cn/article/260878fca03750bb3a93571f8】。文章转载请联系作者。


芯动大师
凡事预则立,不预则废! 2022-06-01 加入
某公司芯片AE工程师,嵌入式开发工程师,InfoQ签约作者,阿里云专家博主,华为云·云享专家,51CTO专家博主,腾讯云社区优秀共创官。
评论