Skip to content

状态机

核心原理公式: 状态机 (逻辑骨架) + 定时器中断 (时间切片/粒度) = 非阻塞的实时多任务处理

我们把这句话拆解开来,看看它们到底是怎么配合的:

一、 定时器:提供“时间切片”(Time Slice)

  • 作用:它是一把**“尺子”**。
  • 原理
    • 如果不作为,CPU 感觉不到时间的流逝。
    • 定时器每 10ms 中断一次,相当于给系统打了一个**“节拍 (Tick)”**。
    • 每一次中断,就是一个基本的时间单位(粒度)
  • 为什么重要?
    • 有了这个节拍,我们就不需要用 delay_ms(1000) 去死等 1 秒钟。
    • 我们只需要数:“滴答”响了 100 次,就是 1 秒钟。

二、 状态机:提供“上下文记忆”(Context)

光有时间切片没用,CPU 每次进中断读一下 GPIO,如果不用状态机,它就是个健忘症患者

  • 场景
    • 第 1 次进中断:读到低电平。CPU:“哦,低的。”
    • 第 10 次进中断:读到低电平。CPU:“哦,低的。”
    • 没有状态机,CPU 不知道这“第 10 次”和“第 1 次”之间是有联系的。
  • 作用:它是一个**“记事本”**。
    • 它记录了**“上一次我处于什么情况”**。
  • 原理
    • 状态变量 (State) 就是记忆。
    • 当 CPU 发现当前是低电平,且状态是 STATE_PRESS_DOWN(按下态),它就知道:“这不是刚按下的,而是已经按下一段时间了。

三、 它们是如何“狼狈为奸”的?(核心逻辑流)

这就是你说的“判断时间粒度”的具体执行过程。让我们看一个长按 1 秒是如何被识别出来的:

  1. 粒度设定:定时器设定 10ms 一次中断(这就是你的粒度)。
  2. 初始状态:状态机处于 IDLE(空闲),计数器 cnt = 0
  3. 开始计数
    • 按下按键。
    • 中断来了,检测到低电平 -> 状态切到 PRESS
  4. 重读与累积(核心)
    • 下一个 10ms 中断来了(重读):GPIO 还是低,状态还是 PRESS -> cnt++ (现在是 1)。
    • 再过 10ms (重读):GPIO 还是低 -> cnt++ (现在是 2)。
    • ...
    • CPU 每次只花几微秒做个加法,然后立马去干别的(比如刷新屏幕)。它没有被卡住!
  5. 阈值判断
    • cnt 累加到 100 时。
    • 数学换算:100次 * 10ms = 1000ms = 1秒
    • 状态机判断:if (cnt > 100) -> 触发长按事件!

结论: 你说得对,核心就是利用定时器的周期性中断(重读),将物理时间转化为“计数值”,再由状态机根据这个“计数值”来决定是否跳转状态。


四、 资深工程师的经验总结:关于“时间粒度”的坑

既然你提到了“时间粒度”,我必须提醒你选择粒度的三个原则

1. 粒度太细(比如 10us)—— CPU 累死

  • 如果你搞个 10us 的定时器去扫按键。
  • 后果:CPU 疯狂进中断,每秒进 10万次。光是进出中断的开销就把 CPU 占满了,主程序会变卡。而按键这种慢速设备根本不需要这么快的反应。

2. 粒度太粗(比如 100ms)—— 手感稀烂

  • 如果你 100ms 才扫一次。
  • 后果:用户快速点击(比如 50ms 内按下松开),你的定时器可能正好夹在中间那段“空隙”里,漏检了。用户会觉得按键“不灵”。

3. 黄金粒度(10ms - 20ms)

  • 对于人手操作的按键,10ms 到 20ms 是最佳平衡点。
  • 既能完美实现消抖(通常抖动在 5-10ms),又能保证 CPU 负载极低,同时用户感觉不到延迟。

总结

定时器是心跳(提供时间基准),状态机是大脑(处理逻辑关系)。**