Variables shared with an interrupt handler must be declared volatile. Without it the compiler may keep an old copy.
What the message means
The compiler assumes the loop is the only code changing a variable. It can then skip reading memory again, so the new value is never seen.
Fix it in this order
- Declare it as volatile, for example volatile bool flag = false;.
- Keep the interrupt handler short: set a flag or add to a counter, nothing more.
- Do not use delay(), Serial.print or long code inside the handler.
- For values larger than one byte, copy them inside noInterrupts() and interrupts().
- Use the pin numbers and interrupt-capable pins of your board.
Why it happens
A variable changed inside an interrupt handler and read in loop() must be declared volatile, or the compiler may keep an old copy and never see the change. A variable wider than one byte can also change halfway through a read.
Declare it volatile and copy it with interrupts off.
volatile unsigned long count = 0;
unsigned long readCount() {
noInterrupts();
unsigned long c = count;
interrupts();
return c;
}
Example sketch: change pins and values for your own wiring. It has not been bench-tested on your hardware.
If you see this, do this
| You see | Do this |
|---|---|
| Flag never changes in loop | Missing volatile. |
| Counter has odd values | Copy it with interrupts off. |
Related
encoder counts, millis roll-over.
Last reviewed: November 2026. These are the usual causes and the order that finds them fastest. Test on your own board; clones and cables differ.
Why this happens
The compiler optimises loop() as if nothing else can change a variable while it runs. If your loop only reads a flag, the compiler is allowed to load it into a processor register once and reuse that copy. The interrupt handler updates the copy in memory, but the loop keeps looking at the register. The keyword volatile tells the compiler that the value can change at any moment, so every read must go to memory.
volatile fixes visibility, not atomicity. On an 8-bit AVR a 16-bit or 32-bit variable takes several instructions to read, and an interrupt can land between them, giving half old and half new bytes. On ESP32 boards there is a separate rule: handler functions should be marked IRAM_ATTR so they run from RAM.
How to confirm the cause
- Print the flag from loop() at a slow rate and press the button or trigger the event. If the value never changes, the handler is not running or the compiler is caching it.
- Add a counter inside the handler (
hits++) declared volatile and print it. A rising counter proves the interrupt fires; if the counter rises but the flag logic does not work, the logic is the fault. - Check the pin: on an Uno only pins 2 and 3 support external interrupts. Use
digitalPinToInterrupt(pin)instead of a bare number. - Check the edge mode. A FALLING handler on a line that idles low never fires.
- Remove any Serial.print or delay from the handler and test again.
Worked example
A button on pin 2 using the internal pull-up. The handler sets a flag and the loop clears it after acting.
volatile bool pressed = false;
void onPress() { // on ESP32 write: void IRAM_ATTR onPress()
pressed = true;
}
void setup() {
Serial.begin(115200);
pinMode(2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), onPress, FALLING);
}
void loop() {
if (pressed) { pressed = false; Serial.println("press"); }
}Common mistakes
- Marking the pointer or the function volatile instead of the variable.
- Never clearing the flag, so the loop acts only once.
- Reading a multi-byte counter without switching interrupts off briefly.
- Expecting millis() to advance inside the handler; it does not while interrupts are blocked.
- Ignoring switch bounce, which makes one press look like several.
If this did not fix it
- Contact bounce on a mechanical switch; add a short time check or a small capacitor.
- A pin shared with another function, such as the serial pins 0 and 1.
- A library using the same timer or interrupt vector as your handler.
- An unsafe handler (long code, memory allocation) that crashes or resets the board.