PX4固件是一套完整的无人机飞行控制软件栈,运行在NuttX实时操作系统之上。理解其内部架构是进行二次开发、算法移植和故障排查的基础。本章将从操作系统层、软件分层架构、源码组织和板级支持四个层面,深入剖析PX4固件的体系结构。

2.1 NuttX实时操作系统基础

PX4选择NuttX作为底层实时操作系统(RTOS),这是理解PX4运行机制的前提。NuttX是一个开源的嵌入式实时操作系统,遵循POSIX标准,具有体积小、实时性强、可裁剪的特点。

2.1.1 NuttX概览与特性

NuttX由Gregory Nutt于2007年创建,目前由Apache基金会维护。其核心特性包括:

• POSIX兼容:支持标准的POSIX API,便于移植和开发

• 可配置的实时调度:支持多级优先级、抢占式调度、时间片轮转

• 微内核架构:只保留最小必要的内核功能,其余以模块形式提供

• 虚拟文件系统(VFS):统一的文件、设备、网络接口

• 网络协议栈:支持TCP/IP、UDP、Socket等标准网络接口

表2-1 NuttX与其他嵌入式RTOS对比

特性NuttXFreeRTOSZephyrChibiOS
POSIX兼容完整支持不支持部分支持部分支持
调度策略优先级抢占+RR优先级抢占优先级抢占+RR优先级抢占+RR
文件系统VFS+NuttFS/FAT无内置VFS+多后端HAL抽象
网络栈内置TCP/IP需第三方内置多协议内置多协议
硬件支持ARM/RISC-V/x86ARM/多平台ARM/RISC-V/x86ARM为主
主要用户PX4ArduPilot/通用Zephyr生态ArduPilot

2.1.2 NuttX任务调度机制

理解NuttX的任务调度机制对于理解PX4固件的实时性至关重要。NuttX采用基于优先级的抢占式调度算法,支持时间片轮转(Round-Robin)。

**任务优先级:**NuttX支持256个优先级等级(0-255),数字越小优先级越高。在PX4中,典型的优先级分配如下:

表2-2 PX4任务优先级分配

优先级范围任务类型典型模块调度特点
0-49系统内核任务Idle、中断处理最高优先级,不可抢占
50-99实时控制任务姿态控制、电机输出确定性执行,严格实时
100-179导航与估计任务EKF2、Navigator、Commander准实时,允许较低频率
180-223通信任务MAVLink、uORB传输较低优先级,容忍延迟
224-255背景任务日志记录、参数保存最低优先级,空闲时执行

**时间片轮转:**当多个任务具有相同优先级时,NuttX使用时间片轮转调度。默认时间片为20ms,可在配置中调整。这意味着同优先级的任务会轮流获得CPU时间,避免某个任务长时间占用处理器。

**中断处理:**NuttX的中断响应延迟可控制在微秒级别。飞控关键的中断源(如IMU数据就绪中断、定时器中断)被分配最高优先级,确保实时响应。

2.1.3 NuttX在PX4中的配置

PX4对NuttX进行了定制配置,主要通过板级配置文件进行。以Pixhawk 4(STM32F765)为例,其NuttX配置位于:

# 板级NuttX配置目录

boards/px4/fmu-v5/nuttx-config/

├── nsh/

│ └── rcS # NSH启动脚本

├── include/

│ └── board.h # 板级头文件

├── Kconfig # 内核功能选择

└── scripts/

└── Make.defs # 编译选项

关键配置参数包括:

# Kconfig 片段:PX4的NuttX内核配置

CONFIG_SCHED_RR_TICK=20000 # 时间片 20ms

CONFIG_TASK_NAME_SIZE=24 # 任务名称最大长度

CONFIG_MAX_TASKS=64 # 最大任务数

CONFIG_MM_REGIONS=2 # 内存区域数

CONFIG_START_YEAR=2020 # 系统时钟起始年

CONFIG_USEC_PER_TICK=1000 # 每 Tick 的微秒数

CONFIG_NFILE_DESCRIPTORS=64 # 文件描述符数量

CONFIG_DEV_SERIAL_CONSOLE=y # 串口控制台

