按键消抖可以用滑动窗口或积分法过滤抖动

32位移位寄存器滑动窗口法原理图

机械按键在按下和松开瞬间有几毫秒到十几毫秒的触点抖动,直接读 GPIO 会把一次按压识别成多次。

常见软件做法是检测到电平变化时记一个时间戳,挂一个软件定时器,到时再回来检查电平是否仍然稳定。逻辑没问题,但需要维护"上一次电平"、"定时器句柄"、"等待中"等额外状态。

下面这种方法只用一个 uint32_t 变量加一个固定节拍的扫描。

原理

每 1ms 采样一次按键电平,左移进 32 位历史寄存器:

history = (history << 1) | sample;

history 保存最近 32 次(32ms)采样。判定规则:

抖动只要还落在 32ms 窗口里,history 就不会是全 1 或全 0,状态被冻结。

实现

按键低电平有效,1ms 定时器中断驱动扫描:

#include <stdint.h>

static uint32_t key_history = 0xFFFFFFFFu;
static uint8_t  key_state   = 0;

void key_scan_1ms(void)
{
    uint32_t sample = (read_key_gpio() == 0) ? 1u : 0u;

    key_history = (key_history << 1) | sample;

    if (key_history == 0xFFFFFFFFu) {
        key_state = 1;
    } else if (key_history == 0x00000000u) {
        key_state = 0;
    }
}

需要"按下瞬间"事件时加一个上次状态:

static uint8_t key_state_prev = 0;

uint8_t key_pressed_edge(void)
{
    uint8_t edge = (key_state_prev == 0) && (key_state == 1);
    key_state_prev = key_state;
    return edge;
}

参数

另一种思路:积分消抖法

滑动窗口法严格要求 32 次采样完全一致,任何一次反向毛刺都会重置窗口。工程现场如果存在偶发尖刺干扰,按键事件会被无限推迟。

积分消抖法换一种判定方式:用一个计数器累计趋势,类似软件实现的 RC 滤波。

#define CNT_MAX  10

static uint8_t cnt = 0;
static uint8_t key_state = 0;

void key_scan_1ms(void)
{
    uint8_t sample = (read_key_gpio() == 0) ? 1 : 0;

    if (sample) {
        if (cnt < CNT_MAX) cnt++;
    } else {
        if (cnt > 0) cnt--;
    }

    if (cnt == CNT_MAX) {
        key_state = 1;
    } else if (cnt == 0) {
        key_state = 0;
    }
}

判定门限可调(CNT_MAX)、抗毛刺、只需要一个计数变量。

两种方案对比

32位滑动窗口法与积分消抖法对比图

对比项 32位滑动窗口法 积分消抖法
判定语义 最近 N 次采样必须完全一致 趋势累积到阈值
抗偶发毛刺 弱,一次反向采样即重置窗口 强,单次毛刺只让计数回退一格
阈值调节 只能改窗口位宽,粒度粗 CNT_MAX 任意整数,粒度细
状态信息 保留最近 N 次原始采样序列 只剩一个计数值
RAM 占用 每键 4 字节(uint32_t)+ 状态 每键 1 字节计数 + 状态

本质:都是脉宽判定

两种方法做的是同一件事 —— 判定输入电平的稳定脉宽是否超过阈值。

抖动是窄脉冲,按键事件是宽脉冲,所有软件消抖归根到底都是在时域上设一个宽度门限。差别只在门限"硬不硬"。

结论:

本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。