FreeRTOS 出问题时,表面症状经常很随机:
- 有时一加任务就崩
- 有时在中断里挂掉
- 有时刚启动调度器就死
- 有时只是加了几句
printf()就开始不稳定
但把常见案例看多了会发现,很多问题其实反复都落在几类原因上。最值得优先排的,通常就是下面三类:
- 中断优先级配置不对
- 任务栈不够或已经溢出
printf()/sprintf()用得太随意
1. 为什么中断优先级是 FreeRTOS 第一大坑
很多端口里,最容易把系统搞成“偶发崩溃”的,就是中断优先级设置错误,尤其是在:
- 中断支持嵌套
- ISR 里调用了 FreeRTOS API
这时候有一个硬限制:
如果某个中断里会调用 FreeRTOS API,那么它的优先级必须满足端口规定,通常不能高于 configMAX_SYSCALL_INTERRUPT_PRIORITY(或类似命名)。
一旦这个关系错了,后果通常不是立刻编译报错,而是:
- 临界区失效
- 上下文切换异常
- 系统随机挂死
这类问题最麻烦的地方正是“看起来不稳定、难复现”。
2. 为什么 Cortex-M 上更容易搞反
很多 Cortex-M 平台上,中断优先级的数字含义和直觉是反着的:
- 数值越小,逻辑优先级越高
如果把这个方向搞反,就很容易出现:
- 你以为自己把某个中断放低了
- 实际上却把它设成了非常高的优先级
于是只要这个 ISR 再去碰 FreeRTOS API,就很容易出问题。
3. 栈溢出为什么排第二
栈不够是 FreeRTOS 里另一类高频问题。它之所以讨厌,是因为很多崩溃症状看起来不像“单纯栈小了”,而像:
- 任务跑飞
- 随机 HardFault
- 某个任务偶尔异常退出
- 调度切换后系统状态很怪
在小型嵌入式系统里,任务栈往往分得比较保守,而一旦函数调用层级、局部变量、格式化输出或中断嵌套稍微多一点,就会逼近边界。
4. 用什么方法判断栈够不够
最实用的手段之一就是看高水位线。
uxTaskGetStackHighWaterMark() 能告诉你:
- 某个任务在运行期间
- 最深一次栈使用之后
- 还剩多少未使用空间
这个值越接近 0,越说明任务已经离栈溢出不远了。
如果你在调系统稳定性,别只看“当前有没有崩”,也要看“最坏时剩多少栈”。
5. 为什么建议把 configCHECK_FOR_STACK_OVERFLOW 打开
FreeRTOS 提供了运行时栈检查机制。最常见做法就是把 configCHECK_FOR_STACK_OVERFLOW 打开,并实现:
vApplicationStackOverflowHook()
这不会让系统 magically 恢复,但能极大提升定位效率。因为相比“系统随机死掉”,能明确知道“哪个任务栈炸了”,排查难度会低很多。
6. printf() 为什么经常把问题搞得更糟
很多人遇到问题第一反应是多打日志,这方向本身没错,但在 FreeRTOS 场景里,printf() 本身常常会带来额外风险:
- 占栈很大
- 运行时间长
- 有的实现并不线程安全
- 有的实现不适合在 ISR 中调用
- 有的实现内部还会碰
malloc()
结果就是:你原本只是想“多打一行日志看看到底怎么回事”,最后却把系统进一步拖进不稳定状态。
7. 为什么“只加一个任务就崩”经常不是任务本身的问题
这种场景里,常见原因其实很朴素:
- 堆不够了
- 任务栈不够了
- 调度器启动时还要额外创建空闲任务和守护任务
很多演示工程把 heap 配得刚刚够跑示例。你再加一个任务、队列或信号量,马上就把边界顶破。
所以一碰到“新建一个简单任务就挂”的情况,先别急着怀疑任务逻辑,先看:
- heap 还有多少
- 栈分得够不够
- 调度器是否正常启动
8. 为什么 ISR 里只能用 FromISR 版本 API
这是 FreeRTOS 里另一个非常基础但非常容易被忽视的原则:
- 普通 API 给任务上下文用
- 中断里只能用
...FromISR()版本
原因不是“命名风格不同”,而是它们在内部处理方式上就不是给同一种上下文准备的。
如果在 ISR 里直接调用普通 API,很容易出现:
- 临界区不匹配
- 调度器状态异常
- 返回路径不正确
9. 调度器一启动就崩时先查什么
如果问题发生在 vTaskStartScheduler() 附近,优先查这几件事:
- heap 是否足够创建空闲任务和 RTOS 守护任务
- FreeRTOS 的中断处理程序是否正确安装
- 启动时处理器模式是否符合端口要求
- 启动前是否已经有会触发上下文切换的中断在乱入
这类问题往往不是应用任务逻辑,而是系统基础配置还没站稳。
10. 开发阶段最值得打开的保护是什么
如果只选一个,我会优先建议:
configASSERT()
因为它能尽早把很多低级但代价很大的错误拦下来,例如:
- 中断优先级不合法
- 参数明显异常
- 某些状态机进入不该进入的路径
它最大的价值不是“帮你修 bug”,而是让错误尽可能早、尽可能明确地暴露。
11. 一套更稳的排查顺序
FreeRTOS 系统出问题时,比较实用的顺序通常是:
- 先查 ISR 是否用了错误 API,优先级是否正确
- 再查任务栈和中断栈是否够
- 再查 heap 是否足够
- 再看日志输出、格式化函数和内存分配是否把系统拖垮
- 最后再回到具体业务逻辑
这套顺序听起来有点保守,但现实里经常能省掉大量无效时间。
12. 总结
FreeRTOS 的很多“奇怪问题”,最后都不是奇怪问题,而是几个高频错误源在反复出现:
- 中断优先级没配对
- 栈空间不够
printf()用得太重- ISR 和任务上下文边界搞混
把这几个地方先守住,系统稳定性通常就能明显上一个台阶。剩下真正复杂的业务问题,再慢慢往里拆也不迟。