单片机按键开发中的状态机切换流程

先贴出安装状态枚举类型:

/* 定义按键的状态枚举类型 */
enum button_state {
    BUTTON_STATE_IDLE = 0,              /* 空闲状态 */
    BUTTON_STATE_PRESS_DEBOUNCE,        /* 按下消抖状态 */
    BUTTON_STATE_PRESSED,               /* 成功按下状态 */
    BUTTON_STATE_LONG_PRESS_ONCE,       /* 长按状态,单次触发 */
    BUTTON_STATE_LONG_PRESS_CONTINUOUS, /* 长按状态,连续触发 */
    BUTTON_STATE_RELEASE_DEBOUNCE       /* 释放消抖状态 */
};

一、 状态切换流程图 (Mermaid 文本渲染)

如果你使用的 Markdown 编辑器(如 Notion、Obsidian、Typora 等)支持 Mermaid,可以直接复制以下代码生成图表;这里也直接为你呈现其逻辑拓扑:

graph TD
START([系统初始化]) --> IDLE[BUTTON_STATE_IDLE]
IDLE -- "1.a 检测到物理按下(引脚变低电平)" --> PRESS_DEB[BUTTON_STATE_PRESS_DEBOUNCE]
PRESS_DEB -- "2a.抖动杂讯(电平恢复高电平)" --> IDLE
PRESS_DEB -- "2b.消抖20ms到&&依旧低电平" --> PRESSED[BUTTON_STATE_PRESSED]
PRESSED -- "3a.物理松开,触发【单击事件】" --> REL_DEB[BUTTON_STATE_RELEASE_DEBOUNCE]
PRESSED -- "3b.按下时长>1s长按阈值" --> LONG1[BUTTON_STATE_LONG_PRESS_ONCE]
LONG1 -- "4a.物理松开,触发【长按释放】" --> REL_DEB
LONG1 -- "4b.按下>200ms连发间隔" --> LONG_CONT["BUTTON_STATE_LONG_PRESS_CONTINUOUS<br/>5a:每200ms触发连发事件"]
%% 自环只写短标签
LONG_CONT -- "5a.保持按下" --> LONG_CONT
LONG_CONT -- "5b.物理松开" --> REL_DEB
REL_DEB -- "6a.松开消抖20ms到&&确定高电平" --> RELEASED[BUTTON_STATE_RELEASED]
RELEASED -- "7.a自动复位(通知应用层后复位)" --> IDLE

下面是流程截图:

二、 核心跳转条件与动作详解

在你的 switch-case 轮询代码中,这 7 个状态的内在驱动逻辑如下:

1. 空闲 \rightarrow 按下消抖 (IDLE \rightarrow PRESS_DEBOUNCE)

  • 触发条件: 读取到硬件引脚电平为低(物理按下)。
  • 执行动作: 启动/重置内部的消抖计数器(如 debounce_count = 0)。

2. 消抖判定 (PRESS_DEBOUNCE 的分叉)

  • 分支 A (退回 IDLE): 如果在消抖时间内(例如没满 20ms),电平突然变回了高电平。判定为火花或电磁干扰(噪声抖动),直接打道回府。
  • 分支 B (走向 PRESSED): 消抖时间顺利数满(如轮询了 2 次 10ms),且电平依然是低电平。判定为真正按下,记录当前的系统 tick 计数用于计算长按。

3. 确定按下 (PRESSED 的分叉)

  • 分支 A (去松开消抖): 如果在长按时间阈值(如 1000ms)到来之前,用户松开了按键(电平变高)。此时判定为标准的“短按/单击”,向队列发送 CLICK 事件,并去往 RELEASE_DEBOUNCE
  • 分支 B (去长按单次): 用户死死按住不放,按下计时器累加达到了长按阈值(1000ms)。此时判定为“长按触发”,向队列发送 LONG_PRESS 事件,状态转移至 LONG_PRESS_ONCE

4. 长按单次 \rightarrow 长按连续 (LONG_PRESS_ONCE \rightarrow LONG_PRESS_CONTINUOUS)

  • 分支 A (去松开消抖): 在触发了长按后,用户松手了。去往 RELEASE_DEBOUNCE
  • 分支 B (进阶连发): 用户继续按着不撒手。时间再次跨过设定的“连发间隔”(比如又过了 200ms),说明用户需要像调时钟一样“一直加数字”,状态转移至 LONG_PRESS_CONTINUOUS

5. 持续连发循环 (LONG_PRESS_CONTINUOUS 自迭代)

  • 自循环: 只要电平是低,每隔固定的连发间隔(如 200ms),就重复触发一次按键事件。状态依然保持为 LONG_PRESS_CONTINUOUS
  • 松开: 只要电平变高,随时打破循环,去往 RELEASE_DEBOUNCE

6. 释放消抖 \rightarrow 释放成功 (RELEASE_DEBOUNCE \rightarrow RELEASED)

  • 意义: 物理按键在弹起时也会产生机械抖动(后沿抖动)。
  • 逻辑: 确保电平稳定保持为高电平达到消抖时间后,状态走向 RELEASED

7. 释放成功 \rightarrow 空闲归位 (RELEASED \rightarrow IDLE)

  • 清理现场: 到了这一步,标志着一个完整的按键生命周期(按下-判定-长/短按处理-松开-判定)彻底结束。代码中将按键的所有计时器清零,状态恢复为初始的 IDLE,静静等待下一次被按下。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Index