3. PX4架构与uORB基础

PX4 的飞控软件并不是一个“从上到下顺序执行”的单体程序,而是由多个可独立运行的模块组成,模块之间通过 uORB(micro Object Request Broker)进行异步消息通信。理解这套架构,是后续开发自定义模块、编写 ROS2 桥接节点、调试传感器与控制链路的前提。本节从模块化架构、发布订阅模型、消息定义文件结构以及 uORB 在飞行控制中的作用四个角度展开。

一、PX4 模块化架构

PX4 在运行时表现为一个类 Unix 的实时操作系统环境(NuttX 或 Linux),每个功能单元以“模块”形式存在,例如传感器驱动、姿态估计、位置控制、混控输出、日志记录等。模块可以启动、停止、查看状态,也可以被替换或新增。常见的模块命令如下:

px4> listener sensor_combined
px4> uorb top
px4> work_queue status
px4> ekf2 status
px4> mavlink start -d /dev/ttyS1 -b 921600

模块之间不直接调用彼此的函数,而是通过 uORB 主题(topic)交换数据。这种设计带来三个直接好处:一是解耦,传感器驱动不需要知道谁在消费数据;二是可裁剪,不需要的模块可以不启动;三是可观测,任意主题都能用 listener 实时查看。下图展示了 PX4 从传感器到执行器的典型数据链路与模块分层关系。

PX4 模块化架构与 uORB 数据链路 传感器驱动层 IMU / 磁力计 GPS / 气压计 光流 / 距离传感器 状态估计层 ekf2 姿态/位置 传感器融合 vehicle_attitude 控制层 位置/速度控制 姿态控制器 mc_att_control 输出层 混控器 PWM / DShot 执行器 uORB 发布 / 订阅总线(共享内存主题) sensor_combined · vehicle_attitude · vehicle_local_position · actuator_motors · vehicle_command 日志模块 logger 订阅多主题 写入 ULog MAVLink 桥接 发布/订阅主题 转发到地面站 ROS2 桥接 px4_ros_com DDS 话题映射 模块通过 uORB 主题解耦,任意模块均可发布或订阅,形成松耦合数据流

二、uORB 发布订阅模型

uORB 是 PX4 内部的轻量级消息中间件。它基于共享内存实现,每个主题对应一块固定大小的数据缓冲区,发布者写入最新数据,订阅者按需读取。其核心特征包括:

  • 多对多:一个主题可以有多个发布者和多个订阅者,但通常约定单一发布者。
  • 异步非阻塞:发布者不等待订阅者,订阅者通过轮询或回调获取数据。
  • 最新值语义:订阅者默认读取最新一帧,不保证每帧都被消费。
  • 固定频率:主题通常按固定周期发布,如 IMU 按 1kHz、姿态按 250Hz。

在代码层面,一个模块订阅主题的典型流程是:先 orb_subscribe 获取句柄,再循环 orb_copy 读取数据,最后 orb_unsubscribe 释放。发布则使用 orb_advertise 和 orb_publish。下面是一段简化示例:

int fd = orb_subscribe(ORB_ID(vehicle_attitude));
struct vehicle_attitude_s att;
while (true) {
    orb_copy(ORB_ID(vehicle_attitude), fd, &att);
    // 使用 att.q 四元数进行控制计算
}
orb_unsubscribe(fd);
调试时可用 uorb top 查看各主题的发布频率与订阅者数量,用 listener vehicle_attitude 查看具体字段值,这是排查“数据是否更新”的第一手段。

三、消息定义文件结构

uORB 消息定义文件位于 msg/ 目录,扩展名为 .msg。文件结构非常直观:第一行是消息名称,随后是字段类型与字段名,支持基本类型、数组和常量。例如:

uint64 timestamp        # 时间戳,微秒
float32[4] q            # 姿态四元数
float32 rollspeed       # 滚转角速度
float32 pitchspeed      # 俯仰角速度
float32 yawspeed        # 偏航角速度
uint8 ORB_QUEUE_LENGTH = 8

字段类型包括 uint8/16/32/64、int8/16/32/64、float32/64、bool 以及定长数组。消息文件在编译时由生成器自动转换为 C 结构体和 ROS2 消息,因此修改 .msg 后必须重新编译,否则会出现“字段不存在”或“类型不匹配”的错误。

要素 说明 示例
消息名 首行,决定主题名与结构体名 vehicle_attitude
时间戳 通常为 uint64 timestamp uint64 timestamp
字段类型 基本类型或定长数组 float32[4] q
常量 编译期常量,不占消息体 uint8 ORB_QUEUE_LENGTH = 8
注释 以 # 开头,生成文档 # 姿态四元数

四、uORB 在飞行控制中的作用

在飞行控制链路中,uORB 承担“数据血管”的角色。以多旋翼姿态控制为例:IMU 驱动发布 sensor_combined,ekf2 订阅后发布 vehicle_attitude 与 vehicle_local_position,位置控制器订阅这些估计值并发布 vehicle_attitude_setpoint,姿态控制器再发布 vehicle_rates_setpoint,混控器最终发布 actuator_motors 驱动电调。整条链路没有直接函数调用,全部通过主题衔接。

这种机制带来的关键价值是:任何模块都可以“旁路”观察数据,例如日志模块订阅全部主题写入 ULog,MAVLink 模块订阅并转发到地面站,ROS2 桥接节点订阅后映射为 DDS 话题。对开发者而言,新增一个自定义模块只需声明订阅和发布,不必修改其他模块。

常见误区:认为 uORB 是“队列”,每条消息都会被消费。实际上多数主题是“最新值覆盖”,若订阅者处理过慢,会丢失中间帧。对需要完整序列的场景,应使用 ORB_QUEUE_LENGTH 或改用日志记录。

五、案例与复盘

假设要新增一个“电池低电量自动返航”模块。正确做法是:订阅 battery_status 与 vehicle_status,判断电量阈值与飞行模式,发布 vehicle_command 触发返航。若直接修改 commander 模块内部逻辑,会破坏模块边界,升级 PX4 时极易冲突。复盘要点如下:

  1. 先确认主题是否存在、发布频率是否正常,用 uorb top 验证。
  2. 检查消息字段单位与坐标系,避免把弧度当角度、把 NED 当 ENU。
  3. 修改 .msg 后必须重新编译并同步 ROS2 消息包。
  4. 新增模块应通过 px4_simple_app 模板起步,保持发布/订阅清晰。

掌握 PX4 模块化架构与 uORB 发布订阅模型后,就能理解飞控内部数据如何流动,也能为后续编写 ROS2 接口、自定义消息桥接和调试数据链路打下基础。