Source: https://mokxi.com/learn/esp32-interrupts
Updated: 2026-10-25

Learn

# ESP32 interrupts with attachInterrupt

Written by the Mokxi team, updated October 25, 2026

Start building free

Or see every lesson.

- 455 parts on the bench
- 43 boards running now
- 1.00x real time, on every board

Hold the button

The button pulls GPIO 15 to ground, so a press is a FALLING edge.

An interrupt lets an ESP32 notice a pin change the moment it happens, whatever loop() is busy with. You write a short function, mark it IRAM_ATTR, and hand it to attachInterrupt() with the pin and the edge to watch. Every GPIO on an ESP32 can trigger one. The two things that go wrong are the button, which bounces, and the variable the handler shares with loop(), which has to be volatile.

The circuit above is an ESP32 DevKit with a button from GPIO 15 to ground. Below, a counter on that pin pressed three times, with the button’s bounce switched off, then at the typical setting, then at its worst, and the debounce that brought all three back to 3.

## attachInterrupt, IRAM_ATTR and volatile

pinMode(15, INPUT_PULLUP) holds the pin high until the button pulls it low, and attachInterrupt(digitalPinToInterrupt(15), onPress, FALLING) runs onPress() on each high-to-low edge. The handler only adds one to a counter; loop() does the printing. On an ESP32, digitalPinToInterrupt(15) is just 15, but writing it keeps the sketch portable to an Uno.

IRAM_ATTR puts the handler in internal RAM. On a real ESP32 most code runs from flash through a cache, and the cache is switched off while the flash is being written, for example by Preferences or WiFi. A handler left in flash that fires then crashes the board. In Mokxi all code already runs from RAM, so the mark changes nothing here, but write it every time.

volatile tells the compiler that the variable can change behind its back. A loop that waited with while (presses == 0) { } on a plain int never ended here, through three presses: the compiler read the variable once and kept the copy. With volatile int presses the same loop printed "pressed" at the first press.

Counting presses with an interrupt

## Why one press counts more than once

A push button does not close once. Its contacts bounce for a few milliseconds when they meet and again when they part, and an interrupt is fast enough to catch every bounce. Mokxi’s push button bounces the way a real one does, with a setting for how badly. Pressed three times, the sketch above counted 3 with bounce off, 14 at the typical setting and 46 at the worst.

The usual fix in tutorials is a time check: ignore a FALLING edge within 50 milliseconds of the last one counted. It counted 6 for 3 presses, at both settings. The press is counted once, but the release bounces too, and some of its edges fall. They come 200 milliseconds after the press, long after the 50 are up.

## A debounce that counts one per press

The handler below watches both edges with CHANGE and reads the pin each time. When it reads HIGH it notes the time; when it reads LOW it counts a press only if the pin had been high for more than 30 milliseconds. A bounce during the press or the release never stays high that long, and a real press always follows a long high. It counted 3 for 3 presses with bounce off, typical and worst.

Keep the handler this short. No Serial.print(), no delay() and nothing that waits: on a real ESP32 a handler that runs too long trips the interrupt watchdog and the board resets. Set a flag or a count, and let loop() do the slow part. detachInterrupt(15) stops watching the pin; a sketch that called it once the count reached 2 stayed at 2 through the third press.

A debounced press counter

## ESP32 interrupt pins, and the ones that need a resistor

Every GPIO can interrupt, but a pin with nothing holding it high or low floats, and a floating pin interrupts on noise. With pinMode(15, INPUT) instead of INPUT_PULLUP and the button released, the pin read 0 and the counter reached 141 in two seconds, with three real presses among them.

GPIO 34 to 39 are input only and have no internal pull-up at all, so INPUT_PULLUP on them does nothing. The same button on GPIO 34 with INPUT_PULLUP counted 142. With a 10 k resistor from GPIO 34 to 3.3 V the pin read 1 at rest and counted the three presses, 3. Use those pins for interrupts only with a resistor, or with a sensor that drives its output both ways.

For several buttons with one handler, attachInterruptArg(pin, handler, &data, FALLING) passes a pointer to the handler; with a struct for the button it counted 3 for 3 presses. Avoid GPIO 6 to 11, which are the flash, and the strapping pins 0, 2, 5, 12 and 15 for anything held low at power on. GPIO 15 is fine for a button that is open at boot.

## Questions

Which ESP32 pins can be used for interrupts?

Every GPIO. GPIO 34 to 39 have no internal pull-up, so they need a resistor or a sensor that drives the pin, and GPIO 6 to 11 are the flash.

Why is my ESP32 attachInterrupt not working?

Most often the pin floats (no INPUT_PULLUP or resistor), the shared variable is not volatile, the pin is 34 to 39 without a pull-up, or the handler prints or delays.

What does IRAM_ATTR do on an ESP32?

It puts the handler in internal RAM, so it can run while the flash cache is off during a flash write. Without it a handler that fires at that moment crashes the board.

How do I debounce a button interrupt on an ESP32?

Watch CHANGE, note when the pin goes high, and count a low only after it has been high for about 30 ms. A plain 50 ms gap after FALLING still counts the release bounce.

Can I use Serial.print in an ESP32 interrupt?

No. Printing waits for the UART, and a slow handler can trip the interrupt watchdog. Set a variable in the handler and print it from loop().

Related

## Keep going

Arduino Interrupts: attachInterrupt on Pin 2 and 3 Interrupts vs Polling on Arduino, Side by Side Arduino Button Debounce: See the Bounce, Then Fix It Debouncing a Button: Why It Fires More Than Once ESP32 DevKit V1 Pinout: Every Pin and Which to Avoid ESP32 PWM: ledcAttach, ledcWrite and Frequency The ESP32 DevKit in Mokxi: what is modeled The ESP32 simulator page

Sources

## Where the facts on this page come from

- Arduino ESP32 core: the GPIO API, attachInterrupt (checked 2026-10-25)
- ESP-IDF: memory types, IRAM and interrupt handlers
- ESP32 datasheet: GPIO 34 to 39 are input only

## Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.

Start building free Open the editor
