4. 机器人模型构建与配置:URDF/SDF、差速驱动、激光雷达与IMU
好,咱们进入正题。这一章要聊的是机器人模型的构建与配置。说白了,就是给你的虚拟机器人「造个身子」,再给它装上「腿」、「眼睛」和「耳朵」。
我个人习惯把这一步叫做「搭积木」。你想想看,一个能跑起来的机器人,至少得有底盘、轮子、传感器。在Nav2的世界里,这些都得用URDF或者SDF描述清楚。
4.1 URDF vs SDF:选哪个?
先解决一个选择困难症的问题。URDF和SDF都能描述机器人,但侧重点不同。
| 特性 | URDF | SDF |
|---|---|---|
| 起源 | ROS原生格式 | Gazebo原生格式 |
| 物理属性 | 需要额外插件 | 内置支持 |
| 多机器人 | 不直接支持 | 原生支持 |
| 复用性 | 通过Xacro宏 | 通过include |
我在项目中遇到过不少新手,一上来就用纯URDF,结果在Gazebo里死活动不了。为什么?因为URDF本身不包含物理引擎的配置,你得手动加插件。而SDF把这些都打包好了。
4.2 构建差速驱动底盘
差速驱动,说白了就是两个主动轮加一个万向轮。控制逻辑很简单:左右轮速度差决定转向。但描述起来,有几个坑得注意。
先看一个标准的差速底盘URDF片段:
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<link name="base_footprint">
<visual>
<geometry>
<box size="0.001 0.001 0.001"/>
</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.2" radius="0.15"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder length="0.2" radius="0.15"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>
</robot>
嗯,这里要注意:base_footprint 是一个虚拟坐标系,它代表机器人在地面的投影点。我见过有人直接拿 base_link 当原点,结果里程计数据全乱了。
4.3 添加差速驱动插件
光有模型还不够,你得告诉Gazebo怎么驱动它。差速驱动插件就是干这个的。
<gazebo>
<plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so">
<ros>
<namespace>/</namespace>
<argument>cmd_vel:=cmd_vel</argument>
<argument>odom:=odom</argument>
</ros>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.4</wheel_separation>
<wheel_diameter>0.1</wheel_diameter>
<max_wheel_torque>20</max_wheel_torque>
<max_wheel_acceleration>1.0</max_wheel_acceleration>
</plugin>
</gazebo>
left_joint 和 right_joint 的名字写反了。记住:插件里的关节名必须和URDF里定义的一模一样,大小写都不能错。
4.4 配置激光雷达与IMU
传感器配置是Nav2能正常工作的前提。激光雷达提供环境感知,IMU提供姿态参考。
先看激光雷达的配置:
<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.1 0 0.15" rpy="0 0 0"/>
</joint>
<gazebo reference="laser_link">
<sensor type="ray" name="laser">
<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.1</min>
<max>10.0</max>
<resolution>0.01</resolution>
</range>
</ray>
<plugin name="laser_plugin" filename="libgazebo_ros_ray_sensor.so">
<ros>
<namespace>/</namespace>
<argument>scan:=scan</argument>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
</plugin>
</sensor>
</gazebo>
IMU的配置相对简单:
<gazebo reference="imu_link">
<sensor type="imu" name="imu_sensor">
<pose>0 0 0 0 0 0</pose>
<update_rate>50</update_rate>
<plugin name="imu_plugin" filename="libgazebo_ros_imu_sensor.so">
<ros>
<namespace>/</namespace>
<argument>imu:=imu</argument>
</ros>
<initial_orientation_as_reference>false</initial_orientation_as_reference>
</plugin>
</sensor>
</gazebo>
update_rate:传感器更新频率。激光雷达一般10-20Hz,IMU可以到50-100Hz。samples:激光雷达的扫描点数。360点对应1度分辨率,720点对应0.5度。min_angle/max_angle:扫描范围。全向雷达设±π,前向雷达设±π/2。
4.5 导出机器人描述文件
模型配好了,怎么让Nav2知道?答案是:通过ROS参数服务器发布机器人描述。
我习惯在启动文件里这样写:
<param name="robot_description"
command="$(find xacro)/xacro '$(find my_robot_description)/urdf/my_robot.urdf.xacro'" />
或者用Python方式:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
pkg_dir = get_package_share_directory('my_robot_description')
urdf_file = os.path.join(pkg_dir, 'urdf', 'my_robot.urdf.xacro')
return LaunchDescription([
DeclareLaunchArgument(
'robot_description',
default_value=urdf_file,
description='Path to robot URDF file'
),
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': open(urdf_file).read()}]
)
])
check_urdf 命令验证模型是否正确。运行 check_urdf my_robot.urdf,它会输出关节树和链接信息。如果报错,多半是坐标变换或者关节类型写错了。
4.6 知识体系总览
说了这么多,咱们用一张图把整个流程串起来:
这张图把整个流程分成了四层:模型描述、驱动与传感器配置、参数导出、验证。每一层都依赖上一层,缺一不可。我在项目里见过有人跳过了验证步骤,结果在仿真里机器人直接穿模——嗯,那画面挺尴尬的。
好了,模型构建这块就聊到这儿。记住一个原则:先搭骨架,再填血肉,最后检查关节。这样配出来的机器人,跑Nav2基本不会出大问题。