1. 课程导论:为什么需要架构设计?从裸机到事件驱动再到RTOS的演进之路

做嵌入式开发这些年,我见过太多项目从「能跑就行」变成「改不动了」。

你想想看,一个产品刚立项时,功能就那么几个。工程师三下五除二,在 main 函数里写个死循环,轮询一下按键,查一下传感器,完事。产品跑起来了,老板很开心。

但三个月后呢?需求翻了一倍。要加蓝牙,要加 OTA,要低功耗。原来的代码就像一团乱麻——改一个地方,崩三个地方。这时候你才意识到:架构设计不是锦上添花,是救命稻草。

1.1 裸机编程:最直接的「能跑就行」

裸机编程,说白了就是没有操作系统。一个 while(1) 循环,把所有的活儿都堆在里面。

// 典型的裸机轮询架构
int main(void) {
    system_init();
    
    while (1) {
        scan_key();      // 扫描按键
        read_sensor();   // 读取传感器
        update_display();// 更新屏幕
        delay_ms(10);    // 延时
    }
}

这种写法有什么问题?

  • 实时性差:按键扫描必须等传感器读完才能执行。如果传感器读取花了 50ms,按键响应就慢了 50ms。
  • 功耗高:CPU 一直在跑,没法进入休眠。我在一个智能门锁项目里就吃过这个亏——裸机轮询,电池撑不过两周。
  • 扩展性差:加一个新功能,就得往主循环里塞一段代码。塞多了,循环周期越来越长,系统越来越卡。
我曾经踩过的坑: 一个温控器项目,用裸机轮询写了 3000 行。后来要加一个 Wi-Fi 配网功能,发现主循环周期已经超过 200ms。按键按下去,要等 200ms 才有反应。用户投诉说「这产品是不是坏了」。最后整个重写,换了事件驱动架构。

1.2 中断与前后台系统:第一次「架构觉醒」

为了解决实时性问题,前辈们想出了一个办法:用中断处理紧急事件,主循环处理后台任务。这就是前后台系统(Foreground/Background)。

// 前后台系统示意
void EXTI_IRQHandler(void) {
    // 前台:中断服务,处理紧急事件
    key_pressed = 1;
    key_value = read_gpio();
}

int main(void) {
    while (1) {
        // 后台:主循环,处理非紧急任务
        if (key_pressed) {
            handle_key(key_value);
            key_pressed = 0;
        }
        update_display();
        system_sleep();  // 没事做就睡觉
    }
}

这个架构比裸机轮询进步了一大截:

  • 紧急事件(按键、通信)由中断实时响应
  • 主循环可以休眠,降低功耗
  • 任务之间通过标志位通信,结构清晰了一些

但问题也来了:中断多了怎么办? 我在一个多传感器采集项目里,用了 6 个中断。每个中断里都要处理数据,中断嵌套一多,栈溢出、数据竞争、优先级反转……调试起来简直要命。

我的经验: 中断服务函数一定要短。我给自己定了个规矩——中断里只做三件事:读数据、置标志、清中断。其他事情全部丢到主循环处理。这个习惯帮我避免了很多「灵异 bug」。

1.3 事件驱动架构:让系统「按需工作」

前后台系统虽然解决了实时响应,但任务之间的协作还是靠全局标志位。代码一多,标志位满天飞,你根本搞不清哪个标志位被谁改了。

这时候,事件驱动架构登场了。

核心思想很简单:系统不主动轮询,而是等待事件发生。事件来了,查一下事件表,找到对应的处理函数,执行完继续睡觉。

// 事件驱动架构示意
typedef enum {
    EVT_KEY_PRESS,
    EVT_SENSOR_READY,
    EVT_TIMER_TICK,
    EVT_UART_RX
} event_id_t;

typedef struct {
    event_id_t id;
    uint32_t   data;
} event_t;

void event_dispatcher(event_t *evt) {
    switch (evt->id) {
        case EVT_KEY_PRESS:
            handle_key(evt->data);
            break;
        case EVT_SENSOR_READY:
            process_sensor_data(evt->data);
            break;
        // ... 其他事件
    }
}

int main(void) {
    event_t evt;
    while (1) {
        if (event_queue_dequeue(&evt)) {
            event_dispatcher(&evt);
        } else {
            system_sleep();  // 没事件就睡
        }
    }
}

