volatile, interrupts and atomicity¶
Learning goals
Understand why ISR/main shared state needs explicit treatment, what volatile does and does not guarantee, why multi-byte objects are not automatically atomic on an 8-bit AVR, and how to take a coherent snapshot.
Prerequisites
Complete the pointers, buffers and structs lesson first. You should also understand interrupts, SREG and SRAM loads/stores.
An interrupt can change program state between two instructions in main(). That is useful, but it means shared data has rules that ordinary sequential code does not.
The paired example¶
EduAVR provides:
examples/c/shared-state/main.cexamples/asm/shared-state/main.S
Timer0 periodically updates:
The ISR increments shared_ticks and adds 0x0101 to shared_word. Main waits until at least three interrupts have occurred and then records one coherent snapshot.
What volatile means¶
In C, the shared objects are declared volatile. This tells the compiler that their values may change for reasons not visible in ordinary control flow and that accesses are observable.
It does not mean:
- the access is atomic;
- multiple accesses form one transaction;
- interrupts are disabled;
- races are impossible;
- a multi-byte value cannot change halfway through a read.
Inspect the generated disassembly and identify the loads/stores that remain because the objects are volatile.
Why 16 bits matter on an 8-bit CPU¶
shared_word is 16 bits wide, but AVR handles it as separate byte operations. An interrupt could occur between the low-byte and high-byte reads.
A mixed result can therefore be possible if main reads a changing multi-byte object without protection.
This is different from visibility. volatile addresses compiler observability; atomicity is a machine/execution property.
Atomic snapshot in C¶
The example preserves SREG, disables interrupts, copies the shared values, then restores the previous SREG:
The protected region is deliberately short. Work that does not require atomicity happens after interrupts are restored.
The same operation in Assembly¶
The Assembly version makes the sequence explicit:
This is the machine-level reason the C pattern works.
Deterministic Q1¶
Run:
At shared_state_ready, Q1 verifies both implementations. The timer must have generated at least three events, and the low and high bytes of the protected shared_word snapshot must correspond to the same captured tick count.
Qualified result
Build #371 and container qualification #29 passed for the paired shared-state implementation and deterministic Q1 probe.
Q1 demonstrates interrupt-driven CPU/SRAM behavior in the simulator. It does not make claims about physical interrupt sources or electrical timing.
Design rules¶
Keep shared state small and ownership clear. Prefer short critical sections. Do not disable interrupts around slow work merely for convenience. For larger data structures, consider copying a minimal snapshot or designing a producer/consumer protocol rather than protecting a long operation.
Exercises¶
- Explain why adding
volatilealone cannot make a 16-bit read atomic. - Find the two byte loads used for
shared_wordin each implementation. - Move unnecessary work into the protected region and explain the latency cost.
- Add another shared 16-bit value and extend the snapshot correctly.
- Describe a ring-buffer design where ISR and main each own different indices.
Next
Next, quantify SRAM and stack use and study how optimization changes generated AVR code without changing required program semantics.