3、创建机器人模型:使用URDF/SDF描述机器人、添加差速驱动插件、添加激光雷达传感器
好,我们进入第三章。这一章,我们要亲手搭建一个能在Gazebo里跑起来的机器人模型。
说白了,就是给机器人画个骨架,装上轮子和眼睛。嗯,这里要注意,模型的质量直接决定了后续导航调试的体验。我在项目中见过太多人因为模型参数写错,折腾了一整天。
3.1 用URDF还是SDF?我的选择
ROS2里描述机器人,主要有两种格式:URDF和SDF。很多人纠结选哪个。
我个人习惯:在Nav2项目里用URDF。为什么?因为URDF和ROS2的生态结合得更紧密。你想想看,robot_state_publisher、joint_state_publisher这些工具,原生就支持URDF。SDF虽然功能更强,但配置起来稍显繁琐。
当然,如果你用Gazebo Classic,SDF是原生格式。但我们现在讲的是Gazebo Ignition(现在叫Gazebo Garden/Harmony),它俩都支持。我建议你从URDF入手,等熟练了再尝试SDF。
3.2 搭建机器人骨架
我们先画一个两轮差速机器人。结构很简单:一个底盘、两个驱动轮、一个万向轮,再加一个激光雷达支架。
来看代码。我习惯把每个部件拆成独立的xacro文件,最后用include组合起来。
<!-- robot_core.xacro -->
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_nav2_robot">
<!-- 底盘 -->
<link name="base_footprint">
<visual>
<geometry>
<box size="0.01 0.01 0.01"/>
</geometry>
</visual>
</link>
<joint name="base_joint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0 0 0.05"/>
</joint>
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.15" radius="0.20"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.2 0.8 1.0"/>
</material>
</visual>
<collision>
<geometry>
<cylinder length="0.15" radius="0.20"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.02" ixy="0.0" ixz="0.0"
iyy="0.02" iyz="0.0"
izz="0.03"/>
</inertial>
</link>
<!-- 左轮 -->
<link name="left_wheel_link">
<visual>
<geometry>
<cylinder length="0.04" radius="0.05"/>
</geometry>
<material name="black"/>
</visual>
<collision>
<geometry>
<cylinder length="0.04" radius="0.05"/>
</geometry>
</collision>
<inertial>
<mass value="0.2"/>
<inertia ixx="0.0001" ixy="0.0" ixz="0.0"
iyy="0.0001" iyz="0.0"
izz="0.0002"/>
</inertial>
</link>
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel_link"/>
<origin xyz="0.0 0.18 -0.05" rpy="1.5708 0 0"/>
<axis xyz="0 0 1"/>
</joint>
<!-- 右轮(对称) -->
<link name="right_wheel_link">
<!-- 内容与左轮类似,位置在 xyz="0.0 -0.18 -0.05" -->
</link>
<joint name="right_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="right_wheel_link"/>
<origin xyz="0.0 -0.18 -0.05" rpy="1.5708 0 0"/>
<axis xyz="0 0 1"/>
</joint>
<!-- 万向轮 -->
<link name="caster_wheel_link">
<visual>
<geometry>
<sphere radius="0.03"/>
</geometry>
</visual>
</link>
<joint name="caster_wheel_joint" type="fixed">
<parent link="base_link"/>
<child link="caster_wheel_link"/>
<origin xyz="-0.15 0.0 -0.08"/>
</joint>
</robot>
这段代码里,我特别强调一下惯性参数。很多人图省事随便填,结果仿真里机器人像纸片一样飘。我曾经踩过这个坑,仿真跑得好好的,一换真实机器人就抖得不行。后来发现是惯性矩阵算错了。
3.3 添加差速驱动插件
模型画好了,怎么让它动起来?我们需要给Gazebo一个驱动插件。差速驱动是最常见的底盘形式。
在URDF里添加插件,用<gazebo>标签。看下面这个例子:
<gazebo>
<plugin name="diff_drive_controller" filename="libgazebo_ros_diff_drive.so">
<ros>
<namespace>/</namespace>
<argument>diff_drive_controller</argument>
</ros>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.36</wheel_separation>
<wheel_diameter>0.10</wheel_diameter>
<max_wheel_torque>20</max_wheel_torque>
<max_wheel_acceleration>10.0</max_wheel_acceleration>
<command_topic>cmd_vel</command_topic>
<odometry_topic>odom</odometry_topic>
<odometry_frame>odom</odometry_frame>
<robot_base_frame>base_footprint</robot_base_frame>
<publish_odom>true</publish_odom>
<publish_odom_tf>true</publish_odom_tf>
<update_rate>50</update_rate>
</plugin>
</gazebo>
这里有几个参数要特别留意:
| 参数 | 说明 | 我的建议 |
|---|---|---|
| wheel_separation | 左右轮中心距 | 精确测量,误差超过1cm转弯半径就不对 |
| wheel_diameter | 轮子直径 | 和模型保持一致,别写错 |
| max_wheel_torque | 最大扭矩 | 设大一点没关系,仿真里不会烧电机 |
| update_rate | 里程计发布频率 | 50Hz够用,太高了CPU扛不住 |
3.4 添加激光雷达传感器
导航需要感知环境。激光雷达是最常用的传感器。我们给机器人头顶装一个2D激光雷达。
先定义雷达的link和joint:
<link name="laser_link">
<visual>
<geometry>
<cylinder length="0.05" radius="0.03"/>
</geometry>
<material name="red"/>
</visual>
</link>
<joint name="laser_joint" type="fixed">
<parent link="base_link"/>
<child link="laser_link"/>
<origin xyz="0.10 0.0 0.10"/>
</joint>
然后添加Gazebo的雷达插件:
<gazebo reference="laser_link">
<sensor type="ray" name="laser_sensor">
<pose>0 0 0 0 0 0</pose>
<visualize>true</visualize>
<update_rate>10</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1</resolution>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.10</min>
<max>12.0</max>
<resolution>0.01</resolution>
</range>
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
<plugin name="laser_controller" filename="libgazebo_ros_ray_sensor.so">
<ros>
<namespace>/</namespace>
<argument>scan</argument>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
<frame_name>laser_link</frame_name>
</plugin>
</sensor>
</gazebo>
这里我加了噪声模型。真实雷达是有误差的,仿真里加一点高斯噪声,能让你的导航算法更鲁棒。我在项目里测试过,不加噪声的仿真跑得飞起,一上真车就撞墙。加了噪声后,算法在仿真里就能提前适应真实环境。
3.5 本章知识体系
下面这张图,帮你理清本章的核心逻辑:
3.6 验证模型
写完了怎么验证?我一般三步走:
- 检查URDF语法:
check_urdf my_robot.urdf,看有没有报错 - 可视化查看:
urdf_to_graphiz my_robot.urdf,生成结构图,确认父子关系正确 - 在Rviz里预览:启动robot_state_publisher,用Rviz看模型姿态
嗯,这一章的内容就到这里。模型是导航的基础,花时间把它做扎实,后面会省很多事。下一章我们把它放进Gazebo里,看看能不能动起来。