第1章:FreeRTOS基础:FreeRTOS简介、任务与调度器、任务状态机、任务优先级
1.1 FreeRTOS到底是什么?
先说说FreeRTOS。很多刚入行的朋友问我,它到底是个啥?
说白了,FreeRTOS就是一个轻量级的实时操作系统内核。专门给嵌入式设备用的。我最早接触它是在2012年,那时候还在做四轴飞行器的飞控。当时项目里需要同时处理传感器读取、姿态解算、PWM输出、遥控器信号接收……一堆事情。如果用裸机轮询,代码写起来简直要命。
FreeRTOS的出现,让我可以把这些任务拆开,各自独立运行。嗯,就像你同时用手机听歌、回微信、刷短视频——虽然CPU只有一个核,但通过快速切换,感觉就像在并行处理。
核心特点:
- 开源免费,商业友好
- 代码量极小,ROM占用约4-9KB
- 支持抢占式、合作式、时间片调度
- 任务数量无限制(受内存限制)
- 丰富的IPC机制:队列、信号量、互斥量、事件组
我在飞控项目中用过uC/OS-II,也试过RT-Thread。但FreeRTOS的生态最成熟,文档最全。尤其是它的任务调度机制,非常适合飞控这种对实时性要求苛刻的场景。
1.2 任务与调度器——飞控的灵魂
任务是什么?在FreeRTOS里,每个任务就是一个无限循环的函数。它有自己的栈空间、优先级和状态。
调度器呢?它就是那个决定「现在该运行哪个任务」的家伙。我习惯把调度器想象成一个交通警察——哪个任务该走,哪个该停,全由它说了算。
飞控里典型的任务划分是这样的:
| 任务名称 | 优先级 | 周期 | 说明 |
|---|---|---|---|
| 传感器采集 | 最高 | 1ms | 读取IMU数据 |
| 姿态解算 | 高 | 2ms | 计算欧拉角 |
| 控制律计算 | 高 | 4ms | PID输出 |
| 遥控器接收 | 中 | 10ms | 解析PWM信号 |
| 日志输出 | 低 | 100ms | 串口打印 |
你看,每个任务都有明确的周期和优先级。调度器会保证高优先级的任务先运行。我曾经在调试一架四轴时,发现姿态解算偶尔会卡顿。查了半天,原来是日志输出任务占用了太多CPU时间。把它的优先级调低后,问题就解决了。
避坑指南:我曾经把传感器采集任务的优先级设得不够高,结果飞控在高速飞行时姿态数据更新不及时,差点炸机。记住:传感器读取永远是最高优先级。
1.3 任务状态机——任务的一生
每个任务在FreeRTOS里都有四种状态:
- 运行态(Running):正在使用CPU
- 就绪态(Ready):可以运行,但CPU被别的任务占着
- 阻塞态(Blocked):等待某个事件(延时、信号量、队列等)
- 挂起态(Suspended):被vTaskSuspend()挂起,需要手动恢复
为什么会这样设计?你想想看,如果所有任务都在抢CPU,那系统就乱套了。状态机让每个任务都知道自己「该干嘛」。
我画了一张状态转换图,帮你理解:
这张图我建议你保存下来。每次写任务代码时,想想你的任务现在处于哪个状态。我在调试飞控时,经常用vTaskList()打印所有任务的状态,一眼就能看出哪个任务在「偷懒」。
注意:阻塞态和挂起态的区别。阻塞是任务自己主动调用延时或等待信号量,挂起是被别的任务强制暂停。我曾经在项目中误用了挂起,导致一个关键任务永远无法恢复——因为挂起它的任务自己先挂了。
1.4 任务优先级——谁说了算?
优先级是FreeRTOS调度器的核心。数值越大,优先级越高。比如优先级5的任务,永远比优先级3的任务先运行。
但这里有个坑:如果两个任务优先级相同,FreeRTOS会使用时间片轮转调度。每个任务运行一个时间片(通常1ms),然后切换。这在飞控里要小心——如果两个高优先级任务都是1ms周期,时间片轮转会导致调度抖动。
我个人的习惯是:
- 飞控里最多用5个优先级
- 关键任务(传感器、控制)用最高优先级
- 非关键任务(日志、LED)用最低优先级
- 避免同优先级任务过多
来看一个实际的任务创建代码:
// 飞控任务创建示例
void vCreateFlightTasks(void)
{
// 传感器采集 - 最高优先级
xTaskCreate(vSensorTask, "Sensor", 256, NULL, 5, NULL);
// 姿态解算 - 高优先级
xTaskCreate(vAttitudeTask, "Attitude", 512, NULL, 4, NULL);
// 控制律计算 - 高优先级
xTaskCreate(vControlTask, "Control", 512, NULL, 4, NULL);
// 遥控器接收 - 中优先级
xTaskCreate(vRCReceiverTask, "RC", 128, NULL, 3, NULL);
// 日志输出 - 低优先级
xTaskCreate(vLogTask, "Log", 256, NULL, 1, NULL);
}
注意看,姿态解算和控制律计算都是优先级4。为什么?因为它们是串行执行的——先解算姿态,再计算控制量。如果设成不同优先级,反而可能因为调度延迟导致控制周期不稳定。
经验之谈:我曾经把控制律任务的优先级设得比姿态解算还高,结果每次控制律运行时,姿态数据还是旧的。嗯,这就像你开车时先踩油门再看路——肯定要出问题。正确的做法是:数据生产者(传感器)优先级最高,数据消费者(控制)次之。
1.5 本章小结
这一章我们聊了FreeRTOS的基础。记住三个核心点:
- 任务是飞控的基本执行单元,每个任务做一件事
- 调度器根据优先级决定谁运行,抢占式调度保证实时性
- 状态机让任务知道自己该干嘛,阻塞态是节省CPU的关键
下一章,我们会深入任务创建的细节,包括栈大小怎么算、任务句柄怎么用、以及那些让我踩过坑的API参数。嗯,到时候再聊。
公众号:蓝海资料掘金营,微信deep3321