4. RTOS选型与移植:FreeRTOS、RT-Thread、uCOS-III对比,任务调度、中断管理、内存管理,移植实战

做飞控这些年,我换过好几套RTOS。从最早的裸机跑大循环,到后来用uCOS-III,再到全面转向FreeRTOS和RT-Thread。说实话,选型这件事没有绝对的对错,关键看你的项目阶段和团队能力。

今天我就把这三款主流RTOS掰开揉碎了讲。你听完之后,应该能自己判断:我的飞控项目,到底该用哪个?

4.1 三款RTOS的出身与定位

先说说它们的背景。这决定了你能拿到什么样的技术支持。

特性 FreeRTOS RT-Thread uCOS-III
开源协议 MIT(商业友好) Apache 2.0 商业授权(收费)
内核大小 极小(3-6KB) 中等(6-12KB) 较大(10-20KB)
社区活跃度 极高(全球) 高(国内为主) 低(逐渐萎缩)
学习曲线 平缓 中等 陡峭
典型应用 IoT、小型飞控 智能硬件、工业 军工、航空航天

我个人习惯把FreeRTOS当作「万金油」。它轻量、稳定、文档全。RT-Thread更像一个「全家桶」,自带文件系统、网络协议栈。uCOS-III嘛,嗯,除非客户指定,否则我一般不推荐新项目用了。

4.2 任务调度:谁在什么时候干活?

飞控里最核心的就是任务调度。传感器采集、姿态解算、控制输出、通信处理,这些任务优先级怎么排?

三款RTOS都支持抢占式调度。但细节上有差异:

  • FreeRTOS:固定优先级+时间片轮转。优先级0最低,configMAX_PRIORITIES-1最高。同优先级任务轮流跑。
  • RT-Thread:256级优先级(可配置)。支持同优先级时间片,还支持软实时调度。
  • uCOS-III:无限优先级(理论上)。支持时间片轮转,但配置复杂。

我的经验:飞控里一般设3-4个优先级就够了。IMU采集最高,姿态解算次之,通信和日志最低。优先级太多反而容易出问题。

举个例子,FreeRTOS创建任务的代码:

// 创建姿态解算任务,优先级设为3
xTaskCreate(
    attitude_control_task,    // 任务函数
    "AttitudeCtrl",           // 任务名
    512,                      // 栈深度(字)
    NULL,                     // 参数
    3,                        // 优先级
    NULL                      // 任务句柄
);

这里要注意栈深度的设置。我踩过坑——栈给太小,任务跑着跑着就崩了。给太大,RAM又不够。一般先给个保守值,然后用uxTaskGetStackHighWaterMark()查看实际使用量,再调整。

4.3 中断管理:别让中断把系统拖垮

飞控里中断特别多。SPI接收IMU数据、UART接收遥控器信号、定时器触发控制循环。中断管理不好,系统就废了。

三款RTOS的中断处理思路差不多:

  • 中断服务函数(ISR)里只做最轻量的操作,比如读寄存器、清标志位。
  • 真正的处理逻辑放到任务里,通过信号量或消息队列通知。
  • 中断优先级要高于任务优先级,否则会出优先级反转。

FreeRTOS里有个特殊点:它要求中断优先级不能高于configMAX_SYSCALL_INTERRUPT_PRIORITY。否则中断里不能调用API函数。

我曾经犯过的错:在STM32上移植FreeRTOS时,没配置中断优先级分组。结果中断里调用了xSemaphoreGiveFromISR(),系统直接hard fault。查了两天才发现是优先级分组没设成4位抢占优先级。

RT-Thread在这方面做得更友好。它用rt_interrupt_enter()和rt_interrupt_leave()来标记中断上下文,API调用更安全。

4.4 内存管理:堆和栈的博弈

飞控的RAM通常很紧张。STM32F4系列也就192KB,F7系列512KB。怎么分配内存是个技术活。

三款RTOS的内存管理策略:

方案 FreeRTOS RT-Thread uCOS-III
静态分配 支持(heap_1/2/3) 支持 支持
动态分配 支持(heap_4/5) 支持(memheap) 支持
碎片处理 heap_4有合并算法 slab分配器 有但效率低
推荐用法 静态分配为主 动态分配为主 静态分配

我个人习惯在飞控里用静态分配。任务栈、队列、信号量都在初始化时创建好。运行时尽量不动态申请释放内存。为什么?因为动态分配可能产生碎片,飞控飞着飞着突然malloc失败,那就麻烦了。

小技巧:用FreeRTOS的heap_4方案时,把堆大小设成2的幂次方。比如16KB、32KB。这样内部合并算法效率最高。

4.5 移植实战:从STM32F4到F7

说一千道一万,不如动手做一遍。我拿FreeRTOS移植到STM32F7为例,讲讲关键步骤。

第一步:准备基础工程

先跑通一个裸机工程。GPIO、定时器、串口都能正常工作。这是移植的前提。

第二步:添加FreeRTOS源码

把FreeRTOS的Source文件夹拷进来。注意要包含portable目录下对应MCU的移植文件。STM32F7用GCC的话,选GCC/ARM_CM7。

第三步:配置FreeRTOSConfig.h

#define configCPU_CLOCK_HZ          ((unsigned long)216000000)
#define configTICK_RATE_HZ          ((TickType_t)1000)
#define configMAX_PRIORITIES        (5)
#define configMINIMAL_STACK_SIZE    ((unsigned short)128)
#define configTOTAL_HEAP_SIZE       ((size_t)(32 * 1024))
#define configUSE_PREEMPTION        1
#define configUSE_IDLE_HOOK         0
#define configUSE_TICK_HOOK         0

这里configCPU_CLOCK_HZ一定要和实际MCU主频一致。我见过有人设错了,结果系统时钟快了10倍,任务调度全乱套。

第四步:实现Systick中断

FreeRTOS需要一个1ms的定时器中断。STM32F7用Systick最方便:

void SysTick_Handler(void)
{
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
    {
        xPortSysTickHandler();
    }
}

第五步:修改中断优先级

STM32F7默认中断优先级分组是5位抢占优先级。FreeRTOS要求至少4位。在SystemInit()里加上:

NVIC_SetPriorityGrouping(3); // 4位抢占 + 0位子优先级

第六步:测试

创建两个任务,一个闪灯,一个打印日志。跑起来看看调度是否正常。

避坑指南:移植完成后,先用vTaskStartScheduler()启动调度器。如果系统卡住,八成是Systick没配置好,或者中断优先级冲突。用调试器看PC指针停在哪儿,基本能定位问题。

4.6 选型建议:到底选哪个?

说了这么多,我直接给结论:

  • 原型验证阶段:用FreeRTOS。上手快,资料多,出了问题好查。
  • 量产产品:如果团队熟悉FreeRTOS,继续用。如果想功能更丰富,考虑RT-Thread。
  • 特殊行业(军工、医疗):uCOS-III有认证,但成本高。除非客户要求,否则不推荐。

我个人现在的主力是FreeRTOS + 自己封装的一些组件。轻量、可控、出了问题自己能搞定。RT-Thread我也用,但主要是在需要文件系统和网络功能的项目里。

嗯,最后说一句:RTOS只是工具,不是目的。把飞控算法写好、把硬件调稳,比纠结用哪个RTOS重要得多。你想想看,是不是这个理?