1. 事件驱动架构概述

各位同学好。我是老周,做了十几年嵌入式开发。今天咱们聊聊事件驱动架构——这个听起来有点玄乎,其实每天都在用的东西。

先问个问题:你写的单片机程序,是不是经常这样——

while(1) {
    if(按键按下) { 处理按键 }
    if(定时器到) { 处理定时 }
    if(串口收到) { 处理数据 }
    // 再检查一堆标志位
}

这种写法,我管它叫「轮询地狱」。项目小的时候还好说,功能一多,代码就乱成一锅粥。我在2015年做过一个智能家居网关项目,8个传感器、3路PWM、2路串口,全用轮询写。结果呢?加一个新功能,改了三天,还引入两个bug。从那以后,我开始认真研究事件驱动。

1.1 什么是事件驱动

事件驱动,说白了就是:系统不主动干活,等事情发生了再响应。

你想想看,现实世界就是事件驱动的。你坐在工位上,不会每秒钟检查一遍「有没有人找我」。而是等手机响了、有人喊你名字、或者邮件弹窗了,你才去处理。嵌入式系统也一样。

事件驱动架构的核心就三个东西:

  • 事件源:谁产生事件?按键、定时器、串口中断、传感器触发……
  • 事件队列:事件来了先排队,不丢不抢
  • 事件处理器:谁负责处理?一个函数,或者一个状态机

核心思想:系统平时处于低功耗空闲状态,只有事件发生时,才唤醒CPU去处理。处理完继续睡。这才是嵌入式该有的样子。

我习惯把事件驱动比作「急诊室分诊台」。病人(事件)来了,护士(调度器)根据病情严重程度(优先级)安排就诊顺序。医生(处理函数)只看自己负责的病人。没有病人时,大家休息。高效、清晰、不浪费资源。

1.2 裸机 vs RTOS:一个真实的对比

很多初学者纠结:到底用裸机还是RTOS?我直接给你看数据。

对比维度 裸机轮询 裸机事件驱动 RTOS
代码复杂度 低,但功能多了就乱 中等,结构清晰 高,需要学API
实时性 差,轮询周期决定 好,事件来了就处理 很好,优先级抢占
内存占用 极小,几百字节 小,1-2KB 大,至少4-8KB堆栈
任务数量 3-5个还行 10-20个轻松 几十个没问题
调试难度 简单,单线程 中等,注意事件顺序 高,要处理优先级反转
功耗控制 差,CPU一直跑 好,空闲可休眠 好,有IDLE任务
学习成本 零 低,一天上手 高,至少一周

我个人的经验是:RAM小于4KB、任务少于10个、对实时性要求不是变态高的项目,裸机事件驱动完全够用。RTOS不是万能的,它带来的上下文切换开销和调试复杂度,在小项目里反而是负担。

我的建议:先学会裸机事件驱动,再去碰RTOS。你想想看,连事件队列都管理不好,怎么去理解消息队列和信号量?基础打牢了,上层建筑才稳。

1.3 为什么选择事件驱动

我直接说结论:事件驱动是裸机程序从「能跑」到「好维护」的分水岭。

给你讲个真实案例。2018年我做一款手持仪表,MCU是STM32F030,64KB Flash、8KB RAM。功能包括:按键扫描、LCD刷新、ADC采集、串口通信、蜂鸣器报警、电池管理。一共6个模块。

第一版用轮询写,main函数里一个200行的while循环。加一个「长按关机」功能,改了4个文件,测试发现ADC采样率被按键扫描拖慢了。为什么?因为轮询里按键去抖用了delay,把整个循环卡住了。

第二版改成事件驱动。按键按下只发一个事件,ADC采集完成发一个事件,串口收到数据发一个事件。main函数变成这样:

while(1) {
    event_t evt;
    if(event_queue_get(&evt)) {
        event_dispatch(&evt);
    } else {
        // 没有事件,进入休眠
        __WFI();
    }
}

代码量从2000行降到1200行。加功能?加一个新的事件类型就行,不影响其他模块。功耗从15mA降到3mA,因为大部分时间CPU在睡觉。

这就是事件驱动的价值:

  • 解耦:模块之间不直接调用,通过事件通信
  • 可扩展:加功能不加复杂度
  • 低功耗:没事就睡,有事才醒
  • 可测试:每个事件处理器可以单独测试

注意:事件驱动不是银弹。如果你的系统有严格的时间同步需求(比如电机控制PWM周期精确到微秒),或者任务之间有复杂的依赖关系,那还是得上RTOS或者干脆上实时Linux。选型要因地制宜,别为了用而用。

1.4 事件驱动架构全景图

下面这张图,是我画的事件驱动裸机架构的核心结构。你看一遍,心里有个谱就行,后面每一章都会展开讲。

事件驱动裸机架构全景图 事件源层 按键中断 定时器溢出 串口接收 ADC完成 外部触发 事件队列层 FIFO队列 / 优先级队列 / 环形缓冲区 事件调度器 从队列取事件 → 查表 → 调用处理函数 事件处理层 按键处理 定时任务 协议解析 数据存储 状态更新 事件源 → 事件队列 → 调度器 → 处理器:单向流动,模块间零耦合

你看这个结构,数据流是单向的。事件源只管产生事件,丢进队列就走。调度器只管分发,不关心业务逻辑。处理器只管干活,不关心事件从哪来。每个模块各司其职,这就是架构的魅力。

嗯,第一章就到这里。记住一句话:事件驱动不是技术,是思维方式。后面每一章,我都会带着你从零搭一套完整的事件驱动框架。准备好了吗?