Interrupts and Shared Data
Learn when interrupts help, why handlers must stay short and why volatile does not make shared operations atomic.
An interrupt is a doorbell: stop briefly to note that someone arrived, then do the longer conversation in the main program.
An interrupt requests execution of a handler in response to an event such as a timer match or input edge. The processor preserves enough context to resume the interrupted code afterward. This reduces dependence on polling, but it does not eliminate latency. Higher-priority work, disabled interrupts and handler execution time all affect when an event is serviced.
A handler should capture the minimum information needed and return. Printing messages, waiting for serial output, delaying or performing a long calculation inside it can block other events. Store a flag, timestamp or sample, then let the main loop process it. If every event matters, a single flag may be insufficient because several arrivals can collapse into one true value.
Shared data introduces another issue. An interrupt can occur between reading a value and writing an updated value. Even reading a multi-byte counter may tear on a narrower processor. Volatile can be required for certain compiler-visible accesses, but it is not a lock, does not make increments indivisible, and is not a general C++ thread synchronization mechanism.
Use the platform's supported critical sections, atomic operations or ISR-safe queues. Keep protected regions short and preserve the previous interrupt state rather than blindly enabling interrupts on exit. Before using a C++ atomic in an ISR, verify that its implementation is suitable and lock-free where required. Platform documentation governs these low-level details.
Learn when interrupts help, why handlers must stay short and why volatile does not make shared operations atomic.
Core ideas
- Capture events quickly; process them outside the ISR.
- A shared flag can lose repeated events.
- Volatile and atomicity solve different problems.
CPU fraction in ISR ≈ event rate × handler time1000/s × 10 μs = 1%Work through a practical example
1An encoder produces 1000 edges per second.
2A 10 μs handler consumes about 10 ms each second, before other overhead.
3The main loop obtains a consistent snapshot using the platform's supported mechanism.
4If the loop is slow, check counter overflow and whether the input can deliver edges faster than the handler can service.
Assuming volatile counter++ is atomic, or calling blocking libraries from a handler.