四行代码完成单片机的按键边沿检测

1、什么是按键抖动

按键抖动是指机械开关(如按键、拨动开关等)在按下或释放时,由于触点的物理弹性,导致信号在短时间内出现多次快速振荡(高低电平跳变)的现象。这种抖动会导致单片机误判为多次触发,从而引发错误操作。

按键抖动的成因

  1. 机械特性
    开关触点由金属片构成,闭合或断开时会产生弹性振动,接触电阻不稳定。
    • 按下时:触点可能弹跳多次才完全闭合。
    • 释放时:触点可能弹跳多次才完全断开。
  2. 抖动时间
    通常持续 5ms~50ms(不同开关差异较大),例如:
    • 轻触按键:约 10~20ms
    • 老式机械开关:可能达 50ms

抖动的危害

  1. 误触发
    • 按一次按键,单片机可能检测到多次上升沿/下降沿(例如:计数器误增)。
  2. 逻辑错误
    • 在状态机或菜单系统中,抖动可能导致跳过预期步骤。

抖动现象示意图:
在这里插入图片描述

2、按键扫描

以某一STM32的板子按键原理图为例:

在这里插入图片描述

按键扫描函数:key_read()

/**
 * @brief 读取按键状态
 *
 * 该函数读取连接在 GPIO 引脚上的按键状态,并返回相应的按键编号。
 *
 * @return 返回按键编号。0 表示没有按键按下,1-4 表示对应的按键被按下。
 */
uint8_t key_read(void)
{
  // 用于存储按键状态的临时变量
  uint8_t temp = 0;

  // 检查 GPIOB 引脚 0 的状态
  if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
    temp = 1; // 如果引脚状态为 RESET,则按键 1 被按下

  // 检查 GPIOB 引脚 1 的状态
  if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
    temp = 2; // 如果引脚状态为 RESET,则按键 2 被按下

  // 检查 GPIOB 引脚 2 的状态
  if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET)
    temp = 3; // 如果引脚状态为 RESET,则按键 3 被按下

  // 检查 GPIOA 引脚 0 的状态
  if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
    temp = 4; // 如果引脚状态为 RESET,则按键 4 被按下

  // 返回检测到的按键编号
  return temp;
}

按键扫描函数 key_read()放在定时器中进行扫描,10ms 执行一次,即 10ms读取一次 I/O 状态,进行一次键值编码,这样就完成了按键的消抖。读取到的键值是瞬态的,只能反映按键此刻的状态,无法反映按下按键抬起按键的稳定过程。

3、按键的边沿检测

由于按键按下到释放过程中即有下降沿也有上升沿,所以我们需要通过检测边沿来确定此时按键处于按下状态还是释放状态。

按键处理函数:key_proc()

uint8_t key_val = 0;  // 当前按键状态
uint8_t key_old = 0;  // 前一按键状态
uint8_t key_down = 0; // 按下的按键
uint8_t key_up = 0;   // 释放的按键

/**
 * @brief 按键处理函数
 *
 * 该函数用于扫描按键的状态,并更新按键按下和释放的标志
 */
void key_proc(void)
{
  // 读取当前按键状态
  key_val = key_read();
  // 计算按下的按键(当前按下状态与前一状态异或,并与当前状态相与)
  key_down = key_val & (key_old ^ key_val);
  // 计算释放的按键(当前未按下状态与前一状态异或,并与前一状态相与)
  key_up = ~key_val & (key_old ^ key_val);
  // 更新前一按键状态
  key_old = key_val;
    
}

四行代码第一行:key_val = key_read(); 读取 10ms 更新一次的 I/O 电平状态,并存储在变量 Key_Val,可以理解为临时按键值。

四行代码第四行:key_old= key_val ;,将这次读取到的临时按键值key_val 存储到key_old中,作为下一次的旧的键码值;即:key_val 和

key_old为相差了10ms的临时按键值

四行代码第二行:key_down= key_val & (key_old^ key_val );两个位操作:按位与,按位异或。

  • key_old ^ key_val:异或(XOR)操作,结果为 1 的位表示状态发生变化的按键(无论从按下→释放还是释放→按下)。

假设按下的是按键 4:

  1. key_old =0, key_val=0 未按下 。key_old ^ key_val=0
  2. key_old =0, key_val=4 按下过程中。key_old ^ key_val=0100=4
  3. key_old =4, key_val=4 按下稳定期间。 key_old ^ key_val=0000
  4. key_old =4, key_val=0 抬起过程中。 key_old ^ key_val=0100=4
  • key_old ^ key_val 的运算结果再&上 key_val :与(AND)操作,筛选出按下动作的按键
key_old key_val 对应的按键过程 key_old ^ key_val key_down
0 0 按键未按下 0 0
0 4 按键按下过程中 0100(4) 4
4 4 按键按下稳定时 0 0
4 0 按键抬起过程中 0100(4) 0

由上表可知:第四行代码的第二行 key_down= key_val & (key_old^ key_val );最后的运算结果 key_down只有在按键按下的过程中为按键值,持续时间大约10ms。

四行代码第三行:key_up = ~key_val & (key_old ^ key_val);也是同样的思路,将key_down(检测到按下动作的键码值)取反就得到了key_up (按键释放动作的键码值)

如下表所示:

对应的按键过程 key_down key_up
按键未按下 0 0
按键按下过程中 相应的键码值 0
按键按下稳定时 0 0
按键抬起过程中 0 抬起时的键码值

整体逻辑总结

动作 条件(数学表示) 代码实现
按下检测 当前按下且状态变化(0→1) key_val & (key_old ^ key_val)
释放检测 当前释放且状态变化(1→0) ~key_val & (key_old ^ key_val)

应用示例

假设检测按键1(最低位):

  1. 初始状态
    • key_old = 0(释放),key_val = 0。
  2. 按键按下
    • key_read()返回1key_val = 1。
    • key_down = 1 & (0^1) = 1(检测到按下)。
    • key_up = 0 & (0^1) = 0。
    • key_old = 1。
  3. 按键释放
    • key_read()返回0key_val = 0。
    • key_down = 0 & (1^0) = 0。
    • key_up = 1 & (1^0) = 1(检测到释放)。
    • key_old = 0。

完整过程总结

事件 key_old key_val key_down key_up 说明
初始状态 0 0 0 0 按键未按下
按下按键 0 → 1 0 → 1 1 0 检测到按下动作
保持按下 1 1 0 0 无新动作
释放按键 1 → 0 1 → 0 0 1 检测到释放动作
保持释放 0 0 0 0 无新动作

按键的扫描 Key_Read() 采用定时器进行扫描。10ms 扫描一次,数据 10ms更新一次。

通过这种状态跟踪机制,可以精准识别按键的按下释放事件,适用于需要区分短按、长按、双击等复杂交互的场景。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