4、PX4与Gazebo通信:MAVSDK与MAVLink协议、uXRCE-DDS中间件、仿真时间同步机制、话题与服务的映射关系

好,咱们进入第四章。这一章聊的是PX4和Gazebo之间到底怎么“说话”的。说白了,就是通信链路。

很多新手搭好环境,一运行仿真,飞机能飞,但一问到数据怎么从飞控传到Gazebo的,就懵了。我当年也踩过这个坑——调了半天位置估计,结果发现话题根本没对上。嗯,今天咱们就把这条链路彻底捋清楚。

4.1 通信架构总览:两条并行的“高速公路”

PX4和Gazebo之间,其实跑着两套完全不同的通信机制。它们各管一摊,互不干扰。

核心观点:PX4内部用uORB,对外用MAVLink;Gazebo内部用DDS/ROS 2,对外用uXRCE-DDS。中间靠一个叫px4_ros_com的桥接层打通。

我习惯把这张图画在笔记本首页,每次调试前先看一眼,思路就清晰了。

PX4 ↔ Gazebo 通信架构总览 PX4 飞控 uORB 内部总线 MAVLink 对外接口 Gazebo 仿真器 DDS / ROS 2 总线 uXRCE-DDS 客户端 px4_ros_com 桥接层 话题映射 · 时间同步 · 协议转换 MAVSDK / MAVLink 外部控制 & 地面站 uXRCE-DDS 客户端 传感器 & 执行器数据 仿真时间同步信号 clock / time_source 话题

4.2 MAVSDK与MAVLink协议:外部控制的“通用语言”

MAVLink是PX4对外通信的“普通话”。它诞生得早,生态成熟,几乎所有地面站都支持。

MAVSDK则是基于MAVLink封装的一套高级API。你不需要手动拼MAVLink报文,直接调函数就行。

4.2.1 MAVLink的消息结构

每个MAVLink报文都很紧凑。我拆开看过,结构大致是这样:

