按一次,为什么可能计数三次?|单片机动画课 03

课程插图

上一课给按钮加上了上拉:松开是 HIGH,按下是 LOW。默认电平已经明确了,为什么按一下,计数器还可能从 0 跳到 3?

要回答这个问题,得把人的“一次按压”和单片机看到的“电平变化”分开。机械动作很慢,触点刚碰到一起时却可能反复接通、断开。

本文继续采用上拉输入、按下为 LOW。动图是放慢的原理示意,非芯片仿真、非实测波形;跳变次数和时间均为教学设定,不代表所有按钮。

放大一次按压

想象两片有弹性的金属刚碰上。它们可能先接触,再弹开,又重新接触,最后才稳定贴在一起。这种短时间的接通与断开,叫机械触点抖动。

一次按压中的接触与弹开

动画只展示电气接通状态,不按真实比例还原按钮内部结构。注意:手还在进行同一次动作,线路已经发生了几次变化。松开按钮时,也可能出现类似抖动。

有上拉,断开时是 HIGH;接通时是 LOW。因此,输入信号可能先经历 HIGH、LOW、HIGH、LOW,才安定下来。上拉没有失效,它恰恰在每次断开时重新提供默认高电平。

只数下降沿,仍然可能多算

“每次读到 LOW 就加一”当然不对:长按期间,程序每循环一遍都可能加一。改成只在 HIGH 变成 LOW 时计数,就是在检测下降沿。

这个修改能避免把稳定长按重复计算,却仍可能把抖动里的多个下降沿算进去。

原始信号中的三个下降沿不等于三次人工按压

动图里橙色游标每走过一次下降沿,原始计数便加一。这是特意构造的三个下降沿,并不是说所有按钮都会恰好计数三次。真实结果还取决于触点、读取频率和程序时序。

所以需要两层处理:先得到可靠的稳定状态,再从这个稳定状态的变化提取按下事件。少一层,都可能出现看似相同、原因却不同的重复计数。

等它稳定一会儿再承认

一种容易理解的软件消抖方法是:原始读数只要变了,就重新计时;连续保持同一读数达到设定窗口,才把它接纳为新的稳定状态。

每次跳变重置等待窗口,稳定后只确认一次按下

图里使用 20ms 的窗口。它只是讲解参数,实际应按按钮特性和响应需求选取。窗口太短,抖动可能漏过去;窗口太长,响应变迟,短促的有效按压也可能被过滤掉。

观察进度条:它不是从手指第一次碰到按钮开始一路倒计时,而是遇到新的变化就归零。最后一个 LOW 保持足够久,稳定状态才从 HIGH 改为 LOW,此时生成一次按下事件。

下面是算法片段。lastRaw 与 stable 在初始未按下时设为 HIGH,changedAt、now 使用无符号毫秒计数;这不是完整可上传程序。

raw = digitalRead(2);
now = millis();
if (raw != lastRaw) changedAt = now;
if (now - changedAt >= 20 && raw != stable) {
stable = raw;
if (stable == LOW) count++;
}
lastRaw = raw;

这里没有让 CPU 原地等 20ms。程序每一轮检查一下条件,没有满足就去做别的事,下一轮再来。它要求循环足够频繁;期间如果很久不读取,就不能声称掌握了每一次电平变化。

一个常见误区

“在第一次按下后直接 delay(20),就等于完整消抖。”两者并不等价。上面的算法关心的是最后一次变化以后是否持续稳定,而且还处理了稳定松开的过程。简单延时在某些小例子中可用,但会阻塞这段主程序,还可能漏掉其他输入。

软件消抖也不会修复任何异常电路;这里讨论的是正常机械触点的短暂抖动。它与第 02 课的悬空问题必须分别理解。

看完再猜

按钮已经稳定按下,连续保持两秒,稳定状态一直是 LOW,计数还会继续增加吗?

本课算法不会。只有稳定状态从 HIGH 变为 LOW 才加一;稳定松开后,再稳定按下,才会产生下一次事件。下一课会接着解释:为什么同样的按钮,在灯闪烁时突然“不灵了”。

资料:Arduino Debounce、Arduino 边沿检测示例。官方 Debounce 示例使用按下为 HIGH 的接法,本文为衔接上一课,将有效电平改为 LOW。

发现更多好玩的开源项目

专注 Arduino、开源硬件、端侧 AI 和值得收藏的开发者工具。

课程插图

长按二维码关注「GitStar」

所有动画为教学原理示意,非芯片仿真、非硬件实测。网页发布:2026-09-21。