= Solution
The <interrupt handler> is the only writer of `interval_ms`, `valid` and `next_slot`. The foreground function takes an <interrupt-safe ring-buffer snapshot> inside a short <critical section>, copying the count and exactly that many valid entries before restoring the previous mask. It performs the comparatively expensive summation and division after interrupts are restored. A new pulse then affects the next query, while the current query consistently describes the earlier snapshot.
Without exclusion, the foreground could read an old entry, be interrupted while the buffer is updated, then read new entries or an inconsistent count. Even indivisible 32-bit loads would not make that multi-element snapshot atomic. The <volatile-qualified objects in C> ensure that the compiler performs required shared or device accesses; `volatile` \b[does not provide mutual exclusion or a coherent multiword snapshot].<C11 committee draft N1570>[Sections 5.1.2.3 and 6.7.3, observable volatile accesses and type qualifiers]{ref}
This design assumes a single-core foreground/interrupt execution model: no other interrupt, DMA engine or core modifies the protected buffer. The mask hooks must prevent compiler reordering across their boundaries and really mask the GPIO interrupt. Restoring the saved mask also makes the function safe to call when interrupts were already disabled. Keep the <critical section> short enough that the event register does not coalesce multiple heartbeats while interrupts are masked; otherwise the hardware interface cannot reconstruct events it never recorded.
Back to article page