ESP32/Interrupts and timing/18. Sharing data with an ISR
Your chip
Your language
Interrupts and timing · 18 of 81

Sharing data with an ISR

The interrupt fires, the flag is set, and the main loop never notices. One keyword fixes it, and understanding why is the difference between fixing this bug and moving it.

/esp32/sharing-data-with-an-isr · arduino · S3

The bug is in the compiler's favour

The code is obviously correct. The ISR writes the flag, the loop reads it. What the compiler sees is a loop that reads a variable nothing in the loop writes — so it reads it once, keeps it in a register, and spins on the register forever.

That is not a bug in the compiler. It is a correct optimisation of a program that never told it the value could change from outside.

volatile bool flag
plain bool
How the flag is declared
In memory
0
In the register
0
Passes since the press
0
Nothing has happened yet. Run it, and watch what the loop is actually reading on each pass — the register on the right, not the memory in the middle.

volatile means "load it every time"

One keyword, one extra memory load per pass, and the optimisation is off. Put it on anything an ISR writes and the main code reads, in either direction.

What it does not do is make the access atomic. A 32-bit read on a 32-bit chip happens in one instruction and is safe by accident; a uint64_t, a struct, or a pair of variables that have to agree with each other is not. For those, take the critical section — it disables interrupts on that core for the few instructions in between, and it must be as short as you can make it.

What an ISR may do

Almost nothing, and less than you would guess:

  • Set a flag, increment a counter, read a pin, write a pin.
  • Not Serial.print, not delay, not String, not new, not anything that waits on a lock.
  • Not anything that touches flash, unless the handler itself lives in RAM — which is what IRAM_ATTR is for.

The pattern that survives all of this is the one above: the interrupt records that something happened, and the loop decides what it means.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

The code

A button on GPIO 12, an ISR that records the press, and a loop that reports it. Everything shared between the two is volatile, and the counter is also atomic.

isrshare.ino
volatile bool pressed = false;
volatile uint32_t lastUs = 0;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR onPress() {
  uint32_t now = micros();
  if (now - lastUs < 40000) return;   // 40 ms of debounce
  portENTER_CRITICAL_ISR(&mux);
  lastUs = now;
  pressed = true;
  portEXIT_CRITICAL_ISR(&mux);
}

void setup() {
  Serial.begin(115200);
  pinMode(12, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(12), onPress, FALLING);
}

void loop() {
  if (!pressed) return;
  portENTER_CRITICAL(&mux);
  pressed = false;
  portEXIT_CRITICAL(&mux);
  Serial.println("button");          // printing happens here, not in the ISR
}

IRAM_ATTR puts the handler in RAM instead of flash. Without it the ISR can fire while flash is busy — during an OTA write, for instance — and the board crashes with a cache error rather than a stack trace.

When it does not work

The flag is set and the loop never sees it

The variable is not volatile. The compiler proved that nothing inside loop() writes it, cached it in a register, and the ISR's write to memory is now invisible. Nothing errors and nothing times out — it simply never fires.

One press produces five events

Switch bounce. A mechanical contact makes and breaks several times in the first few milliseconds, and an interrupt is fast enough to see every one. The micros() guard above is the cheapest fix; a capacitor across the switch is the other one.

The board crashes inside the interrupt handler

Something in the ISR is not allowed there. Serial.print, delay, anything that takes a mutex, and any call that touches flash can all fault. Set a flag and do the work in loop.

A 32-bit counter occasionally reads as nonsense

The loop read it halfway through the ISR's write. Volatile stops the caching, it does not make the access atomic — wrap both sides in a critical section, or use one of the atomic types.

Where this goes next

The other source of interrupts, and the one that fires whether or not anything is connected. Same rules about what an ISR may touch.

Hardware timers

Edit this page — content/esp32/sharing-data-with-an-isr.mdx

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse ESP32 on the forum