PX4在NuttX基础上增加了自己的抽象层(Platform Layer),将NuttX的原生API封装为更高层的接口,使得PX4的业务逻辑不直接依赖NuttX API,从而支持未来可能的操作系统迁移。

2.1.4 NuttX Shell(NSH)

NuttX提供了一个内置的命令行Shell——NSH(NuttShell)。在PX4中,NSH是调试和诊断的重要工具。可以通过USB串口或MAVLink Shell访问。

常用NSH命令:

表2-3 PX4常用NSH调试命令

命令功能示例
top查看任务运行状态和CPU占用top
listener监听uORB主题数据listener sensor_combined
uorb top查看uORB主题发布频率uorb top
perf查看性能计数器perf
param show查看参数值param show MC_ROLLRATE_P
ver all查看固件版本信息ver all
dmesg查看系统启动日志dmesg
ps查看进程列表ps
dumpfile导出文件内容dumpfile /fs/microsd/log.ulg

以下是一个NSH调试的典型会话示例:

# 通过USB串口连接后,查看IMU原始数据

nsh> listener sensor_accel

TOPIC: sensor_accel 0

sensor_accel

timestamp: 123456789 (0.005s ago)

topic_instance: 0

device_id: 393226 (0x0006000A)

x: 0.0153 y: -0.0012 z: -9.7892

temperature: 38.5000

errors: 0

# 查看当前所有运行任务及其CPU占用

nsh> top

PID PRI POLY SCHD TYPE NPX STATE NAME CPU(%) STACK

0 0 FIFO 0 TCB 0 Running Idle 23.5 2048

10 180 FIFO 0 TCB 0 Waiting init 0.0 2048

20 250 FIFO 0 TCB 0 Ready hp_default 5.2 4096

25 246 FIFO 0 TCB 0 Waiting rate_ctrl 15.3 2048

30 245 FIFO 0 TCB 0 Waiting att_ctrl 3.1 2048

2.2 PX4软件分层架构

PX4固件采用经典的分层架构设计,从底层硬件到上层应用,共分为五个层次。这种设计使得各层职责清晰、接口明确,便于独立开发和测试。

2.2.1 五层架构概览

从下到上,PX4的软件架构可以分为以下五层:

表2-4 PX4软件分层架构

层次名称职责典型组件
第5层应用层飞行任务管理、用户交互Commander、Navigator、Mavlink
第4层控制层飞行控制算法姿态控制、位置控制、速率控制
第3层估计层状态估计与融合EKF2、姿态估计器、空速估计
第2层驱动层硬件设备抽象IMU驱动、GPS驱动、PWM输出
第1层平台层操作系统与硬件抽象NuttX、HAL、uORB总线

2.2.2 uORB消息总线

uORB(micro Object Request Broker)是PX4固件架构的核心组件,它实现了模块间的解耦通信。所有模块通过uORB主题(Topic)交换数据,而不是直接调用彼此的函数。这种设计模式类似于ROS的话题(Topic)机制,也为后续与ROS2集成提供了天然的桥接基础。

发布**-****订阅模型:**uORB采用经典的发布-订阅(Publish-Subscribe)模型。一个模块可以发布(publish)数据到某个主题,其他模块可以订阅(subscribe)该主题来获取数据。发布者和订阅者之间完全解耦,不需要知道对方的存在。

以下是uORB消息定义的示例。每个uORB主题由一个.msg文件定义,编译时自动生成C/C++结构体和注册代码:

# msg/sensor_combined.msg

# 传感器组合数据 - 融合后的IMU数据

uint64 timestamp # 时间戳(微秒)

float32[3] gyro_rad # 陀螺仪数据(rad/s)

float32 gyro_integral_dt # 陀螺仪积分时间

int32 accelerometer_timestamp_relative # 加速度计相对时间戳

float32[3] accelerometer_m_s2 # 加速度计数据(m/s²)

float32 accelerometer_integral_dt # 加速度计积分时间

uint8 CLIPPING_X = 1

uint8 CLIPPING_Y = 2

uint8 CLIPPING_Z = 4

uint8 accelerometer_clipping # 加速度计剪切标志

uint8 gyro_clipping # 陀螺仪剪切标志

