3. 飞控软件架构设计
飞控软件不是“把算法写进单片机”这么简单。它要在毫秒级周期内完成传感器采样、姿态解算、控制律计算、混控输出,同时还要处理遥控链路、导航任务、日志记录和故障保护。若所有代码平铺在一个大循环里,任何一处阻塞都会直接破坏控制周期。因此,飞控软件架构的核心目标只有一句话:用确定性的时间边界,把不同实时性要求的任务隔离在可控的层次中。
本节围绕“分层 + 模块化”展开,先建立整体分层视图,再逐层说明职责、接口与调度关系,最后给出可落地的模块划分与配置示例。
一、分层结构:从硬件到应用的六层视图
典型的飞控软件自上而下可分为应用层、中间件层、实时操作系统层、驱动层、硬件抽象层与物理硬件。各层之间通过明确定义的接口通信,上层不直接操作寄存器,下层不感知飞行逻辑。
图中左侧箭头表示“调用方向”:应用层调用中间件,中间件调用 RTOS 服务,RTOS 调用驱动,驱动经 HAL 操作硬件。右侧箭头表示“数据/事件方向”:传感器数据经 HAL 和驱动上报,RTOS 以信号量或消息通知中间件,中间件发布给应用层订阅者。这种双向分离是理解飞控数据流的关键。
二、实时操作系统:确定性的基石
RTOS 在飞控中的核心价值不是“多任务”,而是可抢占的优先级调度和可预测的响应时间。以常见的 Cortex-M7 飞控(如 480MHz 主频)为例,姿态控制任务通常设为最高优先级,周期 1kHz(1ms),要求最坏执行时间(WCET)不超过 400μs,留出 60% 余量给中断和其他任务。
任务优先级分配遵循“速率单调调度”原则:周期越短、截止时间越紧的任务,优先级越高。典型配置如下:
| 任务名称 | 优先级 | 周期 | WCET预算 | 职责 |
|---|---|---|---|---|
| 姿态控制 | 最高(如 5) | 1ms | ≤400μs | 读取IMU、解算姿态、计算控制量、输出混控 |
| 位置控制 | 高(如 4) | 5ms | ≤800μs | GPS/光流融合、位置环、速度环 |
| 导航制导 | 中(如 3) | 20ms | ≤1.5ms | 航点管理、航线切换、返航逻辑 |
| 通信任务 | 低(如 2) | 事件驱动 | ≤2ms | MAVLink 收发、地面站指令解析 |
| 日志记录 | 最低(如 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 周期内的执行序列为:
- 从中间件订阅
sensor_combined,获取融合后的姿态角与角速度; - 计算姿态误差:
e = setpoint - current; - PID 计算:
u = Kp*e + Ki*∫e + Kd*de/dt; - 将控制量发布到
actuator_controls话题; - 混控模块订阅该话题,根据机型(四旋翼/固定翼)分配电机输出。
模块化设计的关键约束是:每个模块只通过话题接口通信,不直接访问其他模块的内部变量。这样,替换姿态估计算法(如从互补滤波换成 EKF)时,只需保证输出话题格式不变,控制模块无需改动。
六、分层与模块化的落地要点
实际工程中,分层不是越细越好。层数过多会引入额外调用开销和调试复杂度。对于 1kHz 控制回路,中间件的话题发布/订阅若涉及内存拷贝,需评估其耗时;必要时对高频话题采用零拷贝共享内存。驱动层的中断服务程序应尽可能短,只做数据搬运和标志置位,复杂处理交给任务上下文。
另一个常见问题是优先级反转:低优先级任务持有互斥锁时被中优先级任务抢占,导致高优先级任务阻塞。解决方案是使用优先级继承互斥锁,或对共享数据采用无锁双缓冲。在飞控中,IMU 数据、遥控输入等高频共享数据优先采用双缓冲,参数等低频数据才用互斥锁。
最后,分层架构的验证应贯穿开发过程:用逻辑分析仪测量控制任务的实际执行时间,用示波器观察 PWM 输出的周期抖动,用地面站日志检查话题发布频率是否稳定。只有每一层的时序都满足预算,整机的控制品质才有保障。