Interrupts are a powerful feature in embedded systems that allow your microcontroller to react to events immediately without continuously checking for them in the main loop. In this post, you’ll learn how to use ESP32 interrupts with the Arduino IDE, understand how they work, and how to implement them in your own projects!
What Are Interrupts?
An interrupt is a mechanism that temporarily halts the normal execution of your program to execute a special function called an Interrupt Service Routine (ISR). Once the ISR is done, the main program resumes from where it was interrupted.
Think of it like this: You’re reading a book (main code), and someone rings the doorbell (interrupt). You pause reading the book, answer the door (ISR), and return to reading afterwards.
Types of Interrupts on the ESP32
The ESP32 supports several types of interrupts triggered by different sources.
External Interrupts
External interrupts are triggered by changes on GPIO pins. These changes can be electrical transitions such as a rising edge (from LOW to HIGH), a falling edge (from HIGH to LOW), or any change in the digital signals. Usually, external interrupts are used to detect inputs from buttons, sensors, or other digital signals.
Timer Interrupts
This type of interrupt is triggered after a specific time using internal timers of the ESP32, similar to an alarm clock.
Internal/Peripheral Interrupts
Triggered by peripherals like UART, ADC, I2C, and SPI, these interrupts allow you to respond to events that occur within internal modules of the ESP32. For example, a UART peripheral can generate an interrupt when new data arrives in its receive buffer, allowing your code to read the data immediately instead of periodically checking for it.
Software Interrupts
As the name suggests, software interrupts are triggered by your code. For instance, you can invoke an ISR function manually by setting flags or calling functions within your sketch. This is useful for simulating an interrupt condition without actual hardware events.
How ESP32 Interrupts Work
When a hardware event occurs, such as a button press or a signal change, it can trigger an interrupt on the ESP32. Once triggered, the microcontroller pauses its current execution and immediately jumps to a predefined function known as an Interrupt Service Routine (ISR).
The ESP32 allows you to define how the interrupt is triggered using different conditions:
- RISING: The signal transitions from LOW to HIGH, useful for detecting the beginning of a pulse.
- FALLING: The signal transitions from HIGH to LOW, often used for detecting the release of a button.
- CHANGE: Any change in signal level, either from LOW to HIGH or HIGH to LOW.
- HIGH or LOW: Triggers as long as the pin stays in the state.
ESP32 Interrupt Pins
Luckily, all GPIO pins of the ESP32 can be used for interrupts.

However, pins with special functions, like GPIO 2, which is connected to the built-in LED on most development boards, should be avoided.
How to Implement ESP32 Interrupts in Arduino IDE
Interrupts on the ESP32 are easy to set up in the Arduino IDE. Basically, we just need to define an ISR function, which runs when an interrupt is triggered, and attach the interrupt pin to that ISR.
In the following, I will show you how to handle interrupts with a button press.
Define an Interrupt Service Routine Function
Whenever the button is pressed, we want to call an ISR called handleButtonInterrupt() that sets a flag that the button is pressed.
volatile bool buttonPressed = false; // flag
void IRAM_ATTR handleButtonInterrupt() { // Interrupt Service Routine (ISR)
buttonPressed = true;
}
The keyword volatile tells the compiler that the value of buttonPressed can change unexpectedly and shouldn’t be “optimized away”.
IRAM_ATTR from the ISR function ensures that it is stored in the ESP32’s instruction memory for faster access, as ISRs should run as quickly as possible.
Attach an Interrupt to a GPIO pin
Next, we want to define the GPIO pin to use for the button interrupt in setup(). In this example, the button is hooked up to GPIO 4.
setup() {
pinMode(4, INPUT_PULLUP); // set button pin as input
attachInterrupt(digitalPinToInterrupt(4), handleButtonInterrupt, FALLING) // attach button to ISR
// ... your other setup code ...
}
Detach an Interrupt
In case you don’t need an interrupt anymore, you can detach with detachInterrupt() as follows:
detachInterrupt(digitalPinToInterrupt(4));
Complete Sketch
Here’s what a complete example sketch using interrupts would look like:
volatile bool buttonPressed = false;
void IRAM_ATTR handleButtonInterrupt() {
buttonPressed = true;
}
void setup() {
Serial.begin(115200);
pinMode(4, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(4), handleButtonInterrupt, FALLING);
}
void loop() {
if (buttonPressed) {
Serial.println("Button Pressed!");
buttonPressed = false;
}
}
Debouncing Interrupts
Cheap mechanical buttons can cause multiple rapid triggers. This is called bouncing, and you should know how to deal with it to avoid unintentional triggers.
To debounce a button in software, we track timestamps to only register button presses after a specific delay. This effectively prevents multiple triggers from one single button press.
volatile unsigned long lastInterruptTime = 0;
const unsigned long debounceDelay = 200;
void IRAM_ATTR handleInterrupt() {
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime > debounceDelay) {
buttonPressed = true;
lastInterruptTime = currentTime;
}
}
Best Practices for ESP32 Interrupts
Even though interrupts are rather simple, there are some best practices you should follow to make the most of them.
Keep your ISR functions short and fast. ISRs are time-critical and shouldn’t include blocking operations or functions that rely on system resources that are available immediately. Hence, avoid using delay(), Serial functions, or malloc/free inside ISRs.
Since ISRs must not perform heavy tasks, set a flag (e.g., a boolean variable) to signal the main loop to handle the actual processing. This helps with responsiveness and efficiency.
Additionally, disable interrupts with detachInterrupt() after the interrupt is no longer used. This can also help prevent unwanted triggers.
Conclusion
Interrupts allow your ESP32 to react immediately to events like button presses, sensor triggers, or timed actions. By using interrupts, you can build more efficient and responsive projects.
For example, you can build an efficient ESP32 motion detection system using interrupts!
What do you use interrupts for? Share your experiences in the comments below!
Thanks for reading!
Links marked with an asterisk (*) are affiliate links which means we may receive a commission for purchases made through these links at no extra cost to you. Read more on our Affiliate Disclosure Page.