π Button Debouncing on ESP32 with Arduino
π§ Part 1: What Is Button Debouncing?
When you press a button, the contacts inside donβt make a perfect connection immediately. Instead, they physically bounce for a few milliseconds. This causes the signal to fluctuate rapidly between HIGH and LOW β even though you pressed the button just once.
𧨠The Problem:
Without debounce logic, your ESP32 may detect multiple button presses for one actual press.
π Part 2: What Does Bounce Look Like?

The microcontroller sees this and thinks:
βThe button was pressed and released multiple times!β
π‘ Part 3: How Debouncing Fixes It
Absolutely! Letβs break down the debounce logic step by step in a way that makes it super easy to understand:
π§ Why Do We Need Debouncing?
When you press a button, it doesnβt make a clean contact right away. Instead, the metal inside the button might vibrate or βbounceβ, causing the signal to flicker between HIGH and LOW rapidly for a few milliseconds.
To us, thatβs just a single press.
To the ESP32? It looks like this:
LOW β HIGH β LOW β HIGH β LOW β (finally stable)
If we donβt filter that out, the ESP32 might think we pressed the button 5 times instead of once.
π So Whatβs the Debounce Logic?
We use a simple idea:
βOnly accept a button press if the signal stays the same for a short amount of time (e.g., 50 milliseconds).β
π§© Step-by-Step Logic:
-
π Read the buttonβs current state using
digitalRead(). - π§ Compare it to the previous reading (
lastButtonState):- If itβs different, it might be a real press β or just a bounce.
- So we reset a timer (
lastDebounceTime) to note when the change was first seen.
-
β± Wait a short time (e.g. 50 ms) to see if the new state stays the same.
-
β If the state is still the same after the delay, accept it as a real change.
- π Finally, if the new (debounced) state is
LOW(button pressed), perform the action, like printing to serial or toggling an LED.
π Part 4: Wiring the Button
Same as GPIO Digital Input: Using a Button with ESP32
π§© Option A: Using an Extension Board (Recommended for Beginners)
If youβre using an ESP32 extension board, good news! π
- You donβt need to worry about pull-up resistors.
- Just use female-to-male jumper wire and connect corrdingly.
| LED Board Pin | ESP32 Pin | |βββββ|ββββββ| | GND | GND | | 5V | 5V | | LED1 | GPIO2 (D2) | | SW1 | GPIO14 |
- The board handles resistor setup internally, making things safer and more reliable.
π§ͺ Option B: Breadboard Wiring (Manual Setup)
| Component | ESP32 Pin |
|---|---|
| One side of button | GND (ground) |
| Other side (diagonally) | GPIO14 |
| Pull-up resistor (10kΞ©) | Between GPIO14 and 3.3V |
| LED1 | GPIO12 (D12) |
π§ Or, skip the external resistor by using INPUT_PULLUP mode in your code.
Note: be careful with the direction of LED and remeber to connect a resistor in series with the LED
- Long leg = positive (anode)
- Short leg = negative (cathode)
Basic Diagram (Text version):
3.3V βββ
β
[10kΞ©]
β
GPIO14 ββ΄ββββ Button βββββ GND
π§Ύ Part 5: Arduino Code β Debounced Button Toggle
Hereβs the full code that:
- Debounces the button
- Toggles the LED on each press
- Prints the result over UART
const int buttonPin = 14; // GPIO connected to button
const int ledPin = 2; // LED pin (built-in or external)
int buttonState = HIGH; // Current debounced state
int lastButtonState = HIGH; // Last raw state
unsigned long lastDebounceTime = 0; // Timestamp of last change
const unsigned long debounceDelay = 50; // Delay in ms
bool ledOn = false; // LED state
void setup() {
Serial.begin(115200); // Start Serial Monitor
pinMode(buttonPin, INPUT_PULLUP); // Internal pull-up: HIGH when not pressed
pinMode(ledPin, OUTPUT); // LED output
}
void loop() {
int reading = digitalRead(buttonPin); // Raw state
// If state changed, reset timer
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
// If stable for debounceDelay, accept new state
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
// Only toggle on button press (LOW = pressed due to pull-up)
if (buttonState == LOW) {
ledOn = !ledOn;
digitalWrite(ledPin, ledOn ? HIGH : LOW);
Serial.println(ledOn ? "LED ON" : "LED OFF");
}
}
}
lastButtonState = reading; // Save reading for next loop
}
β οΈ A Note on delay()-Based Debouncing: Not Ideal for Complex Systems
In our current example, we used a simple delay-based software debounce using delay() or millis(). This is great for beginners and small projects, but in more complex systems, this approach is not recommended.
Why?
In complex and multitasking microcontroller systems, using simple looping delays for debounce can waste valuable CPU time and reduce system responsiveness and real-time performance.
In other words, the CPU is stuck waiting during that 50ms delay β doing nothing else β which is a huge problem if your system needs to handle multiple tasks or respond quickly to other events.
β Better Approaches for Debouncing
- Timer Interrupts
- Set up a hardware timer that triggers an interrupt at regular intervals.
- Use the interrupt to check and update button states.
- Keeps your main loop free for other tasks.
- Non-blocking millis()-based Debounce
- Instead of
delay(), usemillis()to track time and debounce without halting the loop. - More efficient and allows multitasking within the main loop.
- Instead of
π§ Comparison Table
| Method | Blocking? | Real-Time Friendly? | Difficulty | Recommended For |
|---|---|---|---|---|
delay() Debounce |
β Yes | β No | β Easy | Beginners, simple projects |
millis() Debounce |
β No | β Yes | ββ Medium | Projects with multitasking |
| Timer Interrupt Debounce | β No | β β Very Good | βββ Advanced | High-performance applications |