本节摘要:裸机编程指不依赖操作系统、直接跑在硬件上的程序组织方式。两大经典模型——前后台(中断加主循环)与事件状态机——支撑了市面上绝大多数嵌入式产品。本节讲清两种模型的结构、各自的扩展极限与组合用法,并用一个多任务小系统的推演示范"如何让裸机程序在变复杂后依然保持秩序"。
一个常见疑问:既然 RTOS 能把系统切成整齐的任务,为什么还要学裸机架构?三个理由。其一,裸机是 RTOS 的地基:RTOS 的任务调度本身依赖中断(SysTick 心跳)与协作标志,不懂裸机的并发模型,RTOS 用起来也只是套模板。其二,大量产品根本不需要 RTOS:一颗几元钱的芯片跑 RTOS,内核占掉几分之一 Flash 与 RAM,换来的是项目用不上的多任务能力——架构要配规模,不是越贵越好。其三,最小系统扩张记的延续:从单一主循环长出前后台、再长出状态机,这条演化路径让你理解"架构为什么会变成这样",而不是背下"架构是什么"。
前后台的名字很形象:后台是主循环里的无限循环(任务优先级低、随时可被打断),前台是各中断服务程序(优先级由 NVIC 决定、随时打断后台)。分工原则在 5.3 节已经建立:前台只做紧急且短暂的事(取数据、清标志、打时间戳),后台做一切业务逻辑,两者之间用标志位与环形缓冲通信。
4.5 节的呼吸灯项目就是标准前后台:定时器中断推进渐变节拍(前台),主循环处理按键与上报(后台)。它的优势是简单透明——整条执行流一目了然,内存全是静态分配,行为可分析。弱点在"任务多了以后":主循环里 if 标志位的分支越积越多,各任务的实时性互相牵制——轮询到第十个标志位时,第一个任务已经等了九个任务的处理时间。
/* 前后台的标准骨架:标志驱动的任务调度 */ for (;;) { if (flag_1ms) { flag_1ms = 0; task_scan_key(); } /* 高频任务 */ if (flag_10ms) { flag_10ms = 0; task_debounce(); } /* 中频任务 */ if (flag_100ms) { flag_100ms = 0; task_refresh_lcd();} /* 低频任务 */ if (flag_1s) { flag_1s = 0; task_report(); } /* 秒级任务 */ }
这段骨架的精髓是软件分频:1ms 节拍中断里对计数器累加,每 10 次、100 次、1000 次各置一个下游标志,主循环按频率分层消费。所有任务按"容忍延迟"归入合适的频率档——这套分层正是 5.2 节优先级四档模型在时间维度上的镜像。主循环骨架写好后,新增任务就是"加一个标志加一个函数"的机械操作,秩序不会随规模崩坏。
前后台解决了"多任务分时"的问题,但还有一种复杂度它应付不了:同一任务内部的行为依赖于历史。典型如按键:单击是切换、双击是长按取消、长按两秒是进入配对——同样一次"按下",在不同状态下含义不同。用 if 标志堆这种逻辑,三个月后没人能看懂自己的代码。
状态机把这类行为组织成显式的模型:系统在任一时刻处于一个确定状态,事件到来时按"当前状态加事件"查转移表,执行动作并迁移到新状态。代码形态是 switch 套 switch(状态套事件)或查表驱动:
/* 按键处理状态机:状态加事件的显式模型 */ switch (key_state) { case ST_IDLE: /* 空闲:等第一个按下 */ if (ev == EV_PRESS) { start_press_timer(); key_state = ST_PRESSED; } break; case ST_PRESSED: /* 已按下:区分单击与长按 */ if (ev == EV_RELEASE) { if (press_ms < 300) { emit_click(); key_state = ST_IDLE; } else { emit_hold(); key_state = ST_IDLE; } } else if (ev == EV_TIMER_2S) { enter_pairing(); key_state = ST_PAIRING; } break; case ST_PAIRING: /* 配对中:长按退出 */ if (ev == EV_TIMER_5S || ev == EV_HOLD_EXIT) { exit_pairing(); key_state = ST_IDLE; } break; }
状态机的好处是行为可穷举、可画图、可评审:状态转移图画在纸上,产品经理与工程师能对着同一张图吵架;漏掉的状态与事件组合(比如配对中突然掉电)一眼可见。嵌入式里值得状态机化的东西远比想象多:通信协议解析(等待帧头、收长度、收数据、校验四状态)、设备工作模式(待机、运行、故障、维护)、菜单界面。判断标准一条:行为依赖历史就用状态机,不依赖就普通函数。

实战中的裸机程序通常是组合形态:前后台做骨架(分时调度),状态机做关节(复杂行为)——主循环按频率档轮询任务,按键、协议、模式管理等"历史依赖"任务内部各自是状态机,状态变量与事件由前后台的通信机制传递。这套组合拳能撑到什么规模?经验值是:十个以内任务、中断七八个、不用动态内存的规模,前后台加状态机都能保持秩序;超过这个规模——任务间出现复杂的等待与同步关系(A 必须等 B 的信号、B 又依赖 C 的资源)——手工管理并发关系的复杂度开始指数上升,这时候 RTOS 的调度器与同步原语才是解药。这个"该上 RTOS 了"的判断,正是下一节的主题。
💡 关键直觉:架构不是选出来的,是长出来的。从单循环长出前后台,从标志堆长出状态机——每一次重构都由具体的失控感驱动。当你觉得"主循环快管不过来了",那就是升级架构的信号,别提前,也别拖后。
前后台模型有一个必须正视的天性:主循环任务的执行时刻有抖动。原因是结构性的——任务 B 的开始时刻取决于排在它前面的 A 跑了多久,而 A 的耗时又会因分支、数据、打印而浮动。量化一下:若任务 A 正常 2ms、最坏 8ms,那么 B 的周期就是"标称节拍加 0 到 6ms 的漂移"。这份抖动账本有什么用?它告诉你哪些任务能放进主循环、哪些不能:显示刷新抖 5ms 毫无感知,可以;1-Wire 总线的时隙操作抖 5 微秒就废,不可以——后者要么挪进定时器中断,要么交给硬件外设。工程上还有个常用的"最坏情况预算表"做法:给主循环每个任务列标称耗时与最坏耗时,两者相加若超过调度节拍,说明主循环已超载,要么拆分任务、要么把急件上移到中断。裸机不提供实时承诺,但工程师可以自己把账算清——这正是 5.1 延迟公式在架构层的续篇。