ESP32Cube Logo
Sign In
ESP32 Buttons in Arduino: Wiring, Debouncing, and Interrupts

ESP32 Buttons in Arduino: Wiring, Debouncing, and Interrupts

esp32cube
Aug 28, 2026
Tutorial
5 views

Wire a mechanical button to ESP32 safely, debounce it in software, and upgrade to interrupt-driven input without blocking your loop—using patterns that work on common Arduino-ESP32 core releases.

GPIOInterruptDebouncing

Buttons are the simplest human input, but mechanical contacts bounce: they chatter for milliseconds and can look like dozens of edges to fast software. This guide shows correct wiring, polling with debounce and edge detection, and a compact interrupt + flag pattern suitable for busy loop() workloads.

Versions: Examples target Arduino-ESP32 core 2.0.x APIs (attachInterrupt, pinMode, INPUT_PULLUP). If you use 3.x, verify attachInterrupt signature and GPIO restrictions in the release notes for your board.

Wiring: pull-ups, pull-downs, and known idle state

An input must never float; otherwise noise toggles the pin.

  • Internal pull-up (common): one terminal to GPIO, the other to GND. Idle reads HIGH; pressed reads LOW when using INPUT_PULLUP.
  • External pull-down: one terminal to 3.3 V, the other to GPIO, with a ~10 kΩ resistor from GPIO to GND. Idle reads LOW if wired accordingly.

GPIO selection tips

Avoid strapping pins where possible (GPIO 0, 2, 5, 12, 15 on many classic ESP32 modules), because their boot‑time level can alter flash / download behavior. GPIO 13, 14, 25, 26, 27 are commonly convenient for buttons on many devkits—still confirm your module pinout.

Polling with debounce (detects one press event)

This pattern records a stable state after 50 ms quiet time and prints once per transition to pressed.

/*
 * ESP32 button polling + debounce + edge detect
 * Arduino-ESP32 core 2.0.x
 */

const int BUTTON_PIN = 14;

int lastRawReading = HIGH;
int stableState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceMs = 50;

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  const int reading = digitalRead(BUTTON_PIN);

  if (reading != lastRawReading) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceMs) {
    if (reading != stableState) {
      stableState = reading;
      if (stableState == LOW) {
        Serial.println("Button pressed (edge)");
      }
    }
  }

  lastRawReading = reading;
}

Interrupts: keep ISRs tiny; debounce in the ISR with care

For low-latency wakeups, use an interrupt to set a flag, then handle serial / network work in loop().

Rules of thumb:

  • Mark ISR-shared data volatile.
  • Keep the ISR short (no delay, no Serial.print, minimal work).
  • On ESP32, avoid millis() inside ISRs; use micros() for simple time gaps.
/*
 * ESP32 button interrupt example (flag + micros debounce)
 * Arduino-ESP32 core 2.0.x
 */

const int BUTTON_PIN = 14;

volatile bool buttonPressed = false;
volatile uint32_t lastIsrUs = 0;
const uint32_t debounceUs = 200000;  // 200 ms (adjust to your switch)

void IRAM_ATTR handleButtonInterrupt() {
  const uint32_t now = micros();
  if (now - lastIsrUs > debounceUs) {
    buttonPressed = true;
    lastIsrUs = now;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonInterrupt, FALLING);
}

void loop() {
  if (buttonPressed) {
    buttonPressed = false;
    Serial.println("Interrupt: button press accepted");
  }
}

Choosing polling vs interrupts vs libraries

MethodComplexityCPU while idleTypical use
Polling + debounceLowHigher (continuous read)Learning, simple UI
Interrupt + flagMediumVery lowBusy loop, wake from light sleep
Library (e.g. OneButton)Low–mediumMediumMulti-click, long-press gestures

Best practices

  1. Always debounce mechanically—software (millis / micros), hardware (RC), or both.
  2. Prefer INPUT_PULLUP for fewer external parts when polarity fits your schematic.
  3. Keep ISRs to flags/counters; do I/O and state machines in loop().

References

  • Arduino attachInterrupt()
  • Espressif ESP32 technical reference (GPIO)

Share this article

Comments

0

Please sign in to post a comment.

No comments yet.

Related Articles

ESP32-C3 vs ESP32-S3 vs ESP32-C6: How to Choose for Real Firmware Projects

Tutorial·2151 views

ESP32 Arduino Button Input: Practical Guide and Debouncing

Tutorial·1087 views

ESP32 + WS2812B LED Strip + 18650 Battery: Rechargeable RGB Lighting Device

Tutorial·1724 views

ESP32 LED Chaser (Running Light) Experiment

Tutorial·734 views

Control GPIO Output: Light Up an LED on ESP32

Tutorial·964 views
View more in this category→
Copyright © 2026 ESP32Cube. All rights reserved.•1.0.1•Terms·Privacy
Source codeTwitterDiscord