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

机械按键在按下和松开瞬间有几毫秒到十几毫秒的触点抖动,直接读 GPIO 会把一次按压识别成多次。
常见软件做法是检测到电平变化时记一个时间戳,挂一个软件定时器,到时再回来检查电平是否仍然稳定。逻辑没问题,但需要维护"上一次电平"、"定时器句柄"、"等待中"等额外状态。
下面这种方法只用一个 uint32_t 变量加一个固定节拍的扫描。
原理
每 1ms 采样一次按键电平,左移进 32 位历史寄存器:
history = (history << 1) | sample;history 保存最近 32 次(32ms)采样。判定规则:
history == 0xFFFFFFFF:连续 32 次为 1,稳定按下,state = 1。history == 0x00000000:连续 32 次为 0,稳定松开,state = 0。- 其他:窗口里还有抖动位,
state保持不变。
抖动只要还落在 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;
}参数
- 采样周期 1ms × 窗口 32 位 = 32ms 稳定判定时间。换
uint16_t得 16ms 窗口。 key_history初值设为0xFFFFFFFF对应"松开",避免上电瞬间被误判成按下。- 多按键就把
history和state做成数组,扫描里循环一次。
另一种思路:积分消抖法
滑动窗口法严格要求 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;
}
}sample = 1:计数器加一,触顶后state = 1。sample = 0:计数器减一,触底后state = 0。- 中间区域保持原状态,偶发反向毛刺只把计数器拉低一格,不会击穿判定。
判定门限可调(CNT_MAX)、抗毛刺、只需要一个计数变量。
两种方案对比

| 对比项 | 32位滑动窗口法 | 积分消抖法 |
|---|---|---|
| 判定语义 | 最近 N 次采样必须完全一致 | 趋势累积到阈值 |
| 抗偶发毛刺 | 弱,一次反向采样即重置窗口 | 强,单次毛刺只让计数回退一格 |
| 阈值调节 | 只能改窗口位宽,粒度粗 | CNT_MAX 任意整数,粒度细 |
| 状态信息 | 保留最近 N 次原始采样序列 | 只剩一个计数值 |
| RAM 占用 | 每键 4 字节(uint32_t)+ 状态 | 每键 1 字节计数 + 状态 |
本质:都是脉宽判定
两种方法做的是同一件事 —— 判定输入电平的稳定脉宽是否超过阈值。
- 滑动窗口法:要求脉宽 ≥ N×Ts,且中途不允许反向脉冲,等价于一个零容忍的脉宽阈值判定。
- 积分消抖法:要求"按下"采样数减"松开"采样数的净累积 ≥
CNT_MAX,等价于带容忍度的脉宽阈值判定,允许窄毛刺嵌在长脉冲里。
抖动是窄脉冲,按键事件是宽脉冲,所有软件消抖归根到底都是在时域上设一个宽度门限。差别只在门限"硬不硬"。
结论:
- 默认用积分消抖法。
CNT_MAX可以根据实测抖动调到合适值,抗毛刺,RAM 占用低。 - 选 32 位滑动窗口法的两种情况:一是需要排查接触不良或硬件抖动,
history的二进制位直接就是采样波形;二是上层要求"必须连续 N 次稳定才认"的严格语义,例如安全相关输入。
本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。