1. 状态机基础:概念、三要素与嵌入式应用
各位同学,咱们今天正式开课。先聊点基础的——状态机。
说实话,我刚开始做嵌入式那会儿,代码写得那叫一个“随心所欲”。一个按键处理,if-else 能嵌套七八层,自己过两天再看都懵。后来带我的老工程师扔给我一句话:“小子,你这就是没有状态机的概念。” 从那以后,我才算真正入了门。
1.1 什么是状态机?
状态机,全称有限状态机(Finite State Machine, FSM)。说白了,就是一个系统在任意时刻只能处于“有限个”确定状态中的一个。它根据外部事件,从一个状态跳转到另一个状态。
你想想看,我们平时用的遥控器、智能手环、甚至电梯,本质上都是状态机。电梯不会同时“上升”和“下降”,它要么在上升状态,要么在下降状态,要么停在某层——这就是状态。
1.2 状态机三要素
状态机有三个核心要素,我习惯把它们叫做“三板斧”:状态、事件、转移。缺一个,这机器就转不起来。
| 要素 | 定义 | 嵌入式中的例子 |
|---|---|---|
| 状态 (State) | 系统在某一时刻的稳定“模样”。状态是有限的、可枚举的。 | LED 灯的 ON / OFF;UART 接收的 IDLE / RECV / DONE |
| 事件 (Event) | 触发状态转移的“导火索”。可以是外部输入、定时器超时、内部信号。 | 按键按下、串口收到一个字节、DMA 传输完成中断 |
| 转移 (Transition) | 从一个状态到另一个状态的“路径”。通常包含:当前状态 → 事件 → 动作 → 下一状态。 | ON 状态 + 按键事件 → 关灯动作 → 进入 OFF 状态 |
嗯,这里要注意:转移里经常还带一个“动作”(Action)。动作是转移发生时执行的操作,比如点亮LED、启动定时器、发送数据包。动作不是必须的,但大部分场景下都有。
1.3 状态机在嵌入式中的应用场景
状态机在嵌入式里,那真是无处不在。我做过一个车载仪表盘的项目,整个显示逻辑就是一个巨大的状态机。从“上电自检”到“正常显示”,再到“报警模式”,全靠状态机撑起来的。
常见的应用场景,我帮你列几个:
- 按键/触摸处理: 去抖、短按、长按、双击。每个按键都是一个独立的状态机。
- 通信协议解析: UART、I2C、SPI 的帧接收。比如 Modbus 协议,从 IDLE 到 RECV_ADDR 到 RECV_DATA 到 CHECKSUM。
- 电源管理: 运行态、休眠态、深度睡眠态。事件就是各种唤醒源。
- 外设驱动: 比如 SD 卡初始化,从 CMD0 到 CMD8 到 ACMD41,每一步都是一个状态。
- 任务调度: 简单的前后台系统,主循环就是一个大状态机,根据标志位跳转。
说白了,只要你的系统有“顺序逻辑”或者“模式切换”,就适合用状态机。它让代码变得可读、可维护、可测试。
1.4 状态机核心逻辑图
下面这张图,是我自己画的一个通用状态机结构。它展示了三要素之间的关系。你把它理解透了,后面写代码就顺了。
这张图里,状态 A 和状态 B 是正常流转。状态 C 是异常处理。自转移表示事件不匹配时,状态机留在原地。你看,整个逻辑一目了然。
1.5 一个简单的例子:LED 灯控制
光说不练假把式。咱们写个最简单的 LED 状态机。这个例子我几乎在每个项目里都会用到,用来验证状态机框架好不好使。
// 状态枚举
typedef enum {
LED_OFF,
LED_ON,
LED_BLINK
} led_state_t;
// 事件枚举
typedef enum {
EVT_BUTTON_SINGLE_CLICK,
EVT_BUTTON_DOUBLE_CLICK,
EVT_TIMEOUT
} led_event_t;
// 状态机处理函数
led_state_t led_fsm(led_state_t current_state, led_event_t event) {
led_state_t next_state = current_state; // 默认不转移
switch (current_state) {
case LED_OFF:
if (event == EVT_BUTTON_SINGLE_CLICK) {
led_on(); // 动作:开灯
next_state = LED_ON;
} else if (event == EVT_BUTTON_DOUBLE_CLICK) {
led_blink_start(); // 动作:开始闪烁
next_state = LED_BLINK;
}
break;
case LED_ON:
if (event == EVT_BUTTON_SINGLE_CLICK) {
led_off(); // 动作:关灯
next_state = LED_OFF;
} else if (event == EVT_BUTTON_DOUBLE_CLICK) {
led_blink_start(); // 动作:开始闪烁
next_state = LED_BLINK;
}
break;
case LED_BLINK:
if (event == EVT_BUTTON_SINGLE_CLICK) {
led_blink_stop(); // 动作:停止闪烁
led_off(); // 动作:关灯
next_state = LED_OFF;
} else if (event == EVT_TIMEOUT) {
led_blink_stop(); // 动作:停止闪烁
led_on(); // 动作:常亮
next_state = LED_ON;
}
break;
default:
// 异常处理,回到安全状态
led_off();
next_state = LED_OFF;
break;
}
return next_state;
}
这段代码,你仔细看。它把状态、事件、转移、动作都分得清清楚楚。主循环里只需要不断调用 led_fsm() 就行。这就是状态机的魅力——逻辑清晰,好维护。