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_STATE | 0b00000001 | 已确认的稳定状态(对外只暴露这个) |
UNSTABLE_STATE | 0b00000010 | 引脚上一次读到的原始电平(幕后跟踪用) |
CHANGED_STATE | 0b00000100 | 本次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);}这种"位打包"设计的好处:所有读写都是无锁的单字节操作,在裸机环境下零开销、零竞争,还方便子类(Bounce、Button)继承扩展。
三、update():状态机的心跳 ⏱️
去抖的本质是:原始信号必须连续保持超过interval毫秒,才允许改变稳定状态。Bounce2 用UNSTABLE_STATE+previous_millis计时器实现了这个判定,核心逻辑在 Bounce2.cpp 的 update()(默认策略):
- 读取引脚当前电平
currentState; - 若与
UNSTABLE_STATE不一致 →翻转跟踪位并重置计时器(抖动中,重新等满 5ms); - 若一致且计时 ≥
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);- 记录
durationOfPreviousState和stateChangeLastTime→ 支撑按键时长统计(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。
六、新手使用清单 ✅
- 每次
loop()只调用一次update(),且放在循环最前面; - 去抖间隔推荐
interval(5)(5ms),绝大多数微动开关足够; - 用
INPUT_PULLUP内嵌上拉,按钮接 GND,setPressedState(LOW); - 用
pressed()/released()写"事件",用changed()+read()写"电平",不要混用; - 需要短按/长按时,组合
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),仅供参考