2. PX4架构概览
理解PX4的架构,是进行自定义模块与uORB开发的前提。PX4并不是一个“从上到下顺序执行”的单片机程序,而是一个运行在NuttX实时操作系统之上的模块化飞行控制软件栈。它把姿态估计、位置控制、传感器驱动、通信链路、日志记录等功能拆分成一个个独立模块,模块之间通过uORB(micro Object Request Broker)这一轻量级发布/订阅消息总线交换数据。掌握这套结构,才能知道自己的代码应该“插”在哪里、和谁通信、由谁启动。
一、PX4软件栈的分层结构
从下往上,PX4可以划分为四个层次,每一层职责清晰、边界明确:
- 硬件与驱动层:包括STM32等MCU、IMU、磁力计、气压计、GPS、PWM输出等外设,以及对应的传感器驱动模块(如
px4fmu、ekf2依赖的IMU驱动)。 - 操作系统与中间件层:NuttX提供任务调度、内存管理、文件系统;uORB、参数系统(param)、日志系统(logger)、MAVLink通信构成中间件。
- 核心功能模块层:传感器融合(
ekf2)、姿态控制(mc_att_control)、位置控制(mc_pos_control)、导航器(navigator)、commander状态机等。 - 应用与接口层:MAVSDK/QGC地面站、自定义业务模块、外部机载计算机通过MAVLink或uORB桥接接入。
这种分层让PX4既能保证实时控制回路的确定性,又能灵活扩展上层应用。下图展示了PX4软件栈的层次关系以及uORB在其中的枢纽位置:
二、模块化设计:一切皆模块
PX4的模块化体现在两个层面。第一是编译期模块化:每个模块是一个独立的C++类,继承自 ModuleBase 或 ScheduledWorkItem,通过 CMakeLists.txt 注册进固件。第二是运行期模块化:模块以NuttX任务(task)或工作队列(work queue)的形式运行,可以独立启动、停止、查看状态。
模块之间不直接调用函数,而是通过uORB主题解耦。例如 ekf2 订阅 sensor_combined,发布 vehicle_attitude 和 vehicle_local_position;mc_att_control 订阅 vehicle_attitude 和 vehicle_control_mode,发布 vehicle_attitude_setpoint。这种“发布者不知道订阅者是谁”的设计,使得新增一个自定义模块时,只需订阅已有主题、发布新主题,无需修改任何现有模块。
三、启动流程:从上电到解锁
PX4的启动流程由启动脚本驱动,核心文件是 ROMFS/px4fmu_common/init.d/rcS 及其调用的 rc.board_defaults、rc.mavlink 等。启动顺序大致如下:
- Bootloader:加载固件到内存并跳转。
- NuttX初始化:建立任务调度、挂载文件系统、初始化串口。
- rcS脚本执行:依次启动传感器驱动、
ekf2、commander、navigator、mc_att_control、mc_pos_control、logger、mavlink等模块。 - commander状态机:进入
STANDBY,等待解锁信号,随后进入ARMED并驱动控制回路。
在终端中可以用以下命令查看模块运行状态和uORB主题:
# 查看所有运行中的模块
px4> top
# 查看指定模块状态
px4> ekf2 status
px4> commander status
# 列出所有uORB主题
px4> uorb top
# 监听某个主题的发布频率
px4> listener sensor_combined
# 手动启动一个模块
px4> mc_att_control start
四、uORB在架构中的位置与作用
uORB位于中间件层,是模块间通信的唯一标准通道。它的核心机制是发布/订阅:发布者调用 orb_publish() 写入主题,订阅者通过 orb_subscribe() 获取句柄并调用 orb_copy() 读取最新数据。uORB内部为每个主题维护一个环形缓冲区,支持多订阅者、非阻塞读取和超时等待。
| 主题名称 | 典型发布者 | 典型订阅者 | 作用 |
|---|---|---|---|
sensor_combined |
传感器驱动 | ekf2 | 汇总IMU、磁力计、气压计数据 |
vehicle_attitude |
ekf2 | mc_att_control、logger | 输出姿态四元数 |
vehicle_local_position |
ekf2 | mc_pos_control、navigator | 输出本地位置与速度 |
vehicle_command |
commander、mavlink | navigator、自定义模块 | 传递解锁、模式切换等指令 |
actuator_controls |
mc_att_control | mixer、PWM输出 | 输出归一化控制量 |
五、案例与常见误区
案例:假设要开发一个“低电量自动返航”自定义模块。正确做法是:订阅 battery_status 和 vehicle_local_position,当电量低于阈值时,向 vehicle_command 发布一条 VEHICLE_CMD_DO_SET_MODE 或 VEHICLE_CMD_NAV_RETURN_TO_LAUNCH 命令,由 commander 和 navigator 执行。这样模块不直接控制电机,符合架构分层。
- 在自定义模块中直接调用其他模块的函数,破坏模块化解耦。
- 忘记在
CMakeLists.txt中注册模块,导致编译后无法启动。 - 订阅主题后未检查
orb_copy()返回值,读取到过期数据。 - 在中断或高优先级任务中执行耗时操作,阻塞uORB发布。
总结来说,PX4的架构可以概括为:NuttX提供运行环境,模块化提供扩展能力,启动脚本负责编排,uORB负责连接。理解这四者的关系,后续编写自定义模块时就能准确找到自己的位置,知道该订阅什么、发布什么、由谁启动、如何调试。