以下是在PX4模块中发布和订阅uORB数据的典型代码:

// 发布示例:在传感器驱动中发布sensor_combined数据

#include <uORB/Publication.hpp>

#include <uORB/topics/sensor_combined.h>

class IMUDriver : public ModuleBase

{

private:

uORB::Publication<sensor_combined_s> _sensor_pub{ORB_ID(sensor_combined)};

void publishData()

{

sensor_combined_s data{};

data.timestamp = hrt_absolute_time(); // 获取当前时间戳

// 填充陀螺仪数据

data.gyro_rad[0] = _gyro_x;

data.gyro_rad[1] = _gyro_y;

data.gyro_rad[2] = _gyro_z;

// 填充加速度计数据

data.accelerometer_m_s2[0] = _accel_x;

data.accelerometer_m_s2[1] = _accel_y;

data.accelerometer_m_s2[2] = _accel_z;

_sensor_pub.publish(data); // 发布到uORB总线

}

};

// 订阅示例:在控制模块中订阅sensor_combined数据

#include <uORB/Subscription.hpp>

#include <uORB/topics/sensor_combined.h>

class AttitudeController : public ModuleBase

{

private:

uORB::Subscription _sensor_sub{ORB_ID(sensor_combined)};

void run()

{

sensor_combined_s sensor_data;

// 检查是否有新数据

if (_sensor_sub.update(&sensor_data)) {

// 使用陀螺仪数据进行姿态控制

float gyro_x = sensor_data.gyro_rad[0];

float gyro_y = sensor_data.gyro_rad[1];

float gyro_z = sensor_data.gyro_rad[2];

// 使用加速度计数据进行姿态补偿

float accel_x = sensor_data.accelerometer_m_s2[0];

float accel_y = sensor_data.accelerometer_m_s2[1];

float accel_z = sensor_data.accelerometer_m_s2[2];

// … 控制算法实现 …

}

}

};

2.2.3 核心uORB主题概览

PX4定义了数百个uORB主题,覆盖了从传感器数据到控制指令的所有数据流。以下是最重要的核心主题:

表2-5 PX4核心uORB主题

主题名称数据内容发布者订阅者
sensor_combined融合后的IMU数据传感器驱动EKF2、控制器
sensor_gpsGPS定位数据GPS驱动EKF2、Navigator
vehicle_attitude飞行器姿态(四元数)EKF2控制器、MAVLink
vehicle_local_position本地坐标系位置EKF2位置控制、Navigator
vehicle_global_position全球坐标系位置EKF2Navigator、MAVLink
vehicle_status飞行状态机Commander几乎所有模块
vehicle_command飞行指令MAVLink、ROS2Commander、Navigator
actuator_outputs执行器输出混控器PWM驱动
vehicle_control_mode控制模式标志Commander所有控制器
battery_status电池状态电池驱动Commander、MAVLink

2.2.4 模块化设计原则

PX4的每个功能模块都是一个独立的线程(Task),通过uORB进行数据交换。这种设计带来了以下优势:

• 解耦:模块间没有直接的函数调用依赖,便于独立测试和替换

• 可扩展:新增模块只需注册新的uORB主题,不影响现有代码

• 实时性:每个模块可以设置独立的优先级和栈大小

• 可调试性:每个模块可以独立启动、停止和调试

每个模块通常继承ModuleBase模板类,这提供了统一的生命周期管理接口:

// PX4模块标准框架

#include <px4_platform_common/module.h>

#include <px4_platform_common/posix.h>

class MyModule : public ModuleBase

{

public:

MyModule();

~MyModule() override;

// ModuleBase接口方法

static int task_spawn(int argc, char *argv[]); // 创建任务

static int custom_command(int argc, char *argv[]); // 自定义命令

static int print_usage(const char *reason = nullptr); // 打印用法

// 主循环

void run() override;

private:

uORB::Subscription _sub{ORB_ID(vehicle_status)};

uORB::Publication<my_output_s> _pub{ORB_ID(my_output)};

bool _should_exit{false};

};

void MyModule::run()

