4. 嵌入式任务模型

在嵌入式系统中,“任务模型”决定了代码如何组织、CPU 时间如何分配、事件如何被响应,以及系统能否满足实时性要求。很多项目并不是败在算法上,而是败在任务模型选型不当:该用状态机的地方用了阻塞延时,该用 RTOS 的地方却硬撑前后台轮询,最终导致响应抖动、优先级反转或功耗失控。本节把常见的四类模型放在同一张“能力地图”上对比:前后台系统、时间片轮转、RTOS 任务、事件驱动与状态机。

一、核心原理:四种模型各自解决什么问题

前后台系统(Foreground/Background)是最经典的裸机结构:主循环(后台)不断轮询处理业务,中断服务程序(前台)响应异步事件。它的优点是结构简单、无调度器开销、RAM 占用极小;缺点是主循环中任一环节阻塞,都会拖慢所有后台任务的响应。它适合功能少、实时性要求不苛刻、成本极敏感的 8/16 位 MCU 场景。

时间片轮转(Round-Robin)把 CPU 时间切成固定长度的时间片,多个就绪任务依次获得一个时间片。它比前后台更“公平”,适合多个同级任务都需要推进的场合,例如多路数据采集与显示刷新。但时间片轮转不区分优先级,高实时任务无法抢占低实时任务,且时间片过短会增加切换开销,过长则响应变差。

RTOS 任务引入抢占式调度、优先级、阻塞/唤醒、信号量、消息队列等机制。任务在等待事件时主动阻塞,CPU 让给更低优先级任务;事件到来后高优先级任务立即抢占。它把“响应时间”从“最坏轮询周期”变成“中断延迟 + 调度延迟”,是硬实时系统的核心手段。代价是内核开销、栈空间、优先级设计复杂度和调试难度上升。

事件驱动与状态机不强调“任务”数量,而强调“事件—状态—动作”的映射。系统在空闲时休眠,事件到达后由状态机决定迁移到哪个状态、执行什么动作。它天然适合低功耗、协议解析、UI 交互、按键消抖等场景,常与前后台或 RTOS 结合使用:RTOS 负责任务调度,状态机负责单个任务内部的逻辑组织。

嵌入式任务模型能力对比 前后台系统 中断 ISR(前台) 主循环(后台) 简单 · 低开销 · 响应受轮询限制 适合 8/16 位 MCU 时间片轮转 任务A 时间片 任务B 时间片 任务C 时间片 公平 · 无优先级 · 实时性一般 适合同级多任务推进 RTOS 任务 高优先级任务(抢占) 中优先级任务 低优先级任务(空闲) 抢占 · 阻塞唤醒 · 硬实时 适合复杂实时系统 事件驱动 / 状态机 事件队列 / 中断标志 状态迁移表 动作执行 / 休眠 低功耗 · 逻辑清晰 · 易扩展 适合协议/UI/按键 简单 / 低成本 复杂 / 高实时 关键指标对照 响应确定性 资源开销 低功耗友好 逻辑可维护性

二、方法流程:如何为项目选型

选型不是“越高级越好”,而是按约束逐层筛选。建议按以下顺序判断:

  1. 列出所有异步事件及其最坏响应时间要求。例如电机过流保护要求 50 μs 内动作,串口命令要求 20 ms 内响应,按键要求 100 ms 内响应。
  2. 判断是否存在“必须抢占”的硬实时事件。若最坏响应时间小于主循环最坏执行时间,前后台就不够用,需要中断 + RTOS 或至少中断 + 状态机。
  3. 评估任务数量与耦合度。任务少且顺序执行即可,用前后台;多个同级任务需并行推进,用时间片或 RTOS;任务间有明确优先级和同步关系,用 RTOS。
  4. 评估功耗与休眠需求。若系统大部分时间空闲且需低功耗,事件驱动 + 状态机是首选,RTOS 的空闲任务也可进入低功耗,但需注意 tick 唤醒。
  5. 评估团队与调试成本。RTOS 带来栈溢出、优先级反转、死锁等新问题,若团队缺乏经验,可先用“前后台 + 状态机”把逻辑做稳,再逐步引入 RTOS。
