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里来了一个紧急中断呢?

我的经验:我曾经接手过一个老项目,里面用了5层嵌套的delay_ms()。改一个功能,得先花两天理清楚每个延时到底卡了多久。从那以后,我写代码再也不敢随便用延时了。

1.3 任务调度的本质:分时复用CPU

任务调度,说白了就是让CPU在多个任务之间快速切换,造成「同时运行」的假象。你不需要真的并行——单核CPU一次只能干一件事。但通过调度,你可以让每个任务都觉得自己「独享」了CPU。

我画了一张图,帮你理解这个转变:

从超级循环到任务调度:思维转变 超级循环(轮询) → 任务A执行(直到完成) → 任务B执行(直到完成) → 任务C执行(直到完成) → 回到任务A(循环) 转变 任务调度(分时复用) → 任务A运行5ms → 暂停 → 任务B运行3ms → 暂停 → 任务C运行2ms → 暂停 → 回到任务A(继续运行) 关键区别:超级循环中,一个任务阻塞,整个系统卡死。 任务调度中,每个任务只运行一小段时间,CPU在任务间快速切换,系统永远「活着」。 调度器核心职责 保存当前任务状态 → 选择下一个任务 → 恢复其状态

1.4 什么时候该用调度?一个简单的判断标准

我经常被问到:「我的项目到底需不需要任务调度?」这里给你一个实用的判断表:

项目特征 超级循环 任务调度
任务数量 ≤ 3个 ≥ 4个
任务执行时间 都很短(< 1ms) 有长有短
实时性要求 无严格实时 有毫秒级实时要求
任务间依赖 简单顺序执行 有同步/互斥需求
代码可维护性 低(改一处可能影响全局) 高(任务独立,模块化)
注意:不要为了用调度而用调度。如果你的项目就两个任务,一个读按键一个点灯,超级循环完全够用。引入调度反而增加复杂度。记住:合适的才是最好的。

1.5 从裸机到RTOS:一条清晰的成长路径

这门课会带你走一条很实在的路。我们不一步跳到RTOS,而是从裸机开始,一步步搭建自己的调度器。我把它分成三个阶段:

  1. 裸机阶段:用状态机+定时器中断,实现简单的非阻塞调度
  2. 过渡阶段:自己写一个协作式调度器,理解任务切换的本质
  3. 进阶阶段:对比FreeRTOS等实时操作系统,理解抢占式调度的原理

我个人习惯是:先理解原理,再使用工具。你如果直接上手FreeRTOS,可能连任务栈溢出了都不知道怎么排查。但如果你亲手写过调度器,那些概念就变成了肌肉记忆。

课程目标:学完这门课,你不仅能熟练使用RTOS,更重要的是——你能自己设计一个适合项目需求的调度架构。这才是系统架构师的思维。

1.6 避坑指南:我踩过的三个坑

最后,分享几个我早年犯过的错误。希望你能绕过去:

  • 坑一:过度设计——一个温控器项目,我硬上了RTOS,结果任务切换开销比实际计算还大。后来换成简单的时间片轮询,反而更稳。
  • 坑二:忽视任务栈——用RTOS时没算好栈大小,程序跑几天就莫名其妙死机。查了一个星期,才发现是栈溢出把关键数据覆盖了。
  • 坑三:中断里做太多事——以为中断越快越好,结果在中断里调用了printf(),整个系统直接挂掉。记住:中断里只做标记,不做处理。

嗯,这些坑后面都会详细讲。今天先让你有个印象。