4. 前后台架构与时间片轮询
前后台架构是嵌入式裸机程序中最经典、也最容易被低估的一种实时软件组织方式。它的基本思想非常朴素:后台是一个永不退出的超级循环(super loop),前台是各类中断服务程序(ISR)。后台负责“常规的、可等待的”工作,前台负责“必须立刻响应的”事件。理解这一架构的关键,不在于循环本身,而在于如何划分前台与后台的职责边界,以及后台如何在没有操作系统调度器的情况下,让多个任务看起来在同时推进。
超级循环的基本形态
最原始的超级循环只做一件事:顺序执行若干函数,然后回到开头。例如:
int main(void)
{
System_Init();
while (1) {
Task_KeyScan(); // 按键扫描
Task_Display(); // 刷新显示
Task_CommPoll(); // 串口协议轮询
Task_LedBlink(); // LED 闪烁
}
}
这种写法的问题在于:每个任务的执行时间不同,且互相阻塞。如果 Task_Display() 需要 8 ms,那么按键扫描的响应间隔就被拉长到至少 8 ms,实时性完全取决于最慢的任务。因此,真正可用的前后台架构必须引入时间片轮询,把“顺序执行”升级为“按时间节拍分时执行”。
中断服务程序的分工原则
在前后台架构中,ISR 的职责应当被严格限制为三件事:清除中断标志、搬运关键数据、置位事件标志。任何耗时操作——浮点运算、字符串格式化、Flash 写入、长延时——都不应放在 ISR 中。原因很直接:ISR 执行期间,同级或更低优先级的中断被屏蔽,ISR 越长,系统的中断响应抖动越大。
一个典型的串口接收 ISR 应当这样写:
void USART1_IRQHandler(void)
{
if (USART1->SR & USART_SR_RXNE) {
uint8_t ch = USART1->DR; // 读走数据,自动清标志
RingBuf_Put(&rx_buf, ch); // 仅做入队,不做解析
g_rx_event = 1; // 置位事件标志
}
}
真正的协议解析、命令执行、应答组包,全部交给后台循环在检测到 g_rx_event 后处理。这样 ISR 的执行时间可以控制在微秒级,而解析逻辑无论多复杂,都不会影响中断响应。
时间片轮询的实现
时间片轮询的核心是:用一个硬件定时器产生固定周期的“节拍”,后台循环在每个节拍到来时,按预设的时间片分配执行各个任务。常见做法是维护一个软件定时器数组,每个任务记录“上次执行时刻”和“执行周期”,后台循环只判断当前节拍是否到达该任务的执行点。
#define TICK_MS 1
volatile uint32_t g_tick = 0;
void SysTick_Handler(void) // 1 ms 节拍
{
g_tick++;
}
typedef struct {
uint32_t period; // 执行周期,单位 ms
uint32_t last; // 上次执行时的 g_tick
void (*fn)(void);
} Task_t;
Task_t tasks[] = {
{ 5, 0, Task_KeyScan }, // 每 5 ms
{ 20, 0, Task_Display }, // 每 20 ms
{ 10, 0, Task_CommPoll }, // 每 10 ms
{ 500,0, Task_LedBlink }, // 每 500 ms
};
void Scheduler_Run(void)
{
for (int i = 0; i < sizeof(tasks)/sizeof(tasks[0]); i++) {
if ((g_tick - tasks[i].last) >= tasks[i].period) {
tasks[i].last = g_tick;
tasks[i].fn();
}
}
}
这里有一个工程细节值得注意:g_tick - tasks[i].last 使用无符号减法,即使 g_tick 溢出回绕,差值仍然正确。这是嵌入式时间比较的标准写法,比直接比较 g_tick >= last + period 更安全。
时间片轮询的调度效果,可以用下面的信号链来理解:定时器节拍驱动 tick 累加,后台循环在每个节拍检查各任务的到期条件,到期任务被依次执行,未到期任务跳过。整个系统的实时性由“最坏情况下所有到期任务的总执行时间”决定,而不是由单个最慢任务决定。
适用场景与局限性
前后台架构最适合任务数量少、实时性要求中等、任务之间耦合度低的场景。例如:家电控制板、简单工控模块、传感器采集节点、教学实验平台。这类系统的共同特征是:最坏响应时间在毫秒级即可接受,任务之间不需要复杂的同步与互斥,代码规模通常在几千行以内。
它的局限性同样明确。第一,任务执行时间不可抢占:如果某个任务因为等待外设而阻塞 30 ms,其他所有任务都被推迟,系统的实时性被最坏任务拖累。第二,优先级无法表达:时间片轮询对所有任务一视同仁,紧急任务和普通任务只能靠缩短周期来“抢”执行机会,但这会加剧 CPU 占用。第三,任务间共享数据需要额外保护:后台任务与 ISR 共享的变量必须用 volatile 修饰,多字节变量的读写要考虑原子性,必要时用临界区保护。第四,可扩展性差:当任务增加到十几个、周期要求各不相同、还存在任务间依赖时,手工维护时间片表会迅速变得难以调试。
从工程实践角度看,前后台架构并不是“过时”的方案,而是复杂度与实时性之间的一种明确取舍。当系统规模尚小、实时性要求不苛刻时,它代码直观、调试简单、无内核开销,是非常合理的选择。真正需要警惕的是:在任务数量和实时性要求增长后,仍然强行用超级循环硬撑,导致系统行为不可预测。此时应当考虑引入 RTOS,把时间片轮询升级为基于优先级的抢占式调度。