4、状态机基础理论:什么是状态机?
状态机,说白了就是让程序“记住自己干到哪了”。
我刚开始做嵌入式那会儿,写按键逻辑就是一堆 if-else 嵌套。按下、抬起、抖动、长按……代码越写越乱,最后自己都看不懂。后来才明白——状态机就是用来治这个病的。
4.1 状态机的本质
状态机(State Machine)是一种数学模型。它描述了一个对象在不同“状态”之间如何切换。你想想看,现实世界里到处都是状态机:
- 电梯:停在1楼、正在上行、停在2楼、正在下行……
- 红绿灯:红灯→绿灯→黄灯→红灯……
- 按键:未按下、抖动中、已按下、抬起中……
在单片机里,状态机帮我们把“当前在做什么”和“下一步该做什么”拆开。代码结构清晰了,bug 自然就少了。
4.2 四要素:状态、事件、转移、动作
一个完整的状态机,离不开这四个东西。我习惯把它们叫做“状态机的四根柱子”:
| 要素 | 英文 | 一句话解释 | 按键消抖例子 |
|---|---|---|---|
| 状态 | State | 系统当前所处的稳定阶段 | 空闲态、消抖态、确认态 |
| 事件 | Event | 触发状态转移的外部或内部信号 | 检测到电平变化、定时器超时 |
| 转移 | Transition | 从一个状态到另一个状态的路径 | 空闲态→消抖态(当检测到下降沿) |
| 动作 | Action | 进入/离开状态时执行的操作 | 启动定时器、更新按键标志位 |
嗯,这里要注意:动作不一定只在转移时发生。有些动作是“进入状态时执行一次”,有些是“在状态内持续执行”。这个区别后面写代码时会遇到。
4.3 状态机的两种常见模型
实际项目中,我们主要用两种:
- Moore 型:输出只取决于当前状态。比如按键状态机里,“按键按下”这个输出只在“确认态”才为真。
- Mealy 型:输出取决于当前状态 + 输入事件。比如串口接收时,收到特定字节才触发某个动作。
我个人习惯:嵌入式裸机里优先用 Moore 型。为什么?因为输出稳定,不容易出现毛刺。我在一个电机控制项目里吃过 Mealy 型的亏——事件和输出耦合太紧,调试时根本分不清是状态问题还是事件问题。
4.4 用流程图描述按键消抖状态机
光讲理论太干。咱们拿一个最经典的应用——按键消抖——来画张图。
按键按下时,金属触点会弹跳 5~20ms。如果不做消抖,一次按下可能被识别成好几次。状态机怎么处理?看下面这张 SVG 流程图:
这张图里,三个状态、三个事件、四条转移路径、三个动作,清清楚楚。你想想看,如果用 if-else 写这个逻辑,得嵌套多少层?
4.5 状态机在裸机中的实现思路
在 STM32 裸机里,状态机通常用 switch-case 或 函数指针数组 来实现。我个人更推荐 switch-case 起步,因为可读性好,调试时一眼就能看到所有状态。
一个典型的状态机骨架长这样:
typedef enum {
STATE_IDLE,
STATE_DEBOUNCE,
STATE_CONFIRMED
} KeyState_t;
KeyState_t currentState = STATE_IDLE;
uint8_t keyPressed = 0;
void KeyFSM_Run(void) {
uint8_t input = ReadKeyPin();
switch (currentState) {
case STATE_IDLE:
if (input == 0) { // 检测到下降沿
StartTimer(10); // 启动10ms定时器
currentState = STATE_DEBOUNCE;
}
break;
case STATE_DEBOUNCE:
if (Timer_IsExpired()) {
if (ReadKeyPin() == 0) { // 电平仍然为低
keyPressed = 1; // 确认按下
currentState = STATE_CONFIRMED;
} else {
currentState = STATE_IDLE; // 抖动,回到空闲
}
}
break;
case STATE_CONFIRMED:
if (input == 1) { // 检测到上升沿
keyPressed = 0; // 清除标志
currentState = STATE_IDLE;
}
break;
default:
currentState = STATE_IDLE;
break;
}
}
4.6 状态机的设计原则
写多了状态机,我总结了几条铁律:
- 状态要互斥:任何时候只能处于一个状态。别搞出“半空闲半消抖”这种怪胎。
- 事件要明确:每个转移只能由一个事件触发。别让两个事件同时抢着转移。
- 动作要干净:进入状态时做的事,离开状态时记得清理。不然状态切来切去,资源就泄漏了。
- 状态图先画再写代码:我见过太多人上来就写 switch-case,写到一半发现状态漏了。先画图,再编码,省心省力。
4.7 本章小结
状态机不是什么高深理论。它就是一套“当前状态 + 输入事件 → 下一个状态 + 执行动作”的规则表。按键消抖只是它的一个应用,后面你会看到状态机在菜单导航、通信协议解析、任务调度里大显身手。
嗯,记住四要素:状态、事件、转移、动作。先把这张图画明白,再动手写代码。顺序别搞反了。