{

// 主循环,直到收到退出信号

while (!_should_exit) {

my_output_s output{};

output.timestamp = hrt_absolute_time();

// 业务逻辑…

_pub.publish(output);

px4_usleep(10000); // 睡眠10ms,即以100Hz运行

}

}

2.2.5 数据流示例:姿态控制链路

为了更直观地理解PX4的uORB数据流,我们以多旋翼姿态控制为例,追踪从传感器到电机输出的完整数据流:

• 1. IMU驱动读取原始数据,发布到 sensor_combined 主题

• 2. EKF2模块订阅 sensor_combined,结合GPS等数据进行状态估计,发布 vehicle_attitude(四元数姿态)

• 3. 姿态控制器(mc_att_control)订阅 vehicle_attitude 和 manual_control_input,计算力矩指令,发布 vehicle_torque_setpoint

• 4. 混控器(control_allocator)订阅力矩指令,根据电机配置计算各电机输出,发布 actuator_motors

• 5. PWM驱动订阅 actuator_motors,输出PWM信号控制电调

整个数据流从传感器到电机输出的总延迟通常在1-2ms以内,这得益于uORB的高效实现和NuttX的实时调度。

2.3 源码目录结构与编译系统

PX4的源码采用清晰的目录组织,基于CMake构建系统。熟悉这套结构是进行源码级开发的基础。

2.3.1 顶层目录结构

PX4-Autopilot/

├── boards/ # 板级配置(每个硬件平台一个子目录)

│ ├── px4/ # Pixhawk系列板卡

│ │ ├── fmu-v5/ # Pixhawk 4(STM32F765)

│ │ ├── fmu-v6x/ # Pixhawk 6X(STM32H753)

│ │ └── …

│ └── sitl/ # 软件在环仿真板级配置

├── msg/ # uORB消息定义(.msg文件)

├── src/ # 源码主目录

│ ├── drivers/ # 硬件驱动

│ ├── lib/ # 公共库(算法、数学库)

│ ├── modules/ # 核心功能模块

│ ├── platforms/ # 平台适配层

│ └── systemcmds/ # 系统命令

├── ROMFS/ # 只读文件系统(启动脚本、混控配置)

├── Tools/ # 开发工具和脚本

├── test/ # 测试代码

└── CMakeLists.txt # 顶层CMake配置

2.3.2 src/modules目录详解

src/modules/是PX4固件的核心,包含了所有重要的飞行控制模块:

表2-6 PX4核心模块概览

模块名称功能描述关键输入关键输出
ekf2扩展卡尔曼滤波器sensor_combined, sensor_gpsvehicle_attitude, vehicle_local_position
commander飞行状态机管理vehicle_command, battery_statusvehicle_status, vehicle_control_mode
navigator自主导航任务处理vehicle_global_position, missionposition_setpoint_triplet
mc_att_control多旋翼姿态控制vehicle_attitude, manual_control_inputvehicle_torque_setpoint
mc_pos_control多旋翼位置控制vehicle_local_position, position_setpoint_tripletvehicle_thrust_setpoint
mc_rate_control多旋翼角速率控制vehicle_angular_velocityvehicle_torque_setpoint
fw_att_control固定翼姿态控制vehicle_attitudeactuator_controls
control_allocator执行器分配(混控)vehicle_torque_setpointactuator_motors, actuator_servos
mavlinkMAVLink协议处理多个uORB主题串口/UDP输出
logger飞行日志记录几乎所有uORB主题ULog文件

2.3.3 CMake构建系统

PX4使用CMake作为构建系统,配合Ninja实现快速编译。理解构建系统有助于添加新模块、修改编译配置和调试编译问题。

**构建命令格式:**make []

表2-7 PX4常用构建命令

命令说明输出
make px4_fmu-v5_default编译Pixhawk 4固件.px4固件文件
make px4_fmu-v6x_default编译Pixhawk 6X固件.px4固件文件
make px4_sitl_default编译SITL仿真版本可执行文件
make px4_sitl gazebo-classic编译SITL并启动Gazebo仿真环境
make px4_fmu-v5_default upload编译并上传到飞控硬件上运行
make clean清理构建产物—
make tests运行单元测试测试报告

2.3.4 添加自定义模块