事件驱动的好处很明显:

  • 解耦:事件产生者和消费者互不关心对方是谁
  • 低功耗:没有事件时 CPU 深度休眠
  • 可扩展:加一个新功能,只需要注册一个新事件

但事件驱动也不是万能的。我在一个需要同时处理 20 个任务的网关项目里,事件队列被塞得满满当当。高优先级的事件(比如网络断连)要等低优先级事件处理完才能执行——优先级反转问题暴露了。

1.4 RTOS:当事件驱动不够用时

事件驱动架构的瓶颈在于:所有事件共用一个线程。一个事件处理慢了,后面的都得排队。

RTOS(实时操作系统)解决了这个问题。它让你可以把不同的任务放到不同的线程里,每个线程有自己的栈、自己的优先级。高优先级的任务可以抢占低优先级的任务。

// RTOS 多任务示意(以 FreeRTOS 为例)
void vKeyTask(void *pvParameters) {
    while (1) {
        if (xQueueReceive(key_queue, &key_val, portMAX_DELAY)) {
            handle_key(key_val);
        }
    }
}

void vSensorTask(void *pvParameters) {
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(100));  // 每 100ms 采集一次
        read_sensor();
        xQueueSend(sensor_queue, &data, 0);
    }
}

int main(void) {
    xTaskCreate(vKeyTask, "Key", 256, NULL, 3, NULL);
    xTaskCreate(vSensorTask, "Sensor", 256, NULL, 2, NULL);
    vTaskStartScheduler();
}

RTOS 带来的核心能力:

  • 任务隔离:一个任务崩溃不会拖垮整个系统
  • 优先级管理:紧急任务可以立即执行
  • 同步机制:队列、信号量、互斥锁,让任务协作更安全
但 RTOS 不是银弹。 它带来了额外的 RAM 开销(每个任务需要独立的栈)、调度延迟、以及更复杂的调试。我见过有人在一个只需要 3 个任务的系统里硬上 RTOS,结果栈空间分配不合理,系统频繁 HardFault。记住:架构的选择要匹配问题的复杂度。

1.5 三种架构的对比与选择

维度 裸机轮询 事件驱动 RTOS
实时性 差(依赖循环周期) 中(依赖事件队列处理速度) 好(抢占式调度)
功耗 高(CPU 持续运行) 低(无事件时休眠) 中(需考虑 Tickless 模式)
代码复杂度 低 中 高
RAM 开销 极低 低(事件队列) 高(每个任务独立栈)
任务数量 ≤3 3~10 ≥5
典型场景 简单控制、玩具 传感器节点、遥控器 网关、工业控制器

我个人习惯这样选:

  • 任务 ≤ 3 个,逻辑简单 → 裸机轮询(别折腾)
  • 任务 3~10 个,需要低功耗 → 事件驱动(性价比最高)
  • 任务 ≥ 5 个,有实时性要求 → RTOS(值得投入)

1.6 本章知识结构

下面这张图总结了从裸机到 RTOS 的演进路径,以及每种架构的核心特征:

嵌入式软件架构演进路径 嵌入式软件架构演进路径 裸机轮询 while(1) 死循环 前后台系统 中断 + 主循环 事件驱动 事件队列 + 分发 RTOS 多任务 + 抢占调度 演进逻辑: • 实时性:从无到有,从软实时到硬实时 • 功耗:从持续运行到按需唤醒 • 复杂度:从单线程到多任务,从全局变量到消息传递

看到这张图,你应该能理解:架构的演进不是技术的堆砌,而是对问题复杂度的回应。裸机解决「能跑」的问题,事件驱动解决「低功耗」的问题,RTOS 解决「多任务协作」的问题。

我的建议: 不要为了用 RTOS 而用 RTOS。我见过太多项目,明明事件驱动就能搞定,非要上 RTOS,结果栈溢出、优先级配置错误、死锁……调试时间比写代码还长。架构选型的第一原则是:够用就好,适度超前。

接下来的课程,我会带你深入每一种架构的细节。我们会从事件驱动开始,一步步构建一个完整的低功耗嵌入式系统。你会在实战中理解:为什么架构设计不是「纸上谈兵」,而是决定产品成败的关键。