Non-atomic volatile reads cause corrupted multi-byte counters shared with ISR
0 reputation · 28 Feb 2025, 18:53 UTC
Shared multi-byte counter shows torn reads
An Arduino Uno sketch uses a volatile uint32_t counter incremented inside a pin-change ISR and read periodically in loop() for telemetry. The ISR executes in a few microseconds and the main loop prints the value every 100 ms. Despite volatile preventing register caching, occasional printed values show impossible jumps (e.g., 0x0000FFFF → 0x00010000 appears as 0x00000000 or 0x0001FFFF).
Constraints and uncertainty
The AVR core is 8-bit; a 32-bit read requires four bus cycles. If the ISR fires between those cycles, the main loop observes a torn value. volatile does not guarantee atomicity for types larger than the native word size. Disabling interrupts around the read (noInterrupts()/interrupts()) would serialize access but adds latency to the ISR path. The ATOMIC_BLOCK macro from util/atomic.h provides a scoped guard, yet its overhead and interaction with nested interrupts on other pins remain unclear.
Goal: reliably snapshot the 32-bit counter in loop() without blocking the ISR longer than a few cycles and without introducing subtle race conditions on other interrupt sources.
- Does
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)safely nest with other active interrupts on the same MCU? - Is a lock-free double-buffer scheme (ISR writes to buffer A, loop reads buffer B after atomic pointer swap) practical on an Uno's 2 KB RAM?
- What is the maximum ISR latency introduced by the chosen guarding method?