一、状态机基础:什么是状态机?为什么用在门锁中?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型。它更安全,也更容易调试。

记住一句话:状态机不是用来炫技的,是用来解决问题的。选哪种,看你的场景,别死磕。

好了,这一章就到这里。下一章咱们手把手画一个门锁的状态图,然后直接生成代码。到时候你会发现,状态机这东西,一旦用上了就回不去了。