2. RTOS选型与配置:FreeRTOS vs RT-Thread,任务优先级设计,内核裁剪
好,咱们直接进入正题。这一章聊的是RTOS选型和配置,说白了就是给窗帘电机找个合适的“大脑操作系统”。我这些年做嵌入式项目,选错RTOS的坑踩过不少,今天把经验掰开揉碎了讲给你听。
2.1 FreeRTOS vs RT-Thread:怎么选?
先说说这两个主流RTOS。我个人习惯是,先看项目资源再选型,别盲目跟风。
| 对比项 | FreeRTOS | RT-Thread |
|---|---|---|
| 内核大小 | 极小,ROM 4-9KB | 标准版约20KB,nano版约3KB |
| 任务数量 | 无硬限制,但建议≤32 | 无硬限制,支持动态创建 |
| 生态组件 | 少,需自己移植 | 丰富,有设备驱动框架 |
| 学习曲线 | 平缓,文档清晰 | 稍陡,但中文资料多 |
| 商业许可 | MIT,无额外费用 | Apache 2.0,商业友好 |
我在项目中遇到过这样的情况:一个窗帘电机项目,MCU是STM32F030,Flash只有64KB。当时团队有人坚持用RT-Thread标准版,结果编译完固件直接超了30%。后来换成FreeRTOS,内核只占了不到8KB,剩下的空间全给业务逻辑了。
我的建议是:
- 如果MCU资源紧张(Flash < 128KB,RAM < 20KB),优先选FreeRTOS
- 如果项目需要网络、文件系统等复杂功能,RT-Thread的组件生态能省很多事
- 如果团队都是新手,FreeRTOS的文档和社区支持更友好
2.2 任务优先级设计:别让电机“饿死”
任务优先级设计,说白了就是决定“谁先干活”。窗帘电机系统里,任务优先级设计不好,轻则响应迟钝,重则电机失控。
我一般把任务分成三类:
- 硬实时任务: 比如电机堵转检测、限位开关响应。优先级最高,必须立即处理
- 软实时任务: 比如窗帘位置控制、速度调节。优先级中等,允许少量延迟
- 后台任务: 比如按键扫描、状态上报。优先级最低,有空再做
举个例子,一个典型的窗帘电机系统任务优先级分配:
| 任务名称 | 优先级 | 说明 |
|---|---|---|
| 电机堵转检测 | 5(最高) | 一旦堵转,必须在1ms内停止电机 |
| 限位开关中断处理 | 4 | 到达限位立即停止,防止机械损坏 |
| 窗帘位置控制 | 3 | 根据目标位置计算PWM占空比 |
| 按键扫描 | 2 | 20ms扫描一次,允许抖动 |
| Wi-Fi状态上报 | 1(最低) | 空闲时上报,延迟几秒无所谓 |
避坑指南: 我曾经犯过一个错误——把按键扫描任务的优先级设得比电机控制还高。结果用户按一下按键,电机就抖一下,因为按键任务抢占了电机控制的CPU时间。后来我把按键任务降到最低优先级,问题就解决了。
2.3 内核裁剪:给RTOS“瘦身”
内核裁剪,说白了就是去掉你用不到的功能。我见过不少项目,RTOS里一堆功能开着,结果Flash和RAM都浪费了。
以FreeRTOS为例,常用的裁剪配置项:
// FreeRTOSConfig.h 裁剪示例
#define configUSE_PREEMPTION 1 // 抢占式调度,必须开
#define configUSE_IDLE_HOOK 0 // 空闲钩子,不用就关
#define configUSE_TICK_HOOK 0 // 时钟钩子,不用就关
#define configUSE_CO_ROUTINES 0 // 协程,现在基本不用
#define configUSE_MUTEXES 1 // 互斥量,需要就开
#define configUSE_RECURSIVE_MUTEXES 0 // 递归互斥量,不用就关
#define configUSE_COUNTING_SEMAPHORES 0 // 计数信号量,不用就关
#define configUSE_QUEUE_SETS 0 // 队列集,不用就关
#define configUSE_TIMERS 1 // 软件定时器,需要就开
#define configUSE_TASK_NOTIFICATIONS 1 // 任务通知,轻量级通信
我个人的经验是:
- 先开所有功能,编译看看ROM和RAM占用
- 然后逐个关闭不需要的功能,每关一个就重新编译对比
- 最后保留最小功能集
举个例子,窗帘电机系统里,我一般会关掉:
- 协程(现在没人用这个了)
- 递归互斥量(电机控制不需要嵌套锁)
- 计数信号量(用队列替代更简单)
- 队列集(多个队列同步,窗帘系统用不到)
这样裁剪下来,FreeRTOS的内核可以从9KB降到5KB左右。你想想看,省下来的4KB Flash,够放多少电机控制算法了?
2.4 实际配置案例:一个窗帘电机项目的RTOS配置
最后,给你看一个我实际用过的配置。这个项目用的是FreeRTOS,MCU是STM32G030,Flash 64KB,RAM 8KB。
// FreeRTOSConfig.h 实际项目配置
#define configCPU_CLOCK_HZ ( ( unsigned long ) 64000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) // 1ms时钟节拍
#define configMAX_PRIORITIES ( 6 ) // 6级优先级
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 4 * 1024 ) ) // 4KB堆
// 任务栈大小分配
#define TASK_STACK_MOTOR_CONTROL ( 256 ) // 电机控制任务
#define TASK_STACK_KEY_SCAN ( 128 ) // 按键扫描任务
#define TASK_STACK_WIFI_REPORT ( 256 ) // Wi-Fi上报任务
// 裁剪选项
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configUSE_CO_ROUTINES 0
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_QUEUE_SETS 0
#define configUSE_TIMERS 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_TRACE_FACILITY 0 // 调试跟踪,发布版关掉
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
嗯,这里要注意:堆大小4KB看起来很小,但实际够用。因为窗帘电机系统里,动态内存分配主要用在创建任务和队列上,一旦系统启动完成,就不再动态分配了。所以4KB堆完全够用。
最后总结一下:RTOS选型看资源,优先级设计看实时性,内核裁剪看需求。这三步走对了,你的窗帘电机系统就能跑得又稳又快。