API 一晚上能背完。真正难的是四件事:它什么时候切换、什么叫安全的共享、内存从哪来、中断里能做什么。这几块没搞明白,系统照样能跑,就是上不了产品。
FreeRTOS 是优先级抢占 + 同优先级时间片。理解这一点,很多「莫名其妙」的行为就都有解释了。
要建立的直觉:任何时刻,CPU 跑的都是「就绪态里优先级最高的任务」中最早就绪的那个。高优先级任务一旦就绪,立刻抢占——不管低优先级任务正在做什么。
常见误解:以为 vTaskDelay(0) 是「让一下」。它其实是「立即重新放入就绪列表」,同优先级才会让,高优先级本来就在跑。
数字方向:FreeRTOS 里数字越大优先级越高。和某些 RTOS(以及 Linux 的 nice 值)相反,写错方向会导致任务饿死,而且不报错。
空闲任务:优先级最低,负责回收被删除任务的栈。如果你把空闲任务钩子写成了耗时操作,低优先级任务会集体卡住。
验收:画出你的系统的任务优先级表,标出每个任务阻塞在什么对象上,并解释为什么这么排。画不出来,说明还没设计过。
队列、二值信号量、计数信号量、互斥量、递归互斥量、事件组、任务通知——七种东西,很多人只会用两种。
选择顺序:传数据用队列;单纯唤醒用任务通知(最轻);保护资源用互斥量;统计事件次数用计数信号量;等多个条件同时满足用事件组。
关键差别:互斥量有优先级继承,二值信号量没有。拿二值信号量当锁用,就是在给自己埋优先级反转。
递归互斥量:同一任务需要重复获取同一把锁时才用。普通互斥量重入会自死锁。
别做的事:用全局变量 + 关中断来传递数据。短临界区可以,但一旦涉及「等待」就必须用 RTOS 对象。
验收:统计你的工程里每一处共享数据,指出它是靠什么机制保护的。找不出来的那几处,就是将来的偶发 bug。
FreeRTOS 自带五种堆实现,很多人从头到尾用默认的 heap_4,从没想过为什么。
heap_1:只分配不释放。最简单、最确定,安全性要求高的产品(医疗、车规)常用它——彻底杜绝碎片。
heap_2:可释放但不合并相邻块。会碎片化,已不推荐。
heap_4:可释放并合并,最常用。长期运行 + 频繁分配释放,仍然会碎。
heap_5:支持多块不连续内存(片内 SRAM + 外部 SDRAM)。
更重要的一点:产品的正确姿势是所有对象在初始化阶段一次性创建,运行期不再动态申请。这样连 heap_1 都够用,也就不用担心碎片。
验收:能说出你的项目用哪种堆、为什么,以及运行 72 小时后 xPortGetFreeHeapSize() 的变化曲线。
FreeRTOS 有一条硬规则:中断里只能调 ...FromISR() 版本的 API。违反它的后果不是报错,是随机崩溃。
为什么:普通 API 会尝试阻塞、会操作调度器的临界区,而中断上下文里不能阻塞,也不能被自己打断。
优先级天花板:configMAX_SYSCALL_INTERRUPT_PRIORITY 之下的中断才能调用 FreeRTOS API;高于它的中断不受 RTOS 管理,也不会被临界区屏蔽。配错这一项,表现是「偶发丢中断」或「临界区保护失效」。数字方向又和直觉相反——数值越大优先级越低。
退出中断要报告:在 ISR 末尾用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken),否则任务要等到下一个 tick 才被调度,实时性直接损失一个 tick 周期。
临界区要短:taskENTER_CRITICAL() 会关中断。在里面做 Flash 擦写或长循环,等于把系统实时性废掉。
验收:能画出一次「串口中断 → 唤醒任务 → 任务处理」的完整路径,并说明每一步的上下文和能不能阻塞。
假设要做一个采样、上报、带屏显和报警的小设备。这是我实际会写的划分,不是标准答案,但每个数字都有来由。
| 任务 | 优先级 | 栈 | 阻塞在什么上 | 干什么 |
|---|---|---|---|---|
| 采集 | 4(最高) | 512 字 / 2 KB | ADC 转换完成信号量 | 每 10 ms 读一轮通道,结果写进数据队列 |
| 通信 | 3 | 512 字 / 2 KB | 数据队列、发送完成通知 | 取数据、组帧、算校验,走串口 DMA 发出 |
| 逻辑 | 2 | 768 字 / 3 KB | 数据队列(另一份拷贝) | 滤波、比阈值、算报警状态,写进共享状态 |
| 人机 | 1 | 512 字 / 2 KB | 按键队列 + 20 ms 周期延时 | 刷屏、处理按键,读逻辑任务的报警状态 |
| 日志 | 1 | 384 字 / 1.5 KB | 日志队列 | 写 Flash,队列满时丢最旧的并计数 |
留言 / 提问
写你卡住的地方:什么芯片、什么现象、已经试过什么。频率高的问题我会写进页面。昵称不填也能发。