一个常见且稳妥的组合是:中断负责最紧急的硬件响应,RTOS 负责任务调度与同步,状态机负责单个任务内部的复杂逻辑。三者并不互斥,而是分层协作。

三、案例场景:同一需求的四种实现

假设一个数据采集终端需要:每 10 ms 采样一次 ADC,每 100 ms 刷新一次 LCD,收到串口命令后 20 ms 内解析并回复,无事件时尽量低功耗。

前后台实现:主循环依次执行 ADC 采样、LCD 刷新、串口解析。若 LCD 刷新耗时 30 ms,则串口响应最坏会延迟 30 ms,可能超时;且无法进入低功耗,因为主循环一直在跑。

时间片轮转实现:把采样、显示、串口解析各作为一个任务,每个时间片 5 ms。响应变均匀,但串口解析仍可能等待两个时间片,且无优先级,紧急命令无法插队。

RTOS 实现:串口解析任务优先级最高,ADC 采样次之,LCD 刷新最低。串口中断唤醒解析任务后立即抢占 LCD 任务,响应可控制在毫秒级;空闲任务进入低功耗。代价是需要为每个任务分配独立栈,并处理队列与信号量。

事件驱动 + 状态机实现:系统主循环等待事件标志,ADC 定时器中断置位采样事件,串口中断置位命令事件。状态机在收到命令事件时优先处理,处理完再回到空闲状态并休眠。逻辑清晰、功耗低,但若命令处理本身耗时较长,仍会阻塞其他事件,需要拆分为多个状态。

// 事件驱动 + 状态机片段(伪代码)
typedef enum { ST_IDLE, ST_SAMPLE, ST_CMD, ST_SLEEP } state_t;

void app_step(void) {
    uint32_t ev = event_take();          // 非阻塞取事件
    switch (state) {
    case ST_IDLE:
        if (ev & EV_ADC)  state = ST_SAMPLE;
        if (ev & EV_CMD)  state = ST_CMD;
        if (ev == 0)      state = ST_SLEEP;
        break;
    case ST_SAMPLE:
        adc_process();
        state = ST_IDLE;
        break;
    case ST_CMD:
        cmd_process();                   // 应保证短小
        state = ST_IDLE;
        break;
    case ST_SLEEP:
        enter_low_power();               // 事件中断唤醒
        state = ST_IDLE;
        break;
    }
}

四、对比清单与常见误区

模型 调度方式 实时性 资源开销 典型适用场景
前后台 主循环轮询 + 中断 低,受最坏轮询周期限制 极低 小家电、简单传感器节点
时间片轮转 固定时间片依次执行 中,无优先级 低 多路采集、显示刷新
RTOS 任务 优先级抢占 + 阻塞唤醒 高,可满足硬实时 中高(栈、内核) 工业控制、通信设备
事件驱动/状态机 事件触发状态迁移 取决于事件处理时长 低 低功耗、协议解析、UI
常见误区一:在中断里做耗时操作。无论哪种模型,ISR 都应尽量短,只置标志或发消息,把处理放到任务或主循环。误区二:用 delay 代替状态机。阻塞延时会让前后台和时间片模型失去响应性,应改为“时间戳 + 状态迁移”。误区三:认为 RTOS 能自动解决实时问题。若优先级分配不当、临界区过长,仍会出现优先级反转和抖动。误区四:状态机状态爆炸。状态过多时应考虑分层状态机或把部分逻辑拆成独立任务。

复盘时可以用三个问题检验选型是否合理:最坏响应时间是否可计算且满足要求?CPU 空闲时是否真的在休眠或执行低优先级任务?新增一个事件时,是否需要改动大量已有代码?如果答案分别是“是、是、否”,说明任务模型与系统需求匹配良好。