1. 任务调度架构概述
各位同学,咱们今天聊聊任务调度。这词听起来挺唬人,说白了就是——单片机怎么安排活儿。
我刚开始做嵌入式那会儿,写程序就是个大循环,所有事排着队来。后来项目越来越复杂,发现这么干不行。按键会丢,屏幕会闪,通信会超时。嗯,那时候我才意识到,任务调度不是锦上添花,是刚需。
什么是任务调度
任务调度,就是决定哪个任务在什么时候运行、运行多久的一套机制。
你想想看,单片机就一个核,同一时刻只能干一件事。但你的系统可能要同时处理按键扫描、LED刷新、串口收发、传感器采集……怎么办?
调度器就是那个交通警察,它指挥这些任务轮流占用CPU,让它们看起来像是“同时”在跑。
核心三要素:
- 任务:一段独立的代码逻辑,完成特定功能
- 状态:就绪、运行、阻塞、挂起
- 切换:从一个任务换到另一个任务的过程
为什么需要任务调度
我见过不少新手,上来就写一个大while(1),把所有功能堆进去。小项目还行,一旦超过三五个功能模块,问题就来了。
举个真实例子。我之前做一款智能家居网关,需要同时处理:
- WiFi模块数据收发(不能断)
- 按键检测(响应要快)
- OLED屏幕刷新(不能闪)
- 传感器定时采集(周期要准)
用裸机大循环写,WiFi收包的时候按键按了没反应,屏幕刷新到一半被传感器打断……用户体验极差。
任务调度要解决的核心痛点:
| 痛点 | 表现 | 调度怎么解决 |
|---|---|---|
| 响应不及时 | 按键按了没反应 | 高优先级任务抢占执行 |
| 时序不精准 | 采样周期忽快忽慢 | 定时器触发任务切换 |
| CPU空转浪费 | 等待时一直跑空循环 | 阻塞任务让出CPU |
| 代码耦合严重 | 改一个功能影响全局 | 任务独立,模块化开发 |
注意:不是所有项目都需要任务调度。如果你的系统只有一两个功能,逻辑简单,裸机大循环反而是最稳定、最省资源的方案。别为了用调度而用调度。
常见调度策略对比
我这些年用过的调度方式,基本就三种。咱们一个一个说。
1. 轮询调度
这是最原始的方式。一个死循环,挨个检查每个任务是否需要执行。
while(1) {
check_key(); // 检查按键
update_led(); // 更新LED
process_uart(); // 处理串口
read_sensor(); // 读传感器
delay_ms(10); // 延时
}
优点:简单、直观、无额外开销。
缺点:所有任务平等,一个任务卡住,后面的全完蛋。
我记得刚毕业那会儿,用轮询做一个小家电控制器。按键消抖用了delay(20ms),结果LED呼吸效果就卡顿了。查了半天才发现是延时把整个循环堵死了。
2. 前后台系统
也叫中断+主循环模式。紧急的事放中断里处理,不紧急的放主循环。
// 中断服务函数
void TIM2_IRQHandler(void) {
key_scan(); // 按键扫描,10ms一次
flag_key_ready = 1;
}
// 主循环
while(1) {
if(flag_key_ready) {
process_key();
flag_key_ready = 0;
}
update_display();
// ...
}
优点:响应快,中断能立即处理紧急事件。
缺点:中断里不能做耗时操作,主循环依然存在互相阻塞的问题。
前后台系统是我用得最久的方案。说实话,对于大部分中小型项目,它够用了。但要注意——中断服务函数里千万别做复杂运算,我见过有人把浮点运算放中断里,结果系统直接崩了。
3. RTOS(实时操作系统)
这才是真正的多任务调度。每个任务有自己的栈空间和优先级,由内核决定谁运行。
void task_led(void *arg) {
while(1) {
LED_toggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void task_uart(void *arg) {
while(1) {
uart_send(data);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 创建任务
xTaskCreate(task_led, "LED", 128, NULL, 1, NULL);
xTaskCreate(task_uart, "UART", 128, NULL, 2, NULL);
vTaskStartScheduler();
优点:
- 任务独立,互不阻塞
- 优先级管理,紧急任务优先执行
- 提供信号量、队列等同步机制
- 代码模块化,易于维护
缺点:
- 占用额外RAM和ROM(任务栈、内核对象)
- 有上下文切换开销
- 调试难度增加(竞态、死锁、优先级反转)
我的建议:如果项目功能超过5个,或者有严格实时性要求,直接上RTOS。别犹豫。我早期在RTOS上犹豫过,结果后期重构的代价更大。
三种策略对比总表
| 维度 | 轮询 | 前后台 | RTOS |
|---|---|---|---|
| 实时性 | 差 | 中 | 好 |
| 资源占用 | 极少 | 少 | 较多 |
| 代码复杂度 | 低 | 中 | 高 |
| 任务隔离性 | 无 | 弱 | 强 |
| 调试难度 | 低 | 中 | 高 |
| 适用场景 | 极简系统 | 中小型项目 | 复杂/实时系统 |
调度策略选择决策流程
下面这张图是我自己总结的,每次选型时都会过一遍。
我的选型建议
做了这么多年嵌入式,我总结了几条经验:
- 别盲目上RTOS。一个LED闪烁+按键检测的项目,用RTOS就是杀鸡用牛刀。资源浪费不说,调试还麻烦。
- 前后台系统是万金油。大部分消费电子、工业控制的中小型项目,前后台系统都能搞定。我做过一个8功能的智能插座,前后台系统跑得稳稳的。
- RTOS是进阶必备。一旦项目涉及网络协议栈、文件系统、多传感器融合,别犹豫,直接上RTOS。后期加功能、修bug都方便。
避坑指南:我曾经在一个项目里,前期图省事用了轮询,结果后期加了4个功能模块后,系统响应完全不可控。最后花了整整两周重构为RTOS。那两周的加班,让我学会了——选型阶段多花一小时,后期省下一周。