一、状态机基础:什么是状态机?为什么用在门锁中?Moore与Mealy状态机区别
大家好,我是你们这门课的老朋友。今天咱们正式开篇,聊聊状态机。
说实话,我刚开始做嵌入式那会儿,对状态机这玩意儿也是云里雾里的。总觉得不就是几个if-else嘛,搞那么玄乎干嘛?直到有一次,我负责一个门锁项目,客户要求「长按3秒进入配对模式,短按1秒开锁,连续输错3次密码锁定5分钟」——好家伙,用if-else写出来的代码,我自己都看不懂了。bug改了一个又一个,最后干脆推倒重来,老老实实用状态机。
嗯,从那以后,我再也不敢小看状态机了。
1.1 什么是状态机?
说白了,状态机就是一个「有记忆的自动售货机」。
你投一块钱,它记住你投了;你再投一块,它知道你够两块钱了,可以出货了。这个「记住」的过程,就是状态。
用专业点的话说:状态机(State Machine)是一种数学模型,用来描述系统在有限个状态之间如何切换。它由三部分组成:
- 状态(State):系统在某一时刻所处的「模式」
- 事件(Event):触发状态切换的条件
- 动作(Action):进入某个状态或发生切换时执行的操作
举个例子,咱们的门锁:
- 状态:上锁、解锁、报警
- 事件:刷卡、输密码、超时
- 动作:开锁、关锁、响警报
核心要点:状态机不是万能的,但处理「有明确阶段、有触发条件、有输出动作」的逻辑,它是最优雅的方案。没有之一。
1.2 为什么门锁要用状态机?
你想想看,门锁的工作流程天然就是「状态化」的。
我见过不少新手,用一堆全局变量加if-else来写门锁逻辑。比如:
// 反面教材
int isLocked = 1;
int wrongCount = 0;
int isPairing = 0;
unsigned long lastPressTime = 0;
void loop() {
if (buttonPressed()) {
if (isPairing) {
// 配对逻辑
} else if (isLocked) {
// 开锁逻辑
} else {
// 其他逻辑
}
}
// 各种乱七八糟的判断...
}
这种代码,写的时候爽,改的时候哭。为什么?
- 变量之间互相依赖,改一个可能崩一片
- 逻辑分散,你根本不知道当前系统到底在干嘛
- 加一个新功能,得把所有if-else翻一遍
用状态机就不一样了:
// 状态机版本
typedef enum {
STATE_LOCKED,
STATE_UNLOCKED,
STATE_PAIRING,
STATE_ALARM
} DoorState_t;
DoorState_t currentState = STATE_LOCKED;
void stateMachine_run(Event_t event) {
switch (currentState) {
case STATE_LOCKED:
if (event == EVENT_CARD_SCAN) {
currentState = STATE_UNLOCKED;
action_unlock();
}
break;
case STATE_UNLOCKED:
if (event == EVENT_TIMEOUT) {
currentState = STATE_LOCKED;
action_lock();
}
break;
// 其他状态...
}
}
看到了吗?每个状态只管自己的事,逻辑清晰,加新状态也不影响老的。这就是状态机的魅力。
我的经验:只要你的系统有超过3个「模式」或「阶段」,就值得用状态机。别等到代码写成一团乱麻再重构,那成本高得多。
1.3 Moore与Mealy状态机区别
好,接下来这个知识点,很多教材讲得云里雾里。我尽量用大白话讲清楚。
状态机分两种:Moore型和Mealy型。区别就一句话:
- Moore型:输出只取决于当前状态
- Mealy型:输出取决于当前状态 + 输入事件
听起来抽象?咱们用门锁举例。
Moore型门锁
假设门锁有两个状态:LOCKED和UNLOCKED。
- 在LOCKED状态,输出永远是「锁住」
- 在UNLOCKED状态,输出永远是「打开」
不管你怎么刷卡、按密码,只要状态没变,输出就不变。这就是Moore型。
Mealy型门锁
同样是LOCKED状态,但刷卡时输出「开锁」,输错密码时输出「报警」。
看到了吗?同一个状态(LOCKED),不同的输入事件,输出不一样。这就是Mealy型。
| 对比项 | Moore型 | Mealy型 |
|---|---|---|
| 输出依据 | 仅当前状态 | 当前状态 + 输入事件 |
| 状态数量 | 通常更多 | 通常更少 |
| 响应速度 | 慢一拍(需等待时钟沿) | 快(输入变化立即响应) |
| 代码复杂度 | 简单直观 | 稍复杂,需考虑输入组合 |
| 典型应用 | 控制类、安全类系统 | 通信协议、数据流处理 |
我个人习惯,在门锁这种安全相关的系统里,优先用Moore型。为什么?因为输出只跟状态挂钩,不容易出现「输入毛刺导致误动作」的情况。我曾经在一个项目中,用Mealy型做门锁的报警逻辑,结果因为按键抖动,同一个刷卡事件被触发了两次,直接报了假警...从那以后,安全相关的逻辑我全用Moore型。
避坑指南:如果你用Mealy型,一定要做好输入信号的消抖和同步处理。否则,一个毛刺就能让你的系统「抽风」。
1.4 实际项目中怎么选?
我给大家一个简单的判断方法:
- 如果输出动作比较「重」(比如开锁、关锁、报警),用Moore型。因为状态切换是明确的,输出不会乱跳。
- 如果输出动作比较「轻」(比如更新显示、发送一个字节),而且对响应速度有要求,用Mealy型。
- 如果拿不准,先用Moore型。它更安全,也更容易调试。
记住一句话:状态机不是用来炫技的,是用来解决问题的。选哪种,看你的场景,别死磕。
好了,这一章就到这里。下一章咱们手把手画一个门锁的状态图,然后直接生成代码。到时候你会发现,状态机这东西,一旦用上了就回不去了。