3、飞控系统软件架构:实时操作系统(RTOS)基础、任务调度与管理、飞控软件分层设计
好,咱们今天聊聊飞控的软件架构。
很多人一上来就啃代码,结果越看越迷糊。为什么?因为没搞懂架构。飞控软件不是写个死循环就能跑的,它背后有一套严谨的“操作系统”在撑着。
我个人习惯,在讲具体代码之前,先把这个“骨架”搭清楚。你想想看,一架无人机在天上飞,每秒要处理几百次传感器数据、控制电机、响应遥控器指令。如果软件乱成一锅粥,那飞机肯定秒炸。
3.1 为什么飞控必须用RTOS?
说白了,普通电脑上的Windows或Linux,是“分时”系统。它尽量让每个程序都公平地分到CPU时间。但飞控不行。
飞控需要的是“实时”系统。什么叫实时?就是“在规定的时间内必须完成规定的事”。
举个例子:
- 姿态控制环:每1毫秒必须算一次,晚0.5毫秒飞机就可能抖。
- GPS数据读取:每10毫秒读一次,晚一点问题不大。
- 遥控器信号:一旦丢失,必须在5毫秒内触发保护。
你看,这些任务对时间的要求完全不一样。如果用裸机写一个大循环,一个任务卡住了,后面的全完蛋。
核心结论:RTOS(实时操作系统)能保证高优先级任务“准时”执行,不会被低优先级任务拖后腿。这是飞控稳定性的基石。
我在项目中遇到过,有人用FreeRTOS跑飞控,结果任务优先级设反了。低优先级的日志打印任务,把高优先级的控制任务给堵死了。飞机一离地就抽风。嗯,这种坑踩过一次就记住了。
3.2 任务调度与管理:谁先跑?谁后跑?
RTOS的核心就是“调度器”。它决定下一个该执行哪个任务。
飞控里最常用的调度策略是抢占式优先级调度。什么意思?就是高优先级的任务可以“抢”低优先级任务的CPU使用权。
3.2.1 任务优先级设计
我一般把飞控任务分成这么几档:
| 优先级 | 任务名称 | 周期 | 说明 |
|---|---|---|---|
| 最高 | 姿态控制 | 1ms | 最核心,不能被打断 |
| 高 | 传感器读取(IMU) | 1ms | 与控制任务同步 |
| 中 | 位置控制 | 10ms | 基于姿态控制之上 |
| 低 | 遥控器接收 | 10ms | 允许偶尔延迟 |
| 最低 | 日志/遥测 | 100ms | 丢了也无所谓 |
我的经验:千万别把日志打印任务的优先级设高。我曾经为了调试方便,把printf任务优先级设得比控制任务还高。结果飞机在天上打印日志,控制环被饿死,直接炸机。血的教训。
3.2.2 任务间通信
任务之间怎么交换数据?比如传感器任务读到了数据,怎么给控制任务?
RTOS提供了几种机制:
- 队列(Queue):最常用。一个任务往里写,一个任务往外读。数据安全,不会冲突。
- 信号量(Semaphore):用来同步。比如传感器任务读完数据后,发个信号量唤醒控制任务。
- 互斥锁(Mutex):保护共享资源。比如两个任务都要修改同一个全局变量,就得加锁。
我个人习惯,飞控里尽量用队列。为什么?因为队列自带“缓冲”功能。如果传感器任务跑得比控制任务快,数据可以先存起来,不会丢。
注意:不要在中断服务函数里调用阻塞型的队列发送函数!我曾经犯过这个错,在IMU中断里直接往队列写数据,结果队列满了,中断被卡住,整个系统死机。正确的做法是在中断里只发信号量,把数据写入的工作放到任务里做。
3.3 飞控软件分层设计
好的飞控软件,一定是分层的。就像盖楼一样,底层负责搬砖,上层负责装修。各层之间通过明确的接口通信。
我常用的分层结构是这样的:
+----------------------------------+
| 应用层 (App) |
| - 航线规划 |
| - 定高/定点/返航 |
| - 光流/视觉处理 |
+----------------------------------+
| 控制层 (Control) |
| - 姿态控制器 (PID) |
| - 位置控制器 |
| - 电机混控 |
+----------------------------------+
| 估计层 (Estimation) |
| - 姿态解算 (Mahony/EKF) |
| - 位置/速度估计 |
| - 传感器融合 |
+----------------------------------+
| 驱动层 (Driver) |
| - IMU读取 (SPI/I2C) |
| - 电机PWM输出 |
| - GPS/UART通信 |
+----------------------------------+
| 硬件抽象层 (HAL) |
| - MCU外设初始化 |
| - 时钟/定时器配置 |
| - 中断管理 |
+----------------------------------+
3.3.1 硬件抽象层(HAL)
这一层直接跟芯片打交道。比如配置GPIO、SPI、I2C、定时器。
为什么要抽象出来?因为飞控可能会换芯片。比如从STM32F4换到F7,甚至换到国产芯片。只要HAL层接口不变,上面的代码几乎不用改。
我见过最惨的案例:有人把SPI读写函数直接写在了姿态解算代码里。后来换了个传感器,SPI时序不一样,结果改代码改到崩溃。你想想看,姿态解算和SPI读写,根本是两码事,为什么要混在一起?
3.3.2 驱动层
这一层负责跟具体的外设打交道。比如读MPU6050、读MS5611气压计、控制ESC电调。
驱动层的输出是“干净的传感器数据”。比如:
- 加速度计:返回 (ax, ay, az) 单位 m/s²
- 陀螺仪:返回 (gx, gy, gz) 单位 rad/s
- 气压计:返回高度 单位 m
注意,驱动层只负责“读”和“写”,不做任何数学运算。数学运算交给上层。
3.3.3 估计层
这是飞控的“大脑”。传感器数据进来,经过滤波、融合,输出姿态、位置、速度。
常用的算法:
- Mahony互补滤波:简单、计算量小,适合低性能MCU。
- EKF(扩展卡尔曼滤波):精度高,但计算量大,需要浮点运算单元。
我个人建议,初学者先用Mahony。为什么?因为EKF调参能调到怀疑人生。我当年调EKF的噪声矩阵,调了整整两周,飞机还是飘。后来换成Mahony,半小时就稳了。
3.3.4 控制层
这一层接收估计层算出来的姿态和位置,然后算出电机应该转多快。
典型的控制链路:
- 期望姿态(来自遥控器或航线)
- 当前姿态(来自估计层)
- PID控制器算出期望力矩
- 混控器将力矩分配到4个电机
- 输出PWM给电调
关键点:控制层必须跑在最高优先级。因为它是飞控的“最后一道防线”。如果控制层延迟了,飞机就会失控。
3.3.5 应用层
这一层离硬件最远,离用户最近。比如:
- 一键起飞/降落
- 航线飞行
- 避障逻辑
- 故障保护(比如GPS丢失后自动悬停)
应用层的代码可以写得“随意”一点,因为即使它卡住了,控制层和估计层还能正常工作,飞机不会立刻炸。
3.4 小结
好了,这一章的内容就这些。总结一下:
- 飞控必须用RTOS,保证实时性。
- 任务优先级要设计好,控制任务最高,日志任务最低。
- 任务间通信用队列,别在中断里干重活。
- 软件要分层,每层各司其职,接口清晰。
下一章,咱们开始动手写代码。我会带着你从零搭建一个RTOS工程,把今天讲的这些理论落到实处。
记住,架构设计花的时间越多,后面写代码就越顺。别急着敲键盘,先把图画清楚。