A robust 16-bit pattern-based button debouncing library for ESP32 microcontrollers using the Arduino framework.
- 16-bit pattern recognition for superior noise immunity
- Non-blocking operation - works with polling or hardware timer interrupts
- Advanced press pattern detection:
- Single press detection
- Double press detection with configurable time window
- Long press detection with configurable threshold
- Optional callback system for event-driven programming
- Multiple button support with efficient resource management
- Configurable timing parameters for different use cases
- Download the library as a ZIP file
- In Arduino IDE, go to Sketch → Include Library → Add .ZIP Library
- Select the downloaded ZIP file
- Restart Arduino IDE
- Copy the
debouncefolder to your project'slibdirectory - The library will be automatically detected and included
VCC ----o o---- GPIO_PIN
↑
Button
GPIO_PIN ----[10kΩ]---- GND
Pull-down
GPIO_PIN ----o o---- GND
↑
Button
VCC ----[10kΩ]---- GPIO_PIN
Pull-up
Or use internal pull-up:
// Library automatically configures INPUT_PULLUP for active LOW
Debounce button(PIN_BUTTON, LOW);#include <Debounce16.h>
const uint8_t PIN_BUTTON = 17;
const uint8_t PIN_LED = 15;
Debounce button(PIN_BUTTON, HIGH); // Active HIGH
void setup() {
pinMode(PIN_LED, OUTPUT);
}
void loop() {
static unsigned long lastUpdate = 0;
// Update button state every 1ms
if (millis() - lastUpdate >= 1) {
lastUpdate = millis();
button.update();
}
// Check for button press
if (button.isPressed()) {
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
}
}#include <Debounce16.h>
const uint8_t PIN_BUTTON = 17;
Debounce button(PIN_BUTTON, HIGH);
hw_timer_t *timer = nullptr;
void IRAM_ATTR onTimer() {
button.update(); // Update at precise 1ms intervals
}
void setup() {
// Configure timer for 1ms interrupts (requires ESP32 Arduino core >= 3.0.0)
timer = timerBegin(1000000); // 1 MHz timer clock
timerAttachInterrupt(timer, &onTimer); // Attach ISR
timerAlarm(timer, 1000, true, 0); // 1000 us = 1ms, auto-reload
}
void loop() {
if (button.isPressed()) {
// Handle button press
}
}#include <Debounce16.h>
Debounce button(17, HIGH);
void setup() {
button.enableDoublePressDetection(true);
button.setDoublePressWindow(300); // 300ms window
}
void loop() {
button.update();
if (button.isDoublePressed()) {
// Handle double press
}
// Prefer the dedicated query methods for correct consume-once behavior
if (button.isSinglePressed()) {
// Single press confirmed (window expired with one tap)
}
if (button.isDoublePressed()) {
// Double press confirmed
}
}#include <Debounce16.h>
Debounce button(17, HIGH);
void setup() {
button.enableLongPressDetection(true);
button.setLongPressThreshold(1000); // 1 second
}
void loop() {
button.update();
if (button.isLongPressed()) {
// Button is being held down
}
}#include <Debounce16.h>
Debounce button(17, HIGH);
void onButtonPress() {
Serial.println("Button pressed!");
}
void onButtonRelease() {
Serial.println("Button released!");
}
void setup() {
Serial.begin(115200);
button.onPress(onButtonPress);
button.onRelease(onButtonRelease);
// IMPORTANT: onPress and onRelease callbacks only fire when isPressed() or
// isReleased() is called explicitly from loop(). Without those calls,
// the callbacks will never execute.
// onDoublePress, onLongPressStart, and onLongPressEnd fire automatically
// from update() without any polling required.
}
void loop() {
button.update();
button.isPressed(); // Required to fire onPress callback
button.isReleased(); // Required to fire onRelease callback
}Debounce(uint8_t pin, bool activeLevel = HIGH)pin: GPIO pin number where button is connectedactiveLevel: Logic level when button is pressed (HIGH or LOW)
void update() // Update button state (call every 1ms)
bool isPressed() // Returns true on press event
bool isReleased() // Returns true on release event
bool isDown() // Returns true if button is held down
bool isUp() // Returns true if button is releasedvoid enableDoublePressDetection(bool enable = true)
void setDoublePressWindow(uint16_t windowMs)
void enableLongPressDetection(bool enable = true)
void setLongPressThreshold(uint16_t thresholdMs)bool isSinglePressed() // Returns true once per confirmed single tap (consume-once)
bool isDoublePressed() // Returns true on double-press event
bool isLongPressed() // Returns true when long-press active
uint8_t getClickCount() // Returns current click count (deprecated)void onPress(void (*callback)())
void onRelease(void (*callback)())
void onDoublePress(void (*callback)())
void onLongPressStart(void (*callback)())
void onLongPressEnd(void (*callback)())The library uses a 16-bit shift register to store the last 16 button state readings. When update() is called (every 1ms), the register shifts left and adds the current button state.
A press is detected when the last 6 bits are all HIGH (button pressed) after being LOW:
0b0000000000111111 = 0x003F
This means the button must be consistently pressed for at least 6ms before being recognized.
A release is detected by masking historyButton against MASK_RELEASE and comparing to PATTERN_RELEASE:
MASK_RELEASE = 0b1111110000111111 = 0xFC3F
PATTERN_RELEASE = 0b1111110000000000 = 0xFC00
Bits 15-10 must be HIGH (6 consecutive pressed readings confirming a prior press). Bits 5-0 must be LOW (6 consecutive released readings). Bits 9-6 are masked as don't-care to handle the transition bits that occur between the press and release windows. This is stricter than a simple level check and provides excellent noise immunity.
This library is based on:
- Jack Ganssle's "A Guide to Debouncing"
- Elliot Williams' "Ultimate Debouncer"
- Memory: ~44 bytes per button (with all features enabled)
- CPU: ~0.04% per button @ 240MHz (polling at 1ms intervals)
- Response Time: ~6ms from physical press to detection
This library is licensed under the MIT License. See the LICENSE file for details.
Copyright (c) 2025 Brooks
Contributions are welcome! Please feel free to submit pull requests or open issues.
- Based on research by Jack Ganssle
- Inspired by Elliot Williams' "Ultimate Debouncer"
- Implemented for ESP32 Arduino framework