A push button is the first input every project needs, and the ESP32 makes it easy: every output-capable pin has an internal pull-up, so the button goes straight from the GPIO to GND with no resistor.
The DevKit V1 has two traps. GPIO34–39 have no pull-ups, and the strapping pins (D2, D12, D15) change how the board boots if the button is held at reset. GPIO32 avoids both. The sketch debounces in software and reports each press as short or long, which is how one button can do two jobs.
Wiring
| ESP32 DevKit V1 | Push button (6 mm tactile switch) | Wire | Notes |
|---|---|---|---|
| D32 | 1 | yellow | Internal pull-up holds the pin HIGH; pressing pulls it LOW. |
| GND | 2 | black | Use diagonally opposite legs of a 4-leg tactile switch so you never pick two legs that are always joined. |
No supply — passive switch part, 3.3 V board. Wire the button between a GPIO and GND and use the internal pull-up. Never connect a button between a GPIO and 5 V on a 3.3 V board.
Board quirks
- No resistor needed:
INPUT_PULLUPturns on the ESP32’s internal pull-up (roughly 45 kΩ). Wire the button to GND, never to 5 V or VIN — a GPIO at 5 V is damaged. - D34, D35, VP and VN are input-only and have no internal pull-ups. A button there floats and reads random presses unless you add a 10 kΩ resistor to 3V3.
- Keep buttons off D2, D12 and D15: a button held down during reset changes the boot mode or flash voltage. D32 is a plain GPIO with no boot role.
- The board’s own BOOT button is GPIO0, which the 30-pin header doesn’t bring out. After the sketch starts you can still read it with
pinMode(0, INPUT_PULLUP)as a free second button. - D32 is also an RTC pin, so the same button can wake the ESP32 from deep sleep with
esp_sleep_enable_ext0_wakeup(GPIO_NUM_32, 0).
Code
// Push button on an ESP32 DevKit V1 with the internal pull-up and software debounce.
// Leg 1 → D32 (GPIO32), leg 2 → GND. Pressed = LOW. Short and long presses are told apart.
constexpr uint8_t BUTTON_PIN = 32;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t LONG_PRESS_MS = 800;
bool stableState = HIGH; // debounced level (HIGH = released)
bool lastReading = HIGH;
uint32_t lastChange = 0;
uint32_t pressedAt = 0;
uint32_t presses = 0;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP); // GPIO32 has an internal pull-up; GPIO34–39 do not
Serial.println("Button on GPIO32 — press it (hold for a long press)");
}
void loop() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) {
lastChange = millis();
lastReading = reading;
}
if (millis() - lastChange < DEBOUNCE_MS || reading == stableState) return;
stableState = reading;
if (stableState == LOW) {
pressedAt = millis();
} else {
uint32_t held = millis() - pressedAt;
presses++;
Serial.printf("Press #%lu — %s (%lu ms)\n", (unsigned long)presses,
held >= LONG_PRESS_MS ? "long" : "short", (unsigned long)held);
}
}// Push button on an ESP32 DevKit V1 with the internal pull-up and software debounce.
// Leg 1 → D32 (GPIO32), leg 2 → GND. Pressed = LOW. Short and long presses are told apart.
constexpr uint8_t BUTTON_PIN = 32;
constexpr uint32_t DEBOUNCE_MS = 30;
constexpr uint32_t LONG_PRESS_MS = 800;
bool stableState = HIGH; // debounced level (HIGH = released)
bool lastReading = HIGH;
uint32_t lastChange = 0;
uint32_t pressedAt = 0;
uint32_t presses = 0;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP); // GPIO32 has an internal pull-up; GPIO34–39 do not
Serial.println("Button on GPIO32 — press it (hold for a long press)");
}
void loop() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) {
lastChange = millis();
lastReading = reading;
}
if (millis() - lastChange < DEBOUNCE_MS || reading == stableState) return;
stableState = reading;
if (stableState == LOW) {
pressedAt = millis();
} else {
uint32_t held = millis() - pressedAt;
presses++;
Serial.printf("Press #%lu — %s (%lu ms)\n", (unsigned long)presses,
held >= LONG_PRESS_MS ? "long" : "short", (unsigned long)held);
}
}Expected serial output
Button on GPIO32 — press it (hold for a long press)
Press #1 — short (142 ms)
Press #2 — short (98 ms)
Press #3 — long (1260 ms)Button on GPIO32 — press it (hold for a long press) Press #1 — short (142 ms) Press #2 — short (98 ms) Press #3 — long (1260 ms)
Common errors
One press counts two or three times
Contact bounce. The sketch ignores changes shorter than 30 ms; raise DEBOUNCE_MS to 50 for worn or cheap switches.
Presses appear on their own
The pin is floating: either the button is on D34–D39 (no internal pull-up — add 10 kΩ to 3V3) or a long unshielded wire picks up noise. Add a 100 nF capacitor from the pin to GND on long runs.
Reads LOW all the time
You used two legs that are joined inside the switch. Rotate it 90° on the breadboard or use diagonal legs.