1. 课程导论:为什么需要任务调度?从点灯工程师到系统架构师的思维转变
大家好,我是你们这门课的老朋友。在嵌入式这行摸爬滚打了十几年,我见过太多工程师卡在「裸机写死循环」的阶段,也见过不少人因为不懂调度,把一个本该简单的项目搞成一团乱麻。
今天这第一讲,我们不急着写代码。我想先跟你聊聊——为什么你的单片机需要任务调度?
1.1 你还在用「超级循环」吗?
先看一段最常见的代码。我相信你肯定写过:
void main(void)
{
while(1)
{
key_scan(); // 扫描按键
led_toggle(); // 闪烁LED
uart_send_data(); // 发送串口数据
delay_ms(10); // 延时
}
}
看起来挺整齐,对吧?每个功能都调用了,顺序也清楚。但你想过没有——如果按键扫描卡住了呢?
我在一个智能家居项目里就吃过这个亏。当时用这种超级循环写了一个温控器,按键扫描里有个去抖延时,结果用户按一下按键,整个屏幕要等200ms才刷新。客户投诉说「这玩意儿反应太慢了」。说白了,超级循环的本质是「串行执行」——一个任务不跑完,后面的都得等着。
1.2 从「点灯」到「系统」:思维的第一道坎
很多初学者觉得:我点个灯、读个按键,要什么调度?直接写不就完了?
嗯,这话在只有两个任务时确实没错。但项目一旦复杂起来,你会发现:
- 任务数量增加:从2个变成5个、10个
- 任务优先级不同:按键响应必须快,LED闪烁可以慢
- 任务之间有依赖:比如先采集完数据才能发送
- 实时性要求:某个任务必须在10ms内完成
这时候,超级循环就彻底不够用了。你想想看,一个10ms的延时函数,就能让整个系统「死」10ms。如果这10ms里来了一个紧急中断呢?
1.3 任务调度的本质:分时复用CPU
任务调度,说白了就是让CPU在多个任务之间快速切换,造成「同时运行」的假象。你不需要真的并行——单核CPU一次只能干一件事。但通过调度,你可以让每个任务都觉得自己「独享」了CPU。
我画了一张图,帮你理解这个转变:
1.4 什么时候该用调度?一个简单的判断标准
我经常被问到:「我的项目到底需不需要任务调度?」这里给你一个实用的判断表:
| 项目特征 | 超级循环 | 任务调度 |
|---|---|---|
| 任务数量 | ≤ 3个 | ≥ 4个 |
| 任务执行时间 | 都很短(< 1ms) | 有长有短 |
| 实时性要求 | 无严格实时 | 有毫秒级实时要求 |
| 任务间依赖 | 简单顺序执行 | 有同步/互斥需求 |
| 代码可维护性 | 低(改一处可能影响全局) | 高(任务独立,模块化) |
1.5 从裸机到RTOS:一条清晰的成长路径
这门课会带你走一条很实在的路。我们不一步跳到RTOS,而是从裸机开始,一步步搭建自己的调度器。我把它分成三个阶段:
- 裸机阶段:用状态机+定时器中断,实现简单的非阻塞调度
- 过渡阶段:自己写一个协作式调度器,理解任务切换的本质
- 进阶阶段:对比FreeRTOS等实时操作系统,理解抢占式调度的原理
我个人习惯是:先理解原理,再使用工具。你如果直接上手FreeRTOS,可能连任务栈溢出了都不知道怎么排查。但如果你亲手写过调度器,那些概念就变成了肌肉记忆。
1.6 避坑指南:我踩过的三个坑
最后,分享几个我早年犯过的错误。希望你能绕过去:
- 坑一:过度设计——一个温控器项目,我硬上了RTOS,结果任务切换开销比实际计算还大。后来换成简单的时间片轮询,反而更稳。
- 坑二:忽视任务栈——用RTOS时没算好栈大小,程序跑几天就莫名其妙死机。查了一个星期,才发现是栈溢出把关键数据覆盖了。
- 坑三:中断里做太多事——以为中断越快越好,结果在中断里调用了printf(),整个系统直接挂掉。记住:中断里只做标记,不做处理。
嗯,这些坑后面都会详细讲。今天先让你有个印象。