The volatile Keyword
What volatile actually tells the compiler, why register and ISR-shared variables need it, and what it does not do.
An optimising compiler assumes that a variable only changes when your code changes it. For hardware registers and variables shared with interrupts, that assumption is false — the value changes behind the compiler's back. volatile tells the compiler: every read and every write in the source must really happen, in that order.
Seeing the bug in the compiler's output
Without volatile the compiler legally hoists the read out of the loop: as far as C is concerned nothing in the loop can change the value. The program then hangs the first time the byte has not arrived yet.
wait_plain: @ no volatile
movw r0, #17408 @ r0 = 0x40004400 (USART2_SR)
movt r0, #16384
ldrb r0, [r0] @ read the register ONCE
lsls r0, r0, #26 @ test bit 5
it mi
popmi {r7, pc} @ already set: return
.LBB0_1:
b .LBB0_1 @ otherwise spin forever, never reading again
wait_volatile: @ with volatile
.LBB1_2:
ldr r1, [r0] @ re-read the register on every pass
lsls r1, r1, #26
bmi .LBB1_4 @ bit set: leave the loop
... @ (loop unrolled, every copy re-reads)
bpl .LBB1_1What volatile does NOT do
bool data_ready; // set in the UART ISR, polled in main volatile bool data_ready;
Without volatile, main's loop may read the flag once into a register and never see the ISR's update.
volatile uint32_t count; ... count++; // in main and in an ISR
Disable interrupts around the update, or keep separate counters per context volatile makes each access real but does not make the read-add-write sequence indivisible.
Is const volatile a contradiction?
No. const means this code may not write it; volatile means it may still change by other means. A hardware status or ID register is exactly that.
Does volatile make i++ atomic?
No. It guarantees the load and store happen, not that nothing runs between them. Use a critical section or an atomic instruction.
Name three kinds of objects that need volatile in bare-metal code.
Memory-mapped registers, variables shared with ISRs, and variables changed by DMA or another bus master.