1. 课程导论与项目全景:为什么需要任务调度?为什么需要硬件抽象层?
各位同学好,我是你们这门课的老王。在嵌入式这行摸爬滚打了十几年,见过太多项目从原型到量产的血泪史。今天咱们开篇第一讲,不急着敲代码,先聊聊两个最根本的问题:任务调度和硬件抽象层,到底为什么非用不可?
说白了,这两个东西就是嵌入式软件的骨架和皮肤。骨架撑得住,系统才不乱;皮肤换得勤,硬件才不绑死你。我见过不少团队,前期图省事,在主循环里堆逻辑,结果后期加功能加得想哭。嗯,咱们今天就把这些坑提前填上。
1.1 裸机开发的痛点:你迟早会遇到的麻烦
先看一个典型场景。你写了一个温控器,主循环大概长这样:
void main(void)
{
while(1)
{
read_temperature(); // 读传感器,耗时 5ms
process_pid(); // PID 计算,耗时 3ms
update_display(); // 刷新屏幕,耗时 20ms
check_keypad(); // 扫描按键,耗时 2ms
delay_ms(10); // 凑个周期
}
}
看起来挺顺?问题大了去了。
- 实时性差:按键扫描要等显示刷新完才能执行。用户按了键,得等 20ms 才有反应。你想想看,这要是工业急停按钮,20ms 可能就出事故了。
- 耦合严重:所有功能挤在一个循环里。想加个蓝牙通信?得重新排时间片。想换个大屏?显示函数改了,整个循环节奏都变。
- 维护噩梦:三个月后回看代码,你自己都分不清哪个延时是给传感器稳定的,哪个是凑周期的。
1.2 任务调度:让 MCU 学会「多线程」思考
任务调度解决的核心问题就一个:谁该什么时候跑。
MCU 是单核的,同一时刻只能干一件事。但我们可以通过快速切换,让每个任务都觉得「CPU 是我的」。这就是调度的本质——分时复用。
常见的调度策略有这几种:
| 调度方式 | 原理 | 适用场景 | 我个人的评价 |
|---|---|---|---|
| 前后台(超级循环) | 中断处理紧急事件,主循环轮询 | 极简系统,任务少于 5 个 | 入门可以,产品慎用 |
| 时间片轮转 | 每个任务分配固定时间片 | 任务周期相对固定 | 简单可靠,我常用 |
| 优先级抢占 | 高优先级任务打断低优先级 | 实时性要求高的场景 | 功能强,但要注意优先级反转 |
| 协作式调度 | 任务主动让出 CPU | 资源极度受限的 MCU | 省 RAM,但怕任务死等 |
咱们这门课,会从最简单的协作式开始,一步步搭建一个可抢占的调度内核。别怕,代码量不大,但思路一定要理清。
1.3 硬件抽象层:给代码穿上「防弹衣」
硬件抽象层,英文叫 HAL(Hardware Abstraction Layer)。说白了,就是在你的应用代码和芯片寄存器之间,加一层「翻译官」。
为什么要加这一层?我直接操作寄存器不香吗?
香,但只香一次。举个例子:
// 直接操作寄存器——STM32 的 GPIO
GPIOA->ODR |= (1 << 5); // PA5 输出高
// 换到 GD32,寄存器地址变了
GPIOA->BSRR = (1 << 5); // 语法都不一样
你看,换个芯片,所有 GPIO 操作都得重写。这还只是一个引脚。要是项目里有 SPI、I2C、UART、定时器……那工程量,想想都头大。
有了 HAL 层,你的应用代码只跟 HAL 接口打交道:
// HAL 层接口——与芯片无关
void hal_gpio_write(uint8_t pin, uint8_t level);
uint8_t hal_gpio_read(uint8_t pin);
void hal_uart_send(uint8_t *data, uint16_t len);
换芯片?只需要重写 HAL 层的实现。应用层代码,一行不动。
1.4 调度器 + HAL:1+1 > 2 的组合
单独用调度器,或者单独用 HAL,都能解决问题。但把它们组合起来,才是真正的「解耦大法」。
来看这张架构图,这就是咱们整个课程的核心骨架:
这张图我希望你刻在脑子里。从上到下,每一层只依赖它的下一层。应用层不知道 GPIO 寄存器在哪,调度层不知道传感器是 I2C 还是 SPI。这就是解耦的精髓。
1.5 课程目标:30 章后你能带走什么
咱们这门课不是纯理论,也不是纯代码。我设计的目标很务实:
- 手写一个轻量级任务调度器:支持协作式和抢占式两种模式,代码量控制在 500 行以内,适合任何 Cortex-M 内核 MCU。
- 搭建一套可移植的 HAL 接口:覆盖 GPIO、UART、SPI、I2C、Timer 五大外设,接口风格统一,换芯片只需改底层实现。
- 完成一个综合实战项目:多传感器数据采集 + 显示 + 通信,全部跑在你自己的调度器上。
- 掌握调试与性能分析方法:任务超时怎么查?CPU 占用率怎么算?这些实战技巧我都会教。
1.6 最终项目演示:一个真实的调度系统长什么样
咱们最终要做的项目,是一个「智能环境监测站」。它同时跑着 6 个任务:
- 温度采集任务(周期 500ms,优先级中)
- 湿度采集任务(周期 500ms,优先级中)
- 气压采集任务(周期 1000ms,优先级低)
- OLED 显示任务(周期 200ms,优先级中高)
- 按键扫描任务(周期 20ms,优先级高)
- 串口通信任务(事件触发,优先级中)
所有任务都在咱们自己写的调度器上运行。HAL 层封装了 STM32 的底层操作。如果你想移植到 GD32 或者 AT32,只需要改 HAL 层的 5 个文件,应用代码一行不动。
嗯,这就是解耦的力量。也是咱们接下来 29 章要一起完成的事。