通过本案例可以掌握:

  • 如何使用 C++ 编写一个 ROS 设备驱动节点;
  • 如何调试 ROS 发布频率、时间戳和消息内容;
  • 如何结合 rosbag 与 rqt_plot 分析传感器数据的稳定性;
  • 为后续多传感器时间同步与融合(如 EKF)打下基础。

多传感器融合中的 ROS 驱动开发实战案例

在自动驾驶系统中,IMU、GNSS 和雷达等传感器是实现环境感知和车辆定位的关键。本文以 ROS 框架为基础,讲解如何编写一个简单的传感器驱动节点,实现数据采集 → 解析 → 发布 → 可视化,并附带常见调试命令。


1. 项目结构

my_sensor_driver/
├── launch/
│   └── sensor_driver.launch
├── src/
│   └── imu_driver_node.cpp
├── include/
│   └── imu_driver.hpp
├── config/
│   └── imu_config.yaml
├── CMakeLists.txt
├── package.xml
  • my_sensor_driver: ROS package 根目录,所有内容都放在这里

  • launch/sensor_driver.launch:

    • ROS 的启动脚本,用 XML 格式描述:
      • 启动哪个节点(node)
      • 使用哪些参数文件(如 config/imu_config.yaml)
      • 是否输出日志到屏幕等
      • 用法:roslaunch my_sensor_driver sensor_driver.launch
      • 示例:
      <launch>
      <!-- 加载 IMU 参数配置 -->
      <rosparam file="$(find my_sensor_driver)/config/imu_config.yaml" command="load"/>
      
      <!-- 启动 IMU 驱动节点 -->
      <node name="imu_driver" pkg="my_sensor_driver" type="imu_driver_node" output="screen"/>
      </launch>
      
  • src/imu_driver_node.cpp

  • C++ 源码文件,实现如下功能:

    • 初始化 ROS 节点
    • 串口读 IMU 数据
    • 解析并发布为 /imu/data ROS 消息
  • include/imu_driver.hpp: 头文件,通常用于:

    • 声明类 ImuDriver、函数、结构体等
    • 和 cpp 分离,便于模块化、复用
    • 如果项目简单也可以省略,用于“规范化”大型项目
  • config/imu_config.yaml:参数配置文件,用于将 IMU 驱动的参数(如串口号、波特率、话题名等)外部化管理

示例:

port: "/dev/ttyUSB0"
baudrate: 115200
frame_id: "imu_link"

在 sensor_driver.launch 中通过 rosparam 加载:

<rosparam file="$(find my_sensor_driver)/config/imu_config.yaml" command="load"/>
  • CMakeLists.txt:ROS 的构建脚本:
  • 作用:
    • 编译 src/imu_driver_node.cpp → 可执行文件 imu_driver_node
    • 指定依赖(如 sensor_msgs、roscpp)
  • 示例:
cmake_minimum_required(VERSION 3.0.2)
project(my_sensor_driver)

## 编译类型设置
add_compile_options(-std=c++11)

## 查找依赖的包
find_package(catkin REQUIRED COMPONENTS
  roscpp
  sensor_msgs
  std_msgs
)

## 生成消息的路径设置
catkin_package()

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)

## 编译可执行文件
add_executable(imu_driver_node src/imu_driver_node.cpp)
target_link_libraries(imu_driver_node ${catkin_LIBRARIES})

package.xml: ROS 的包描述文件

  • 作用:

    • 告诉 ROS 这个包的名称、依赖库、作者信息等

    • 配合 catkin_make 使用

示例:

<?xml version="1.0"?>
<package format="2">
  <name>my_sensor_driver</name>
  <version>0.0.1</version>
  <description>Minimal ROS sensor driver package</description>

  <maintainer email="your_email@example.com">Your Name</maintainer>
  <license>BSD</license>

  <buildtool_depend>catkin</buildtool_depend>

  <depend>roscpp</depend>
  <depend>sensor_msgs</depend>
  <depend>std_msgs</depend>

  <export>
  </export>
</package>

使用 catkin_make打包与运行流程:

cd ~/catkin_ws
catkin_make
source devel/setup.bash
roslaunch my_sensor_driver sensor_driver.launch

TODO: 模拟 IMU 串口发送脚本(mock_imu_sender.py) 模拟 GNSS ROS 节点(mock_gnss_node.py) EKF launch 文件


2. 驱动逻辑核心(src/imu_driver_node.cpp部分代码示例)

实现读取串口数据并发布为 /imu/data 的逻辑

#include <ros/ros.h>
#include <sensor_msgs/Imu.h>
#include <serial/serial.h>
#include <string>
#include <sstream>

// 模拟解析函数:从原始字符串中提取四元数(实际应依据具体协议实现)
void parseIMUData(const std::string& raw, sensor_msgs::Imu& imu_msg) {
    // 示例数据: "0.0,0.0,0.0,1.0"
    std::stringstream ss(raw);
    std::string item;
    float q[4];
    int i = 0;

    while (std::getline(ss, item, ',') && i < 4) {
        q[i++] = std::stof(item);
    }

    imu_msg.orientation.x = q[0];
    imu_msg.orientation.y = q[1];
    imu_msg.orientation.z = q[2];
    imu_msg.orientation.w = q[3];
}

