3. PX4 SITL 深度解析:从架构到通信链路

这一章,我们直接钻进 PX4 SITL 的核心。很多朋友跑仿真就是敲个 make px4_sitl gazebo,然后看飞机飘起来就完事了。但真机部署时遇到问题,往往就卡在对仿真底层的不理解上。我个人习惯是,先把架构摸透,再动手。这样出了问题,你知道该去哪个模块翻代码。

3.1 PX4 架构概览:分层与模块

PX4 的架构,说白了就是一套「飞控大脑」的分工体系。它不像 Arduino 写个循环那么直白,而是分成了几个清晰的层次。

核心分层:

  • 应用层: 各种飞行模式(定高、悬停、任务)、导航逻辑。这部分你改得最多。
  • 中间件层: uORB(微对象请求代理)消息总线。所有模块之间的数据交换都走这里。
  • 硬件抽象层: 驱动层,负责跟传感器(IMU、GPS)、执行器(电调、舵机)打交道。
  • 实时操作系统: NuttX,负责任务调度、内存管理。

我在项目中遇到过一个问题:真机上日志里某个传感器数据突然跳变,查了半天,最后发现是 uORB 消息队列被高频率的日志任务堵死了。嗯,这就是对中间件层不够了解吃的亏。

下面这张图,是我自己画的一个简化版架构图,帮你快速建立整体认知。

PX4 软件架构分层图 应用层 (Application Layer) 飞行模式 (MC/FW) | 导航 (Navigator) | 任务规划 (Mission) 位置估计 (EKF2) | 路径规划 (Path Planning) 中间件层 (Middleware Layer) uORB 消息总线 (Micro Object Request Broker) 模块间通信 | 发布/订阅模式 | 消息队列 硬件抽象层 (HAL / Driver Layer) 传感器驱动 (IMU, GPS, Baro, Mag) | 执行器驱动 (PWM, DShot) I2C / SPI / UART 总线抽象 实时操作系统 (RTOS: NuttX) 任务调度 | 文件系统 | 网络协议栈 | 内存管理

3.2 SITL 启动流程:一条命令背后发生了什么

你执行 make px4_sitl gazebo,机器就开始干活了。但具体干了什么?我把它拆成几个关键步骤。

  1. 编译 PX4 固件: 针对 x86_64 Linux 架构编译,而不是 ARM 或 STM32。所有硬件驱动被替换成仿真驱动。
  2. 启动 Gazebo 服务器: 加载世界模型和飞机模型(通常是 Iris 四旋翼)。
  3. 启动 PX4 主进程: 加载参数文件,初始化 uORB 总线,启动各个模块(传感器、导航、控制)。
  4. 建立仿真桥接: Gazebo 通过 gazebo_mavlink_interface 插件,把仿真传感器的数据(IMU、GPS、气压计)通过 MAVLink 协议发给 PX4。
  5. 等待外部连接: PX4 在 UDP 端口 14540(给 QGC)和 14580(给 SDK)上监听。

避坑指南: 我曾经在启动 SITL 时遇到飞机直接坠地,查了半天发现是 Gazebo 的模型文件里惯性参数写错了。仿真环境里的 .sdf 或 .urdf 文件,质量、惯性矩阵这些参数一定要跟真机对得上,否则仿真结果毫无意义。

3.3 MAVSDK 与 MAVLink 通信原理

MAVLink 是无人机界的「通用语言」。它定义了消息格式,比如心跳包、姿态数据、GPS 位置。而 MAVSDK 是 PX4 官方提供的一个高级 SDK,帮你封装了 MAVLink 的底层细节。

你想想看,如果让你直接解析 MAVLink 的二进制流,处理消息 ID、CRC 校验、序列号,那得写多少代码?MAVSDK 把这些全干了。你只需要调用 Telemetry::position() 就能拿到位置。

3.3.1 MAVLink 消息结构

一个标准的 MAVLink v2 消息帧长这样:

字段 长度 (字节) 说明
STX (起始标志) 1 0xFD 表示 MAVLink v2
Payload Length 1 有效载荷长度
Incompatibility Flags 1 签名、加密等标志
Compatibility Flags 1 兼容性标志
Sequence 1 消息序列号,用于丢包检测
System ID 1 系统编号(飞控通常是 1)
Component ID 1 组件编号(自动驾驶仪、摄像头等)
Message ID 3 消息类型(如 #30 是 ATTITUDE)
Payload 0-255 实际数据
Checksum (CRC) 2 16 位 CRC 校验
Signature (可选) 13 用于安全认证

为什么要有 System ID 和 Component ID?我在做多机编队时,一台地面站同时控制 5 架飞机,就是靠这两个 ID 来区分消息该发给谁。

3.3.2 MAVSDK 的工作模式

MAVSDK 内部维护了一个 MAVLink 通信线程。你调用 API 时,它把请求打包成 MAVLink 消息发出去,然后异步等待响应。核心流程如下:

// C++ 示例:使用 MAVSDK 请求无人机位置
#include <mavsdk/mavsdk.h>
#include <mavsdk/plugins/telemetry/telemetry.h>

using namespace mavsdk;

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

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

    Telemetry telemetry{system.value()};

    // 订阅位置更新(异步回调)
    telemetry.subscribe_position([](Telemetry::Position position) {
        std::cout << "纬度: " << position.latitude_deg << std::endl;
        std::cout << "经度: " << position.longitude_deg << std::endl;
    });

    // 保持主线程运行
    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    return 0;
}

注意: 上面的代码里,add_any_connection("udp://:14540") 是监听本机 14540 端口。如果你在真机上,通常会用串口连接,地址会变成 serial:///dev/ttyUSB0:57600。端口和波特率一定要跟飞控参数匹配,否则连不上。

3.4 地面站 (QGC) 连接仿真

QGroundControl 是 PX4 官方推荐的地面站。连接 SITL 仿真非常简单,因为 PX4 启动后会自动在 UDP 14540 端口广播心跳包。

具体操作步骤:

  1. 启动 SITL:make px4_sitl gazebo
  2. 打开 QGC,在「应用程序设置」-「通信」-「添加」里,选择 UDP,端口填 14540。
  3. QGC 会自动发现飞控,状态栏从灰色变成绿色。

连接成功后,你可以在 QGC 上看到仿真飞机的姿态、位置、电池电压(仿真值)。你还可以在「计划」页面里上传一条航线,飞机就会自动执行。

我的经验: 如果你在 QGC 里看不到仿真飞机,八成是防火墙把 UDP 端口拦了。在 Linux 上可以临时关掉防火墙试试:sudo ufw disable。Windows 上记得在防火墙设置里放行 QGC 和 PX4 的进程。

下面这张流程图,总结了从 SITL 启动到 QGC 连接的完整数据流。

SITL 仿真数据流 Gazebo 仿真环境 传感器模拟 PX4 SITL 进程 飞控逻辑 MAVSDK / QGC 地面站 / 用户程序 MAVLink (UDP:14560) 仿真传感器数据 MAVLink (UDP:14540/14580) 遥测 / 控制指令 关键端口说明 14560: Gazebo 与 PX4 之间的仿真数据通道 14540: PX4 对外广播的遥测端口 (QGC 连接)

看到这里,你应该对 PX4 SITL 的架构、启动流程、通信原理和地面站连接有了一个立体的认识。这些东西,说白了就是一套「仿真-飞控-地面站」的铁三角关系。把这三者之间的数据流和协议搞明白,后面真机部署时,你就能快速定位问题是出在飞控逻辑、通信链路还是地面站配置上。