开发自定义模块是PX4二次开发的常见需求。以下是添加一个自定义模块的完整步骤:

**Step 1: **创建模块目录和源文件

# 创建模块目录

mkdir -p src/modules/my_controller

cd src/modules/my_controller

**Step 2: **编写模块代码(MyController.hpp)

#pragma once

#include <px4_platform_common/module.h>

#include <px4_platform_common/module_params.h>

#include <uORB/Subscription.hpp>

#include <uORB/Publication.hpp>

#include <uORB/topics/vehicle_attitude.h>

#include <uORB/topics/vehicle_torque_setpoint.h>

class MyController : public ModuleBase, public ModuleParams

{

public:

MyController();

~MyController() override = default;

static int task_spawn(int argc, char *argv[]);

static int custom_command(int argc, char *argv[]);

static int print_usage(const char *reason = nullptr);

void run() override;

// 参数定义(自动绑定到PX4参数系统)

DEFINE_PARAMETERS(

(ParamFloatpx4::params::MY_GAIN_P) _param_my_gain_p,

(ParamFloatpx4::params::MY_GAIN_I) _param_my_gain_i,

(ParamFloatpx4::params::MY_GAIN_D) _param_my_gain_d

)

private:

uORB::Subscription _att_sub{ORB_ID(vehicle_attitude)};

uORB::Publication<vehicle_torque_setpoint_s> _torque_pub{ORB_ID(vehicle_torque_setpoint)};

};

**Step 3: **编写CMakeLists.txt(模块构建配置)

# src/modules/my_controller/CMakeLists.txt

px4_add_module(

MODULE modules__my_controller # 模块唯一标识

MAIN my_controller # 命令行名称

STACK_MAIN 4096 # 主栈大小

STACK_MAX 2048 # 子线程栈大小

COMPILE_FLAGS

-Os # 优化大小

SRCS

MyController.cpp # 源文件

DEPENDS

px4_workqueue # 依赖库

)

提示:

添加新模块后,还需要在目标板级的default.cmake文件中启用该模块:drivers/my_controller CONFIG_MODULES_MY_CONTROLLER YES

2.4 板级支持包(BSP)与硬件抽象层

PX4通过板级支持包(Board Support Package,BSP)和硬件抽象层(Hardware Abstraction Layer,HAL)实现对不同硬件平台的支持。这种设计使得同一套飞控代码可以运行在不同的硬件上,也可以以SITL方式运行在普通计算机上。

2.4.1 BSP目录结构

每个硬件平台都有对应的BSP目录,以Pixhawk 4(fmu-v5)为例:

boards/px4/fmu-v5/

├── default.px4board # 默认构建配置(启用哪些模块)

├── bootloader.px4board # Bootloader构建配置

├── nuttx-config/ # NuttX内核配置

│ ├── include/

│ │ └── board.h # 板级头文件(引脚定义、时钟配置)

│ └── Kconfig # 内核配置选项

├── src/

│ ├── board_config.h # 板级硬件配置头文件

│ ├── board_identity.c # 板级身份识别

│ ├── init.c # 板级初始化

│ ├── led.c # LED控制

│ ├── spi.c # SPI总线配置

│ └── timer_config.c # 定时器配置(PWM输出)

└── CMakeLists.txt # 板级构建配置

2.4.2 硬件抽象层(HAL)

PX4的硬件抽象层位于src/platforms/目录,它封装了NuttX的原生API,为上层模块提供统一的接口。主要包括:

• px4_platform_common:提供模块基类、日志、参数系统、锁等基础设施

• px4_workqueue:工作队列抽象,支持任务调度和定时触发

• px4_log:统一日志接口,支持多级别日志输出

• hrt(High Resolution Timer):高精度时间戳接口,精度为微秒级

以下是PX4 HAL中常用的基础接口示例:

// 高精度时间戳

#include <drivers/drv_hrt.h>

hrt_abstime now = hrt_absolute_time(); // 获取当前时间戳(微秒)

hrt_abstime elapsed = hrt_elapsed_time(&start_time); // 计算经过时间

// 日志输出

#include <px4_platform_common/log.h>

PX4_INFO(“Module started successfully”); // 信息级别

