事件驱动架构把中断、定时器和输入转换为事件,再由状态机根据当前状态执行短小动作。它解决的不是“如何写一个循环”,而是如何把隐藏在大量 if、延时和标志位里的状态关系显式写出来。这样超时、取消和异常输入才有清楚的去处。

中断/定时器产生事件事件入队调度器分发状态机执行转换输出动作并等待下一事件
状态图状态、事件、守卫条件和动作各自负责一件事。
事件源中断、定时器、通信和按键只产生事实,不做业务决策。事件队列规定容量、丢弃策略和生产者上下文。当前状态例如 idle、connecting、running、fault,必须可观察。守卫条件在转移前检查资源、权限和输入是否仍有效。转移动作保持短小,必要的耗时工作交给后续任务。超时事件与普通事件一样进入状态机,避免散落的阻塞延时。

中断里只投递事件

中断处理程序应该尽快读取必要状态、清除硬件标志并投递事件。它不应等待通信、调用复杂回调或执行状态转换,因为这些操作会放大中断延迟,也容易与主循环并发修改同一份状态。事件队列的生产者、消费者和满队列策略要明确:关键故障事件不能被普通日志挤掉。

让所有退出路径都可见

好的状态机不仅有“成功下一步”,还要有超时、取消、资源缺失和恢复路径。给每个等待状态配置一个超时事件,给每个外部输入定义可接受状态,避免收到旧事件后误触发新的任务。状态转换表比层层嵌套的 switch 更容易审查,尤其适合通信协议、升级流程和设备初始化。

用转换函数守住状态边界

事件结构应包含类型和必要快照,不能只放一个全局标志。状态机消费事件时先判断当前状态,再执行动作并更新状态。无法处理的事件要有统一策略:忽略、记录,或转入 fault,而不是落入未定义路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef enum { ST_IDLE, ST_WAIT_ACK, ST_RUNNING, ST_FAULT } state_t;
typedef enum { EV_START, EV_ACK, EV_TIMEOUT, EV_CANCEL } event_t;

static state_t dispatch(state_t state, event_t event)
{
switch (state) {
case ST_IDLE:
if (event == EV_START) {
send_request();
arm_timeout();
return ST_WAIT_ACK;
}
break;
case ST_WAIT_ACK:
if (event == EV_ACK) {
cancel_timeout();
return ST_RUNNING;
}
if (event == EV_TIMEOUT || event == EV_CANCEL)
return ST_IDLE;
break;
default:
return ST_FAULT;
}
return state;
}

当一次操作可以被取消并重新开始时,事件最好携带 generation 或 request ID。状态机只接受与当前操作一致的 ACK 和超时,避免上一轮迟到事件破坏新状态。

证据边界

这种结构不能自动保证实时性。最坏响应时间还取决于队列深度、事件生产速率、单次转换耗时和中断屏蔽时间。EFSM 链接用于观察一种实现方式,具体队列并发、内存占用和许可证仍需按目标项目核对。

参考:EFSM