3. 飞控软件架构设计

飞控软件不是“把算法写进单片机”这么简单。它要在毫秒级周期内完成传感器采样、姿态解算、控制律计算、混控输出,同时还要处理遥控链路、导航任务、日志记录和故障保护。若所有代码平铺在一个大循环里,任何一处阻塞都会直接破坏控制周期。因此,飞控软件架构的核心目标只有一句话:用确定性的时间边界,把不同实时性要求的任务隔离在可控的层次中。

本节围绕“分层 + 模块化”展开,先建立整体分层视图,再逐层说明职责、接口与调度关系,最后给出可落地的模块划分与配置示例。

一、分层结构:从硬件到应用的六层视图

典型的飞控软件自上而下可分为应用层、中间件层、实时操作系统层、驱动层、硬件抽象层与物理硬件。各层之间通过明确定义的接口通信,上层不直接操作寄存器,下层不感知飞行逻辑。

应用层 Application 姿态控制律 · 位置控制器 · 导航制导 · 任务管理 · 故障保护 · 混控输出 中间件层 Middleware uORB 发布/订阅 · MAVLink 协议栈 · 参数系统 · 日志服务 · 时间同步 · 消息队列 实时操作系统 RTOS 任务调度器 · 优先级管理 · 信号量/互斥锁 · 定时器 · 内存管理 · 中断管理 驱动层 Drivers IMU驱动 · 磁力计驱动 · GPS驱动 · 气压计驱动 · PWM/DShot输出 · 串口/CAN驱动 硬件抽象层 HAL SPI/I2C/UART 寄存器封装 · 时钟配置 · DMA通道 · GPIO映射 · 中断向量表 物理硬件 Hardware MCU · 传感器 · 执行机构 · 通信模块 调用方向 数据/事件方向

图中左侧箭头表示“调用方向”:应用层调用中间件,中间件调用 RTOS 服务,RTOS 调用驱动,驱动经 HAL 操作硬件。右侧箭头表示“数据/事件方向”:传感器数据经 HAL 和驱动上报,RTOS 以信号量或消息通知中间件,中间件发布给应用层订阅者。这种双向分离是理解飞控数据流的关键。

二、实时操作系统:确定性的基石

RTOS 在飞控中的核心价值不是“多任务”,而是可抢占的优先级调度和可预测的响应时间。以常见的 Cortex-M7 飞控(如 480MHz 主频)为例,姿态控制任务通常设为最高优先级,周期 1kHz(1ms),要求最坏执行时间(WCET)不超过 400μs,留出 60% 余量给中断和其他任务。

任务优先级分配遵循“速率单调调度”原则:周期越短、截止时间越紧的任务,优先级越高。典型配置如下:

任务名称优先级周期WCET预算职责
姿态控制最高(如 5)1ms≤400μs读取IMU、解算姿态、计算控制量、输出混控
位置控制高(如 4)5ms≤800μsGPS/光流融合、位置环、速度环
导航制导中(如 3)20ms≤1.5ms航点管理、航线切换、返航逻辑
通信任务低(如 2)事件驱动≤2msMAVLink 收发、地面站指令解析
日志记录最低(如 1)空闲时—SD卡写入、飞行数据存储

调度器在每个 SysTick 中断(通常 1kHz)中检查就绪队列,若高优先级任务就绪则立即抢占当前低优先级任务。任务间共享数据必须用互斥锁或双缓冲保护,避免优先级反转。例如 IMU 数据用双缓冲:驱动写入缓冲区 A 时,控制任务读取缓冲区 B,写完交换指针,无需阻塞。

三、驱动层与 HAL:隔离硬件差异

驱动层的职责是“把传感器变成结构化数据”。以 ICM-42688 IMU 为例,驱动初始化流程为:HAL 配置 SPI 时钟(如 8MHz)、片选 GPIO、DMA 通道;驱动写入配置寄存器设置量程(±16g / ±2000dps)和输出数据率(8kHz);随后在数据就绪中断中读取 12 字节原始数据,转换为物理量并打时间戳。

// 示例:IMU 驱动数据读取伪代码
void icm42688_read(float *accel, float *gyro, uint64_t *timestamp) {
    uint8_t buf[12];
    hal_spi_transfer(IMU_CS, IMU_REG_ACCEL_X, buf, 12);
    *accel = (int16_t)(buf[0]<<8|buf[1]) * ACCEL_SCALE;
    *gyro  = (int16_t)(buf[6]<<8|buf[7]) * GYRO_SCALE;
    *timestamp = hal_get_us();
}

HAL 层则把不同 MCU 的 SPI/I2C/UART 寄存器操作封装成统一函数,使驱动层代码在 STM32、GD32 或国产 MCU 间移植时只需替换 HAL 实现,驱动逻辑不变。这是模块化设计在硬件层面的直接体现。

四、中间件:解耦模块间的通信

中间件层解决“模块之间怎么说话”的问题。飞控中最常用的是发布/订阅模型(如 PX4 的 uORB)。姿态控制模块不直接调用 GPS 驱动,而是订阅 vehicle_gps_position 话题;GPS 驱动发布该话题。发布者与订阅者互不知晓对方存在,新增一个日志模块只需订阅同一话题,无需修改任何已有代码。

中间件还负责参数系统:所有可调参数(如 PID 增益、滤波截止频率)集中注册,地面站通过 MAVLink 读写,参数变更时通知相关模块重新加载。日志服务则把关键话题按固定频率写入 SD 卡,供事后分析。

五、应用层:飞行逻辑的最终表达

应用层由多个功能模块组成,每个模块是一个独立任务或话题处理函数。以姿态控制模块为例,其 1ms 周期内的执行序列为:

  1. 从中间件订阅 sensor_combined,获取融合后的姿态角与角速度;
  2. 计算姿态误差:e = setpoint - current;
  3. PID 计算:u = Kp*e + Ki*∫e + Kd*de/dt;
  4. 将控制量发布到 actuator_controls 话题;
  5. 混控模块订阅该话题,根据机型(四旋翼/固定翼)分配电机输出。

模块化设计的关键约束是:每个模块只通过话题接口通信,不直接访问其他模块的内部变量。这样,替换姿态估计算法(如从互补滤波换成 EKF)时,只需保证输出话题格式不变,控制模块无需改动。

模块化检查清单:新增一个功能模块时,确认它是否只依赖话题接口、是否注册了独立参数、是否能在不修改其他模块代码的前提下编译通过。若答案是否定的,说明模块边界划分有问题。

六、分层与模块化的落地要点

实际工程中,分层不是越细越好。层数过多会引入额外调用开销和调试复杂度。对于 1kHz 控制回路,中间件的话题发布/订阅若涉及内存拷贝,需评估其耗时;必要时对高频话题采用零拷贝共享内存。驱动层的中断服务程序应尽可能短,只做数据搬运和标志置位,复杂处理交给任务上下文。

另一个常见问题是优先级反转:低优先级任务持有互斥锁时被中优先级任务抢占,导致高优先级任务阻塞。解决方案是使用优先级继承互斥锁,或对共享数据采用无锁双缓冲。在飞控中,IMU 数据、遥控输入等高频共享数据优先采用双缓冲,参数等低频数据才用互斥锁。

最后,分层架构的验证应贯穿开发过程:用逻辑分析仪测量控制任务的实际执行时间,用示波器观察 PWM 输出的周期抖动,用地面站日志检查话题发布频率是否稳定。只有每一层的时序都满足预算,整机的控制品质才有保障。