PX4_WARN(“Sensor data timeout!”); // 警告级别

PX4_ERR(“Failed to initialize driver: %d”, ret); // 错误级别

PX4_DEBUG(“Debug value: %f”, value); // 调试级别(可编译时禁用)

// 参数系统

#include <px4_platform_common/module_params.h>

// 在类中声明参数

DEFINE_PARAMETERS(

(ParamFloatpx4::params::MC_ROLLRATE_P) _param_mc_rollrate_p,

(ParamIntpx4::params::MPC_XY_VEL_MAX) _param_mpc_xy_vel_max

)

// 访问参数值

float kp = _param_mc_rollrate_p.get();

int max_vel = _param_mpc_xy_vel_max.get();

2.4.3 设备驱动框架

PX4的设备驱动位于src/drivers/目录,按传感器类型组织。每个驱动通常继承自I2CSPI驱动基类,以简化硬件访问:

// 典型的I2C/SPI传感器驱动框架

#include <drivers/device/spi.h>

#include <lib/drivers/accelerometer/PX4Accelerometer.hpp>

#include <lib/drivers/gyroscope/PX4Gyroscope.hpp>

class ICM42688P : public device::SPI, public I2CSPIDriver

{

public:

ICM42688P(const I2CSPIDriverConfig &config);

~ICM42688P() override;

static I2CSPIDriverBase *instantiate(const I2CSPIDriverConfig &config, int runtime_instance);

static void print_usage();

void RunImpl(); // 周期性调用的主函数

private:

int init() override; // 初始化传感器

int probe() override; // 探测设备是否存在

PX4Accelerometer _px4_accel; // 加速度计数据处理器

PX4Gyroscope _px4_gyro; // 陀螺仪数据处理器

// 寄存器访问

uint8_t RegisterRead(Register reg);

void RegisterWrite(Register reg, uint8_t value);

};

2.4.4 启动脚本与系统初始化

PX4的启动过程由ROMFS中的启动脚本控制。启动脚本使用NSH语法,定义了系统启动时加载哪些模块及其参数:

# ROMFS/px4fmu_common/init.d-posix/rcS

# SITL启动脚本示例

# 加载板级配置

set VEHICLE_TYPE mc # 设置载体类型为多旋翼

set MIXER quad_x # 使用X型四旋翼混控

# 启动核心模块

dataman start # 数据管理器

param load # 加载参数

commander start # 飞行状态机

navigator start # 导航模块

ekf2 start # 状态估计

mc_att_control start # 多旋翼姿态控制

mc_pos_control start # 多旋翼位置控制

mc_rate_control start # 多旋翼角速率控制

control_allocator start # 执行器分配

# 启动通信模块

mavlink start -x -u 14556 -r 4000000 # MAVLink UDP

mavlink boot_complete # 通知启动完成

2.4.5 SITL与真实硬件的差异

理解SITL与真实硬件的差异对于开发者非常重要。两者在架构层面的主要区别如下:

表2-8 SITL与真实硬件对比

维度真实硬件(Pixhawk)SITL****仿真
操作系统NuttX RTOSLinux/macOS 原生进程
传感器数据真实硬件驱动读取仿真器模拟生成
PWM输出硬件PWM信号虚拟执行器接口
通信接口串口/CAN/I2C/SPIUDP/TCP网络
时间基准硬件定时器系统时钟
文件系统SD卡(FAT32)主机文件系统
实时性确定性保证非确定性(受主机负载影响)

尽管SITL在实时性方面不如真实硬件,但它的代码路径与真实硬件相同——所有的控制算法、uORB消息流、参数系统都是同一套代码。因此,在SITL中验证的算法可以直接移植到真实硬件上,只需调整参数即可。

2.5 本章小结

本章深入剖析了PX4固件的体系结构。我们了解了NuttX RTOS的实时调度机制、NSH调试命令,PX4的五层软件架构和uORB消息总线的发布-订阅模型,以及SITL与真实硬件的架构差异。这些知识是后续学习传感器驱动、控制算法和导航逻辑的基础。从下一章开始,我们将进入具体的功能模块,首先从地面站和参数配置开始。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