4. 机器人模型构建与配置:URDF/SDF、差速驱动、激光雷达与IMU

好,咱们进入正题。这一章要聊的是机器人模型的构建与配置。说白了,就是给你的虚拟机器人「造个身子」,再给它装上「腿」、「眼睛」和「耳朵」。

我个人习惯把这一步叫做「搭积木」。你想想看,一个能跑起来的机器人,至少得有底盘、轮子、传感器。在Nav2的世界里,这些都得用URDF或者SDF描述清楚。

4.1 URDF vs SDF:选哪个?

先解决一个选择困难症的问题。URDF和SDF都能描述机器人,但侧重点不同。

特性 URDF SDF
起源 ROS原生格式 Gazebo原生格式
物理属性 需要额外插件 内置支持
多机器人 不直接支持 原生支持
复用性 通过Xacro宏 通过include

我在项目中遇到过不少新手,一上来就用纯URDF,结果在Gazebo里死活动不了。为什么?因为URDF本身不包含物理引擎的配置,你得手动加插件。而SDF把这些都打包好了。

我的建议:如果你主要做Nav2仿真,用URDF+Xacro组合。原因很简单——生态成熟,社区资源多。但如果你要搞复杂的物理仿真,比如多关节摩擦、流体阻力,那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 知识体系总览

说了这么多,咱们用一张图把整个流程串起来:

机器人模型构建与配置流程 URDF/SDF 模型描述 Xacro 宏定义 物理属性配置 差速驱动插件 激光雷达传感器 IMU 传感器 导出 robot_description 参数 check_urdf / 可视化验证

这张图把整个流程分成了四层:模型描述、驱动与传感器配置、参数导出、验证。每一层都依赖上一层,缺一不可。我在项目里见过有人跳过了验证步骤,结果在仿真里机器人直接穿模——嗯,那画面挺尴尬的。

好了,模型构建这块就聊到这儿。记住一个原则:先搭骨架,再填血肉,最后检查关节。这样配出来的机器人,跑Nav2基本不会出大问题。