3. 嵌入式系统中的事件类型
事件驱动架构的核心,说白了就是「对发生的事情做出反应」。那嵌入式系统里到底会发生哪些事?我做了十几年嵌入式开发,接触过的项目少说也有几十个,总结下来,事件类型基本就这五类。咱们一个一个聊。
3.1 外部中断事件
这是最「硬」的一类事件。外部设备通过芯片的物理引脚,直接触发 CPU 中断。比如按键按下、电平跳变、边沿触发。
我记得刚入行时做智能家居面板,按键扫描用的是轮询。产品上市后客户投诉反应慢,尤其在多任务场景下。后来改成外部中断触发,问题立刻解决。你想想看,轮询就像你每隔一秒去门口看有没有快递,中断则是快递员按门铃叫你。哪个效率高?
典型应用场景:
- 按键/开关状态变化
- 外部传感器触发(如红外感应)
- 电源掉电检测
- 机械限位开关触发
3.2 定时器事件
定时器事件是嵌入式系统的「心跳」。没有它,很多周期性任务就没法做。比如每 10ms 采集一次传感器数据,每 100ms 刷新一次屏幕,每 1s 上报一次设备状态。
我个人习惯把定时器事件分为两类:
| 类型 | 特点 | 典型用途 |
|---|---|---|
| 硬件定时器 | 精度高,占用硬件资源 | PWM 生成、精确延时、电机控制 |
| 软件定时器 | 基于系统滴答,数量灵活 | 超时检测、周期性任务调度、看门狗喂狗 |
这里有个坑。我曾经在一个项目中,同时开了 20 多个软件定时器,结果发现系统响应越来越慢。排查后发现,定时器回调函数里做了太多事情,导致事件队列被塞满。后来我定了个规矩:定时器回调里只发事件,不处理事件。问题迎刃而解。
3.3 传感器数据就绪事件
这类事件在 IoT 设备里特别常见。传感器完成一次采集后,通过中断或 DMA 通知 CPU:「数据准备好了,快来取。」
为什么要把这个单独列出来?因为传感器数据就绪事件有个特点:它往往伴随着数据量。不像按键事件只传一个键值,传感器事件可能需要传递一个结构体,包含温度、湿度、时间戳等信息。
我建议的做法是:
- 传感器 ISR 中只设置「数据就绪」标志
- 主循环或任务中检测到标志后,读取传感器数据
- 将数据封装成事件结构体,放入事件队列
- 事件处理器根据数据类型分发处理
// 传感器数据就绪事件结构体示例
typedef struct {
uint32_t event_id; // 事件ID,如 EVT_TEMP_READY
uint32_t timestamp; // 时间戳
union {
struct {
float temperature;
float humidity;
} env_sensor;
struct {
uint16_t distance;
uint8_t quality;
} range_sensor;
} data;
} sensor_event_t;
3.4 通信接口接收事件
UART、SPI、I2C、CAN、Ethernet……只要涉及通信,就离不开接收事件。数据来了,你得知道,还得及时处理。
这类事件有个麻烦:你不知道数据什么时候来,也不知道来多少。可能是 1 个字节,也可能是 1KB 的报文。
我的处理原则是:
- 接收中断只做数据搬运(从硬件 FIFO 搬到内存缓冲区)
- 当检测到完整报文(比如收到换行符或固定长度),才触发「接收完成事件」
- 事件中携带数据指针和长度,避免数据拷贝
3.5 软件触发事件
这类事件不是由硬件产生的,而是由软件自身触发的。比如:
- 任务 A 处理完数据后,通知任务 B 可以开始工作了
- 状态机检测到超时,触发超时事件
- 用户通过命令行输入指令,触发配置更新事件
软件触发事件的好处是:你完全可控。不会像外部中断那样不可预测,也不会像定时器那样有固定周期。它是你用来编排系统逻辑的「软开关」。
我个人习惯把软件事件分为同步和异步两种:
| 类型 | 触发方式 | 典型场景 |
|---|---|---|
| 同步软件事件 | 函数调用直接触发 | 状态机跳转、函数回调 |
| 异步软件事件 | 通过事件队列投递 | 任务间通信、延迟处理 |
嗯,这里要注意:同步事件虽然简单直接,但如果处理时间过长,会阻塞调用者。异步事件虽然灵活,但需要管理好事件队列的生命周期。没有银弹,只有权衡。
3.6 事件类型关系图
下面这张图展示了五类事件在嵌入式系统中的位置和关系。你可以看到,所有事件最终都汇聚到事件队列,由事件处理器统一调度。
3.7 事件类型的优先级考量
不同事件对实时性的要求不一样。外部中断事件通常要求立即响应,而软件触发事件可以稍微排队。我个人习惯给事件分配优先级:
| 优先级 | 事件类型 | 响应要求 | 典型处理方式 |
|---|---|---|---|
| 最高 | 外部中断事件 | 微秒级 | ISR 中直接处理或置标志 |
| 高 | 通信接收事件 | 毫秒级 | DMA + 中断 + 事件队列 |
| 中 | 传感器就绪事件 | 毫秒~十毫秒级 | 事件队列 + 任务调度 |
| 中低 | 定时器事件 | 取决于周期 | 软件定时器 + 事件队列 |
| 低 | 软件触发事件 | 可排队 | 事件队列 + 空闲处理 |
当然,这个优先级不是死的。比如一个 1ms 的定时器事件,优先级可能比某些通信事件还高。关键是你得根据项目需求自己权衡。