机械按键为什么要消抖?同步和消抖有何区别?
更新时间:2026-09-21。问题驱动短答页。GPIO 去抖实现见 GPIO 去抖与同步、施密特输入去抖,亚稳态见 metastability,同步器见 两级触发器。
一句话回答
机械按键的金属触点在按下和松开瞬间不是一次干净的通断,而会在几毫秒内反复“接触—弹开—再接触”,输出一串窄脉冲(bounce);如果直接送进时钟边沿逻辑或中断/计数器,会被当成连续多次按下或多次中断。所以要消抖:典型数字做法是先经两级触发器同步(解决异步输入的亚稳态),再用一个计数器或移位寄存器要求输入电平连续稳定 N 个时钟周期才更新“已按下/已松开”的确认状态。注意同步和消抖解决的是两个不同问题:同步器防的是亚稳态(一个沿采在禁区、需收敛时间),消抖防的是机械弹跳造成的多个电平跳变(需要毫秒级稳定判据)。施密特触发器只负责把缓慢/带噪的模拟边沿整形成干净数字沿,不能消除弹跳。
两个问题分开看
| 问题 | 成因 | 时间尺度 | 手段 |
|---|---|---|---|
| 亚稳态 | 异步输入落在建立/保持窗口 | ns~一个周期 | 两级/多级同步 |
| 机械抖动 | 触点物理弹跳 | 数 ms | 稳定计时/移位确认 |
| 缓慢边沿/噪声 | RC、长走线 | 模拟 | 施密特触发器 |
先同步、后消抖;只做其中一个不够:只同步仍会把多个抖动脉冲忠实同步过去。
数字消抖结构
verilog
// 先两级同步
always @(posedge clk) begin s1 <= key_in; s2 <= s1; end
// 再要求连续 N 拍一致才确认
always @(posedge clk or negedge rst_n)
if (!rst_n) begin cnt <= 0; key_db <= 1'b1; end
else if (s2 !== key_db) begin
if (cnt == N-1) begin key_db <= s2; cnt <= 0; end
else cnt <= cnt + 1;
end else cnt <= 0;N 由“时钟频率 × 抖动时间(常取 5~20ms)”决定。也可用“移位寄存器全 0/全 1 才更新”。
其他做法
- 施密特触发 + RC:模拟整形,常作输入第一级,见 施密特去抖。
- MCU 软件消抖:检测到沿后延时 10ms 再采样确认,或定时轮询取稳定值,见 中断与轮询。
- GPIO/MCU 内置数字去抖单元:可配置采样周期,见 GPIO 去抖。
- 用作中断/唤醒的按键尤其要消抖,否则中断风暴,见 中断/DMA 入门。
常见坑
- 只加同步器不消抖,中断仍触发多次。
- 用消抖时间去防亚稳态——毫秒计数器解决不了 ns 级亚稳。
- N 取得太短没盖住弹跳,或太长导致按键响应迟钝。
- 复位/默认电平与按键有效电平(常开/常闭)搞反。
总结
机械触点通断瞬间有数毫秒弹跳,直接进时钟逻辑会被当成多次事件,必须先两级同步消除异步亚稳、再用计数器或移位寄存器要求电平连续稳定若干毫秒才确认;同步解决的是建立保持违例导致的亚稳态、消抖解决的是物理弹跳产生的多个跳变,二者层级不同且都需要,施密特触发器只整形模拟边沿、不能去抖,软件则可用延时重采样或 GPIO 内置去抖单元实现同样目的。