字段 长度(字节) 说明
STX(起始标志) 1 0xFD(MAVLink 2)或 0xFE(MAVLink 1)
Payload长度 1 有效载荷的字节数
序列号 1 丢包检测用
系统ID / 组件ID 2 标识发送方
消息ID 3 标识消息类型(如 #30 = ATTITUDE)
Payload 0~255 实际数据
CRC校验 2 完整性校验

小技巧:调试时可以用mavlink-inspector或者QGroundControl的消息视图实时抓包。我曾经靠这个抓到一个姿态数据字节序配错的问题,省了两天排查时间。

4.2.2 MAVSDK的典型用法

MAVSDK把MAVLink的细节藏起来了。你写代码时,感觉就像在调一个本地库。

#include <mavsdk/mavsdk.h>
#include <mavsdk/plugins/telemetry/telemetry.h>
#include <mavsdk/plugins/action/action.h>

using namespace mavsdk;

int main() {
    Mavsdk mavsdk;
    ConnectionResult conn_result = mavsdk.add_any_connection("udp://:14540");

    // 等待系统上线
    auto system = mavsdk.first_autopilot(3.0);
    if (!system) {
        std::cerr << "连接超时" << std::endl;
        return 1;
    }

    Telemetry telemetry(system.value());
    Action action(system.value());

    // 订阅姿态数据
    telemetry.subscribe_attitude_euler([](Telemetry::EulerAngle euler) {
        std::cout << "Roll: " << euler.roll_deg
                  << " Pitch: " << euler.pitch_deg
                  << " Yaw: " << euler.yaw_deg << std::endl;
    });

    // 解锁并起飞
    action.arm();
    action.takeoff();

    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 0;
}

你看,代码里没有出现任何MAVLink字节操作。这就是MAVSDK的价值——让开发者专注于业务逻辑。

4.3 uXRCE-DDS中间件:PX4与ROS 2的“翻译官”

MAVLink虽然好,但它不是为ROS 2设计的。PX4要跟Gazebo里的ROS 2节点通信,需要另一条路——uXRCE-DDS。

uXRCE-DDS的全称是“Micro XRCE-DDS”。它让资源受限的嵌入式设备(比如PX4运行的STM32)也能接入DDS世界。

4.3.1 客户端-代理架构

uXRCE-DDS采用不对称设计:

  • 客户端(Client):运行在PX4上,轻量级,只负责收发序列化的数据。
  • 代理(Agent):运行在Gazebo宿主机上,负责与DDS全局数据空间交互。

我刚开始接触时,总以为客户端和代理之间走的是DDS。其实不是——它们之间走的是自定义的XRCE协议,通常承载在UDP或串口上。代理再把数据翻译成DDS格式。

注意:uXRCE-DDS的客户端和代理版本必须匹配。我曾经因为升级了ROS 2但忘了更新micro_ros_agent,结果通信一直断断续续,查了两天才发现是版本不兼容。

4.3.2 启动uXRCE-DDS代理

在Gazebo仿真中,启动代理的命令很简单:

# 通过UDP连接,端口默认8888
MicroXRCEAgent udp4 --port 8888

# 或者通过串口(用于实际硬件)
MicroXRCEAgent serial --dev /dev/ttyACM0 --baud 921600

PX4那边默认会在启动时自动连接127.0.0.1:8888。如果你改了端口,记得在PX4的启动脚本里加参数:

# 在px4_config.xml或启动脚本中
<micro_ros_config>
    <port>udp://127.0.0.1:8889</port>
</micro_ros_config>

4.4 仿真时间同步机制:让“虚拟时钟”对齐

仿真中最容易出问题的,就是时间。Gazebo有自己的仿真时钟,PX4也有自己的硬件时钟。如果不同步,你看到的传感器数据时间戳全是乱的。

PX4的仿真时间同步机制,说白了就是让PX4“认”Gazebo的时钟为老大。

4.4.1 时间源的选择

PX4支持三种时间源:

时间源 适用场景 精度
硬件时钟(RTC) 实际飞行 高(依赖晶振)
仿真时钟(Gazebo) Gazebo仿真 由仿真步长决定
回放时钟(Log) ULog回放 与录制时一致

在Gazebo仿真中,PX4通过订阅clock话题来获取仿真时间。这个机制是自动的——只要px4_ros_com检测到Gazebo在运行,就会切换到仿真时钟模式。

4.4.2 时间同步的典型问题

我遇到过最典型的问题,是仿真跑得比真实时间快很多。原因通常是:

  • Gazebo的real_time_factor设置过高(默认1.0,但有时会被误改)
  • PX4没有正确订阅clock话题,导致它用自己的硬件时钟,而硬件时钟在仿真中是不走的

检查方法:在PX4控制台输入listener sensor_combined,看时间戳是否与Gazebo的仿真时间一致。如果时间戳是0或者乱跳,说明时间同步没生效。

4.5 话题与服务的映射关系:uORB ↔ ROS 2

这是整个通信链路中最需要理解的部分。PX4内部用uORB,ROS 2用DDS话题。两者之间怎么对应?

答案就在px4_ros_com的配置文件里。它定义了一张映射表。

4.5.1 常用话题映射

uORB话题(PX4内部) ROS 2话题(Gazebo侧) 消息类型 方向
sensor_combined /fmu/in/sensor_combined px4_msgs::msg::SensorCombined PX4 → Gazebo
vehicle_attitude /fmu/out/vehicle_attitude px4_msgs::msg::VehicleAttitude PX4 → Gazebo
vehicle_local_position /fmu/out/vehicle_local_position px4_msgs::msg::VehicleLocalPosition PX4 → Gazebo
actuator_outputs /fmu/in/actuator_outputs px4_msgs::msg::ActuatorOutputs Gazebo → PX4
vehicle_command /fmu/in/vehicle_command px4_msgs::msg::VehicleCommand Gazebo → PX4

注意命名规律:/fmu/in/是发给PX4的,/fmu/out/是PX4发出来的。这个命名约定在调试时非常有用——你只要看话题前缀,就知道数据流向。

4.5.2 服务映射

除了话题,还有服务(Service)映射。比如参数设置、校准命令等。

# 通过ROS 2服务设置PX4参数
ros2 service call /fmu/vehicle_command px4_msgs/srv/VehicleCommand "{command: 1, param1: 0.0}"

不过说实话,服务映射在实际仿真中用得不多。我更喜欢用MAVSDK来调参数,因为MAVSDK的API更直观。

4.5.3 自定义映射

如果你需要添加自己的话题映射,可以修改px4_ros_com的配置文件。路径通常在:

src/px4_ros_com/templates/urtps_bridge_topics.yaml

格式很简单:

- topic: /fmu/out/my_custom_topic
  type: px4_msgs::msg::MyCustomMsg
  direction: out

改完后重新编译px4_ros_com,重启仿真就能看到新话题了。

避坑:自定义话题的消息定义必须同时存在于PX4的uORB消息列表和ROS 2的px4_msgs包中。少一个,桥接就会静默失败——话题不出现,也不报错。我因为这个浪费过一整个下午。

4.6 本章小结

好,咱们把这一章的核心点串一下:

  • PX4对外通信有两条路:MAVLink(给地面站和MAVSDK)和uXRCE-DDS(给ROS 2/Gazebo)
  • MAVSDK是MAVLink的高级封装,适合快速开发外部控制程序
  • uXRCE-DDS采用客户端-代理架构,让PX4能接入DDS生态
  • 仿真时间同步靠Gazebo的clock话题驱动,PX4自动切换时间源
  • 话题映射表在px4_ros_com中定义,/fmu/in/和/fmu/out/是识别数据流向的关键

理解这些通信机制,你就能在调试时快速定位问题——是数据没发出来?还是发出来了但话题名不对?还是时间戳乱跳?心里有数,手就不慌。