Arduino Hardware Interrupts: Skip the Polling Loop, Handle Events Instantly
Hardware interrupts let Arduino respond to external events instantly—no polling required. Learn how to use attachInterrupt() safely and avoid common pitfalls that crash your sketches.
23 Aug 2025, 01:49 UTC

The Polling Problem That Slows Down Your Arduino
When you're building a responsive Arduino project, you've probably written code that constantly checks a pin in your main loop. This polling approach works for simple projects, but it burns CPU cycles and can miss fast events. What if your button gets pressed while the chip is busy doing something else? The press vanishes into the ether.
Hardware interrupts solve this by letting the microcontroller pause whatever it's doing and jump to your code the instant a pin changes. No missed events. No wasted cycles. Just immediate response.
How Arduino Maps Pins to Interrupt Handlers
The attachInterrupt() function connects a digital pin to an Interrupt Service Routine (ISR). The function takes three arguments: the pin number, the function to call, and the signal type (RISING, FALLING, CHANGE, or LOW).
On an Arduino Uno, only pins 2 and 3 support external interrupts. This limitation matters when designing your circuit. Here's the basic syntax:
attachInterrupt(digitalPinToInterrupt(pinNumber), myISR, mode);
Using digitalPinToInterrupt() is safer than hardcoding numbers—it returns NOT_AN_INTERRUPT for unsupported pins, letting you catch errors at runtime.
Write ISRs That Won't Crash Your Sketch
Your ISR must be short and simple. Avoid delay(), Serial.print(), analogWrite(), and any function that relies on interrupts itself. These can cause deadlocks or unpredictable behavior because interrupts are disabled during ISR execution.
Share variables between your ISR and main code using the volatile keyword. This tells the compiler to always read from RAM instead of optimizing repeated accesses into registers.
volatile bool buttonPressed = false;
void buttonISR() {
buttonPressed = true;
}
void loop() {
if (buttonPressed) {
// Handle the button press
buttonPressed = false;
}
}
Worked Example: Reliable Button Debouncing Without Delay
Here's a complete sketch that detects button presses with hardware interrupts and debounces without blocking the main loop:
const byte buttonPin = 2;
volatile unsigned long lastInterruptTime = 0;
volatile bool buttonEvent = false;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(buttonPin), buttonISR, FALLING);
Serial.begin(9600);
}
void buttonISR() {
unsigned long currentTime = millis();
if (currentTime - lastInterruptTime > 50) {
buttonEvent = true;
}
lastInterruptTime = currentTime;
}
void loop() {
if (buttonEvent) {
buttonEvent = false;
Serial.println("Button pressed - debounced");
}
// Other non-blocking code runs here
}
Note: This example uses millis() inside an ISR, which works on most Arduino boards because millis() only reads a timer register without enabling interrupts. However, this behavior isn't guaranteed on all platforms—test carefully.
Trade-Offs: When Interrupts Aren't the Answer
Interrupts introduce complexity. Debugging becomes harder because execution jumps unpredictably. An interrupt storm—rapid, repeated triggers—can starve your main loop, causing missed events elsewhere or system instability.
For slow, predictable events, simple polling might be clearer and more maintainable. Use interrupts when you need guaranteed response time for asynchronous events like encoder movement, pulse measurement, or emergency stop signals.
Actionable Checklist Before You Deploy
- Verify pin support: Check your board's documentation. Uno supports pins 2 and 3; Mega adds more options.
- Mark shared variables: Always use
volatilefor any variable accessed in both ISR and main code. - Keep ISRs minimal: Set flags or copy data, then return. Do heavy processing in the main loop.
- Test with a scope: Measure actual interrupt latency to confirm your system responds fast enough.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.