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
实时性 差 中 好
资源占用 极少 少 较多
代码复杂度 低 中 高
任务隔离性 无 弱 强
调试难度 低 中 高
适用场景 极简系统 中小型项目 复杂/实时系统

调度策略选择决策流程

下面这张图是我自己总结的,每次选型时都会过一遍。

调度策略选择决策流程 开始选型 功能模块 <= 3 个? 是 轮询调度 否 有严格实时性要求? 是 RTOS 否 前后台系统 说明 • 轮询:功能极少、无实时要求、资源受限 • 前后台:功能适中、有简单中断响应需求 • RTOS:功能复杂、多任务、强实时、可接受资源开销

我的选型建议

做了这么多年嵌入式,我总结了几条经验:

  • 别盲目上RTOS。一个LED闪烁+按键检测的项目,用RTOS就是杀鸡用牛刀。资源浪费不说,调试还麻烦。
  • 前后台系统是万金油。大部分消费电子、工业控制的中小型项目,前后台系统都能搞定。我做过一个8功能的智能插座,前后台系统跑得稳稳的。
  • RTOS是进阶必备。一旦项目涉及网络协议栈、文件系统、多传感器融合,别犹豫,直接上RTOS。后期加功能、修bug都方便。

避坑指南:我曾经在一个项目里,前期图省事用了轮询,结果后期加了4个功能模块后,系统响应完全不可控。最后花了整整两周重构为RTOS。那两周的加班,让我学会了——选型阶段多花一小时,后期省下一周。