2、状态机基础理论:什么是状态机、状态机的三要素(状态、事件、动作)、状态机分类(Moore与Mealy)
各位同学,咱们今天聊聊状态机。说实话,我入行那会儿,第一次接触状态机是在一个自动售货机项目里。当时觉得这东西玄乎,后来才发现——说白了,它就是一套「规矩」,告诉系统在不同情况下该干什么。
你想想看,嵌入式系统里最怕什么?怕乱。按键按一下,灯该亮还是该灭?电机该转还是该停?如果没有一套清晰的规则,代码写到最后就是一团乱麻。状态机就是来解决这个问题的。
什么是状态机?
状态机,全称有限状态机(Finite State Machine, FSM)。它描述了一个系统在有限个状态之间,如何根据外部事件进行切换,并执行相应动作的数学模型。
我习惯用一个比喻:状态机就像你手机里的「勿扰模式」。
- 状态:开启、关闭
- 事件:你手动切换、定时到达
- 动作:静音通知、恢复响铃
是不是很好理解?系统不可能同时处于「开启」和「关闭」两个状态,它只能二选一。这就是有限状态机的核心——在任何时刻,系统只能处于一个状态。
核心定义:状态机 = 有限个状态 + 状态间的转移规则 + 转移时执行的动作。
状态机的三要素
搞懂了什么是状态机,咱们来看看它的三个核心零件。这三个东西缺一不可,我在项目里吃过亏——有一次漏定义了某个事件,结果系统在异常状态下卡死了,查了两天才找到原因。
1. 状态(State)
状态是系统在某一时刻的「快照」。它描述了系统当前所处的稳定情形。
- 特点:状态是离散的、有限的。比如灌溉系统的「待机」、「灌溉中」、「排水」。
- 注意:状态之间必须互斥。不能同时处于「灌溉中」和「待机」。
嗯,这里要注意:状态不是变量,而是一种「模式」。我见过有人把状态定义成「温度大于30度」,这不对——温度是连续值,状态应该是「高温报警」这种离散概念。
2. 事件(Event)
事件是触发状态转移的「导火索」。没有事件,状态机就是一潭死水。
- 常见事件:按键按下、定时器超时、传感器数值越界、串口收到指令。
- 关键点:事件是瞬时的,不是持续存在的。你按一下按钮是一个事件,但按住不放不算——那是另一个事件(长按)。
我曾经在一个项目中,把「水位高于阈值」当作持续条件来处理,结果状态机在边界值附近来回跳,系统像抽风一样。后来改成「水位从低于阈值变为高于阈值」这个上升沿事件,问题就解决了。
3. 动作(Action)
动作是状态转移时系统执行的「操作」。它可以是控制硬件、更新变量、发送数据等。
- 动作发生时机:进入状态时、离开状态时、转移过程中。
- 常见动作:打开继电器、启动定时器、记录日志、点亮LED。
我的小技巧:动作尽量放在「进入状态」时执行,这样逻辑清晰。比如进入「灌溉」状态时打开水泵,离开时关闭水泵。避免在转移过程中做复杂操作,容易遗漏。
状态机分类:Moore与Mealy
状态机分两种主流类型:Moore机和Mealy机。很多教材讲得云里雾里,我换个角度给你讲。
Moore型状态机
特点:输出只取决于当前状态,与输入事件无关。
说白了就是:你处于什么状态,就输出什么信号。不管谁来触发,只要状态不变,输出就不变。
举个例子:一个简单的红绿灯。
- 状态「红灯」:输出红灯亮,持续30秒。
- 状态「绿灯」:输出绿灯亮,持续25秒。
- 状态「黄灯」:输出黄灯亮,持续5秒。
你看,只要处于「红灯」状态,不管有没有车经过,红灯就是亮的。这就是Moore机。
Moore机优点:输出稳定,不会因为输入抖动而突变。适合对安全性要求高的场景,比如电梯控制、工业安全联锁。
Mealy型状态机
特点:输出不仅取决于当前状态,还取决于输入事件。
说白了就是:同样的状态,遇到不同的事件,输出可能不一样。
举个例子:自动门控制。
- 状态「门关闭」:
- 事件「有人靠近」→ 输出「开门动作」
- 事件「手动按钮按下」→ 输出「开门动作」
- 事件「无事件」→ 输出「保持关闭」
同样是「门关闭」状态,不同事件导致不同输出。这就是Mealy机。
避坑指南:Mealy机输出对输入敏感,容易产生毛刺。我曾经在通信协议解析中用Mealy机,结果输入信号有毛刺时,输出乱跳。后来加了边沿检测和去抖,才稳定下来。
两种状态机的对比
| 对比项 | Moore机 | Mealy机 |
|---|---|---|
| 输出依赖 | 仅当前状态 | 当前状态 + 输入事件 |
| 输出稳定性 | 高,无毛刺 | 低,可能产生毛刺 |
| 状态数量 | 通常较多 | 通常较少 |
| 响应速度 | 慢一拍(需等状态稳定) | 快(输入变化立即响应) |
| 典型应用 | 安全系统、协议解析 | 实时控制、数据流处理 |
我个人习惯:能用Moore就用Moore。为什么?因为调试方便。你只要看当前状态,就知道输出是什么。Mealy机虽然状态少,但调试时得同时看状态和输入,脑细胞死得快。
一个简单的代码示例
光说不练假把式。我写一个灌溉系统的Moore状态机片段,你感受一下。
// 状态定义
typedef enum {
STATE_IDLE, // 待机
STATE_IRRIGATE, // 灌溉中
STATE_DRAIN, // 排水
STATE_ALARM // 报警
} SystemState_t;
// 当前状态
SystemState_t currentState = STATE_IDLE;
// 状态机主循环
void StateMachine_Run(Event_t event) {
switch (currentState) {
case STATE_IDLE:
if (event == EVENT_START_IRRIGATE) {
// 动作:打开水泵
Pump_On();
currentState = STATE_IRRIGATE;
}
break;
case STATE_IRRIGATE:
if (event == EVENT_TIMEOUT) {
// 动作:关闭水泵,打开排水阀
Pump_Off();
Valve_Open();
currentState = STATE_DRAIN;
}
if (event == EVENT_SENSOR_ALARM) {
// 动作:紧急停止
Pump_Off();
currentState = STATE_ALARM;
}
break;
case STATE_DRAIN:
if (event == EVENT_DRAIN_DONE) {
// 动作:关闭排水阀
Valve_Close();
currentState = STATE_IDLE;
}
break;
case STATE_ALARM:
if (event == EVENT_RESET) {
currentState = STATE_IDLE;
}
break;
}
}
你看,每个状态只处理自己能响应的事件。不相关的事件直接忽略——这就是状态机的健壮性所在。
我的经验:写状态机时,一定要加一个「默认处理」。如果收到未定义的事件,至少记录日志或进入安全状态。别问我为什么知道——有一次我忘了加,系统收到一个干扰信号后直接跑飞了。
小结
今天咱们把状态机的基础捋了一遍。记住三要素:状态、事件、动作。Moore和Mealy的区别,说白了就是「输出看谁脸色」的问题。
下一章,我会带你用状态机实际设计一个智能灌溉系统。到时候你会发现,状态机这东西,一旦用顺手了,写代码就像搭积木一样清晰。
嗯,今天就到这儿。有什么问题,咱们群里聊。