Bounce2源码深度剖析:3个位标志如何构建一个完美的去抖状态机
2026/8/23 13:01:56 网站建设 项目流程

Bounce2源码深度剖析:3个位标志如何构建一个完美的去抖状态机

【免费下载链接】Bounce2Debouncing library for Arduino and Wiring项目地址: https://gitcode.com/gh_mirrors/bo/Bounce2

Bounce2是一款专为 Arduino 设计的按钮去抖库(Debouncing library for Arduino and Wiring)。如果你写固件时遇到过"按一下按钮 LED 却闪了好几次"的诡异现象,问题就出在机械开关的接触抖动上。本文带你深入 src/ 目录,用最少的代码量,讲透 Bounce2 如何用1 个字节里的 3 个位标志,构建出一个稳定、可靠、可扩展的去抖状态机。


一、为什么按钮需要去抖?

机械按钮在闭合/断开的瞬间,金属触点会高速弹跳,在几毫秒内产生十几个高低电平翻转

⚡ 你以为按了 1 下,单片机却"看到"了 10 次按下 + 10 次松开。

delay()延时是新手常用方案,但它会阻塞整个程序;硬件 RC 滤波又增加成本。软件去抖才是 Arduino 项目的主流解法,而 Bounce2 的实现堪称教科书级别——核心逻辑不到 100 行。


二、3 个位标志:整个状态机的灵魂 🎯

Bounce2 没有用 bool 变量散落各处,而是把全部状态压缩进Debouncer类里的一个 uint8_t 成员变量state,其中定义了 3 个标志位(见 Bounce2.h):

位标志含义
DEBOUNCED_STATE0b00000001已确认的稳定状态(对外只暴露这个)
UNSTABLE_STATE0b00000010引脚上一次读到的原始电平(幕后跟踪用)
CHANGED_STATE0b00000100本次update()中稳定状态发生了变化
// Bounce2.h —— 全部状态只占 1 个字节 static const uint8_t DEBOUNCED_STATE = 0b00000001; static const uint8_t UNSTABLE_STATE = 0b00000010; static const uint8_t CHANGED_STATE = 0b00000100;

配合 4 个内联工具函数(Bounce2.h)完成置位、清位、翻转、查询:

inline void setStateFlag(const uint8_t flag) {state |= flag;} inline void unsetStateFlag(const uint8_t flag) {state &= ~flag;} inline void toggleStateFlag(const uint8_t flag) {state ^= flag;} inline bool getStateFlag(const uint8_t flag) const {return((state & flag) != 0);}

这种"位打包"设计的好处:所有读写都是无锁的单字节操作,在裸机环境下零开销、零竞争,还方便子类(BounceButton)继承扩展。


三、update():状态机的心跳 ⏱️

去抖的本质是:原始信号必须连续保持超过interval毫秒,才允许改变稳定状态。Bounce2 用UNSTABLE_STATE+previous_millis计时器实现了这个判定,核心逻辑在 Bounce2.cpp 的 update()(默认策略):

  1. 读取引脚当前电平currentState
  2. 若与UNSTABLE_STATE不一致 →翻转跟踪位并重置计时器(抖动中,重新等满 5ms);
  3. 若一致且计时 ≥interval→ 信号稳定,若与DEBOUNCED_STATE不同,则调用changeState()翻转稳定状态。

也就是说,抖动期间计时器被反复清零,DEBOUNCED_STATE纹丝不动——这就是去抖。

💡 Bounce2 还通过编译期宏提供了两种可选策略(见 Bounce2.h):

  • 默认 Stable 模式:稳定后延迟interval才响应,最保守;
  • LOCK-OUT 锁存模式(Bounce2.cpp):状态切换后锁定interval时长,期间彻底忽略输入,适合点动按钮;
  • PROMPT_DETECTION 快速响应模式(Bounce2.cpp):距上次切换超过interval立即响应,兼顾速度与防抖。

一行#define即可切换三种行为,是典型的"一套状态机、多种策略"设计。


四、changeState():一次翻转,三件事同时做

真正"放行"一次状态变化时,changeState() 只做了三件事:

  • toggleStateFlag(DEBOUNCED_STATE)→ 翻转稳定状态;
  • setStateFlag(CHANGED_STATE)→ 打上"已变化"标签,供changed()查询(Bounce2.h);
  • 记录durationOfPreviousStatestateChangeLastTime→ 支撑按键时长统计(currentDuration() / previousDuration()),这是做"短按/长按"判断的利器。

外部读取接口同样优雅(Bounce2.cpp):

接口判定逻辑
read()只读DEBOUNCED_STATE永远拿不到抖动值
rose()DEBOUNCED为高CHANGED置位
fell()DEBOUNCED为低CHANGED置位

rose/fell天然是一次性事件:下一次update()开头就会清掉CHANGED_STATE(Bounce2.cpp),杜绝"重复触发"。


五、三层类继承:从硬件到语义 🧱

Bounce2 的类结构是一条清晰的继承链(Bounce2.h):

Debouncer(纯算法层)→Bounce(绑定引脚)→Bounce2::Button(绑定"按压"语义)

  • Debouncer通过虚函数readCurrentState()与硬件解耦(Bounce2.h),意味着去抖算法本身可以脱离 Arduino 单独测试、复用
  • Bounce的 readCurrentState() 只是返回digitalRead(pin),把算法接回真实引脚;
  • Button最贴心:用 setPressedState() 声明"按下 = HIGH 还是 LOW",并提供isPressed() / pressed() / released()这类人类语义接口,新手再也不用自己判断电平方向。

想直接上手?看示例 bounce_basic.ino 和 bounce_button.ino;需要长按/点按时长判断,参考 bounce_current_duration.ino。


六、新手使用清单 ✅

  1. 每次loop()只调用一次update(),且放在循环最前面;
  2. 去抖间隔推荐interval(5)(5ms),绝大多数微动开关足够;
  3. INPUT_PULLUP内嵌上拉,按钮接 GND,setPressedState(LOW)
  4. pressed()/released()写"事件",用changed()+read()写"电平",不要混用;
  5. 需要短按/长按时,组合pressed()currentDuration(),比手写millis()更省心。

七、总结

回看源码你会发现,Bounce2 的"完美"来自三个朴素决策:

  • 🔢1 个字节装下全部状态:3 个位标志 + 位运算,小、快、清晰;
  • ⏱️计时器重置法去抖:抖动即清零,稳定才放行,逻辑只需十几行;
  • 🧱虚函数解耦硬件:算法、引脚、语义三层继承,各得其所。

读透 src/Bounce2.cpp 这不到 150 行的代码,你收获的不只是一个去抖库,更是一套如何在资源受限的单片机上设计状态机的完整思路。

【免费下载链接】Bounce2Debouncing library for Arduino and Wiring项目地址: https://gitcode.com/gh_mirrors/bo/Bounce2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询