3、飞控软件架构总览:前后台系统、RTOS的选择与对比、分层架构设计

聊到飞控软件架构,我脑子里第一个蹦出来的问题就是:你到底需不需要一个操作系统?

这个问题,我在刚入行那会儿也纠结过。那时候做一个小四轴,觉得裸机跑得挺欢,干嘛要上RTOS?后来项目越做越大,任务一多,裸机那套轮询机制就开始捉襟见肘了。嗯,今天咱们就把这块掰开揉碎了讲清楚。

3.1 前后台系统:简单粗暴,但别滥用

前后台系统,说白了就是一个大循环(后台)加中断(前台)。

// 典型的前后台飞控主循环
void main(void)
{
    while(1)
    {
        // 后台:轮询执行
        sensor_read();      // 读取传感器
        attitude_estimate(); // 姿态解算
        control_calc();     // 控制计算
        pwm_output();       // 输出PWM
        comm_handle();      // 处理通信
    }
}

// 前台:中断服务
void TIM1_IRQHandler(void)
{
    // 1ms定时中断,标记采样时刻
    sensor_ready_flag = 1;
}

这种架构的优点很明显:代码简单、资源占用少、实时性可控。我在早期做一款玩具级飞控时,就用这种结构,跑得挺稳。

但问题也来了——任务之间互相阻塞。你想想看,如果通信处理卡住了,整个控制循环都得等着。这在飞控里是致命的。我曾经遇到过,串口接收一个长数据帧,导致姿态更新延迟了2ms,飞机直接抖成筛子。

避坑指南:前后台系统只适合任务数少于5个、且每个任务执行时间可控的场景。飞控里如果要做复杂的路径规划、视觉处理,趁早放弃裸机。

3.2 RTOS:FreeRTOS vs RT-Thread,我该怎么选?

说实话,现在做飞控,我个人的习惯是直接上RTOS。为什么?因为飞控天然就是多任务系统:传感器采集、姿态解算、控制输出、通信、日志、故障处理……这些任务优先级不同,执行周期不同,用RTOS管理起来太舒服了。

那FreeRTOS和RT-Thread怎么选?我列个表,你一看就明白。

对比项 FreeRTOS RT-Thread
内核大小 极小(3-6KB) 中等(6-12KB)
任务数量 无限制 无限制
IPC机制 队列、信号量、互斥量 邮箱、消息队列、信号量、事件集
组件生态 较少,需自己移植 丰富(Finsh、DFS、LWIP等)
学习曲线 平缓 中等
典型应用 资源受限的MCU 功能复杂的飞控系统

我的建议是:

  • 如果你用的是STM32F103这类资源紧张的芯片,做简单的四轴,FreeRTOS够用了
  • 如果你做的是带视觉、带RTK、带复杂日志的无人机,RT-Thread更合适。它的组件生态能帮你省很多事。
个人经验:我在一个项目中用FreeRTOS做飞控,后来需要加一个FTP上传日志的功能,结果发现FreeRTOS没有现成的文件系统支持,硬是自己撸了一个。如果当时用RT-Thread,直接挂DFS就完事了。

3.3 分层架构设计:让飞控代码不再是一团乱麻

不管你用裸机还是RTOS,分层架构都是必须的。我见过太多新手把传感器读取、姿态解算、控制输出全写在一个文件里,那维护起来简直是噩梦。

我个人习惯把飞控软件分成四层:

  1. 硬件抽象层(HAL):封装MCU外设和传感器驱动。换芯片?只改这一层。
  2. 核心算法层:姿态解算、控制律、导航算法。这部分是飞控的灵魂,与硬件无关。
  3. 任务管理层:基于RTOS的任务调度、优先级管理、通信机制。
  4. 应用层:遥控器处理、地面站通信、日志记录、故障处理。
// 分层架构示例(伪代码)
// 硬件抽象层
int mpu6050_read_accel(float *ax, float *ay, float *az);

// 核心算法层
void attitude_ekf_update(float gx, float gy, float gz, 
                         float ax, float ay, float az);

// 任务管理层(FreeRTOS)
void task_control(void *param)
{
    while(1)
    {
        // 等待传感器数据就绪
        xSemaphoreTake(sensor_sem, portMAX_DELAY);
        
        // 调用算法层
        attitude_ekf_update(gx, gy, gz, ax, ay, az);
        control_calc();
        
        // 输出到硬件层
        pwm_set(motor1, motor2, motor3, motor4);
    }
}

// 应用层
void task_comm(void *param)
{
    while(1)
    {
        // 处理遥控器数据
        rc_parse();
        // 发送遥测数据
        telemetry_send();
        vTaskDelay(10);
    }
}

你看,这样一分层,每个模块的职责就清晰了。换传感器?只改HAL层。改控制算法?只动核心算法层。加新功能?在应用层加个任务就行。

核心原则:层与层之间通过接口通信,不要跨层调用。比如应用层不能直接操作I2C寄存器,必须通过HAL层。

3.4 避坑指南:我踩过的那些坑

最后分享几个我实际项目中遇到的坑,希望能帮你少走弯路。

  • 任务优先级别设反了:我曾经把姿态解算任务的优先级设得比传感器采集还低,结果传感器数据来了,姿态解算还在等,导致控制周期不稳定。记住:传感器采集 > 姿态解算 > 控制输出 > 通信 > 日志
  • 中断里别做复杂运算:有个同事在定时器中断里直接做姿态解算,结果中断时间过长,导致其他中断丢失。中断里只做标记或数据拷贝,复杂运算交给任务。
  • 堆栈大小要留余量:FreeRTOS默认的任务堆栈大小往往不够。我习惯在调试阶段把堆栈设大两倍,稳定后再优化。

好了,这一章的内容就到这里。下一章咱们会深入讲讲任务调度与优先级设计,这可是飞控实时性的关键。到时候我会拿一个实际案例,带你一步步设计任务调度策略。