int main(int argc, char** argv) {
    ros::init(argc, argv, "imu_driver_node");
    ros::NodeHandle nh;
    ros::NodeHandle pnh("~");

    std::string port;
    int baudrate;
    std::string frame_id;

    // 获取参数
    pnh.param<std::string>("port", port, "/dev/ttyUSB0");
    pnh.param<int>("baudrate", baudrate, 115200);
    pnh.param<std::string>("frame_id", frame_id, "imu_link");

    // 初始化串口
    serial::Serial ser;
    try {
        ser.setPort(port);
        ser.setBaudrate(baudrate);
        serial::Timeout to = serial::Timeout::simpleTimeout(1000);
        ser.setTimeout(to);
        ser.open();
    } catch (serial::IOException& e) {
        ROS_ERROR_STREAM("Unable to open port " << port);
        return -1;
    }

    if (!ser.isOpen()) {
        ROS_ERROR_STREAM("Serial port not opened.");
        return -1;
    }

    ros::Publisher imu_pub = nh.advertise<sensor_msgs::Imu>("/imu/data", 50);

    ROS_INFO_STREAM("IMU Driver Started on " << port << " at " << baudrate << " baudrate.");

    ros::Rate loop_rate(50);
    while (ros::ok()) {
        if (ser.available()) {
            std::string raw_data = ser.readline();
            sensor_msgs::Imu imu_msg;
            imu_msg.header.stamp = ros::Time::now();
            imu_msg.header.frame_id = frame_id;

            parseIMUData(raw_data, imu_msg);
            imu_pub.publish(imu_msg);
        }
        ros::spinOnce();
        loop_rate.sleep();
    }

    return 0;
}

🚀 3. Launch 文件示例

<launch>
  <node pkg="my_sensor_driver" type="imu_driver_node" name="imu_driver" output="screen" />
</launch>

4. 常用 ROS 调试命令(推荐操作顺序)

操作目的 常用命令
查看 topic 发布频率 rostopic hz /imu/data
查看发布数据内容 rostopic echo /imu/data
查看所有活跃节点 rosnode list
检查节点状态 rosnode info /imu_driver
可视化数据 rqt_plot /imu/data.orientation.x
录制调试日志 rosbag record -O imu_log.bag /imu/data
播放 rosbag 日志 rosbag play imu_log.bag

5. 延迟与频率验证技巧

# 查看延迟(是否 timestamp 稳定)
rostopic echo -n 5 /imu/data | grep "stamp"

# 绘制数据间隔时间
rqt_plot /imu/data.header.stamp

6. 总结

下一步是 整合 GNSS 数据,并使用 message_filters::ApproximateTime 实现 IMU + GNSS 时间同步。

🛰️ GNSS + IMU 融合定位(基于 ROS + EKF)

接下来这部分将介绍如何基于 ROS 框架将 GNSS 与 IMU 数据进行融合,利用 robot_localization 包中的 EKF 节点实现车辆位置估计,提高定位的鲁棒性和精度。


1. 使用包依赖

sudo apt install ros-<distro>-robot-localization

2. 数据准备

输入数据:

  • /imu/data:IMU 原始数据(方向、加速度)
  • /gps/fix:GNSS 数据(sensor_msgs/NavSatFix

可选增强数据:

  • /gps/vel:GNSS 速度信息
  • /odometry/wheel:轮速计(用于补偿 GNSS 信号丢失)

3. EKF 节点配置文件(ekf.yaml)

frequency: 30
sensor_timeout: 0.1

two_d_mode: true
publish_tf: true

map_frame: map
odom_frame: odom
base_link_frame: base_link
world_frame: odom

imu0: /imu/data
imu0_config: [false, false, false,
              true, true, false,
              false, false, false,
              false, false, false,
              false, false, false]
imu0_differential: false
imu0_remove_gravitational_acceleration: true

gps0: /gps/fix
gps0_config: [true, true, false,
              false, false, false,
              false, false, false,
              false, false, false,
              false, false, false]
gps0_differential: false
use_control: false

4. 启动文件(ekf_fusion.launch)

<launch>
  <node pkg="robot_localization" type="ekf_localization_node" name="ekf_localization" output="screen">
    <param name="~config" value="$(find your_package)/config/ekf.yaml" />
  </node>
</launch>

5. 可视化与验证

推荐指令:

# 查看定位融合结果
rostopic echo /odometry/filtered

# 用 rviz 显示路径轨迹
rviz

# 对比 GNSS 原始与 EKF 融合输出
rqt_plot /gps/fix/latitude /odometry/filtered/pose/pose/position/x

6. 问题排查建议

问题 原因 解决建议
融合输出跳变 / 无输出 时间戳不一致 确保所有话题时间同步(使用 rosbag + --clock
EKF 输出延迟大 频率设置太低或topic不活跃 增加 frequency,确认输入话题活跃
数据漂移严重 IMU 重力未剔除 设置 imu0_remove_gravitational_acceleration: true
融合失败报错 配置字段顺序错误 检查每个 *_config 数组长度为15,顺序严格

7. 总结

通过本案例,我们可以掌握:

  • 使用 robot_localization 进行 GNSS + IMU 融合定位;
  • 如何配置 EKF 参数以适配不同传感器特性;
  • 如何通过 rviz / rqt_plot 进行融合结果验证;
  • 以及时间同步、漂移修正等常见问题的处理方法。

下一步我们可以尝试加入轮速计、视觉里程计等,进行三传感器融合。