Skip to content

Commit 9174f26

Browse files
committed
Explain volatile
1 parent 420b1f8 commit 9174f26

File tree

1 file changed

+39
-14
lines changed

1 file changed

+39
-14
lines changed

README.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,9 @@ from A0 to A15, to input mode:
171171
* (volatile uint32_t *) (0x40020000 + 0) = 0; // Set A0-A15 to input mode
172172
```
173173

174-
> Note the `volatile` specifier. Its meaning will be covered later.
175-
176-
By setting individual bits, we can selectively set specific pins to a desired
177-
mode. For example, this snippet sets pin A3 to output mode:
174+
Note the `volatile` specifier. Its meaning will be covered later. By setting
175+
individual bits, we can selectively set specific pins to a desired mode. For
176+
example, this snippet sets pin A3 to output mode:
178177

179178
```c
180179
* (volatile uint32_t *) (0x40020000 + 0) &= ~(3 << 6); // CLear bit range 6-7
@@ -884,18 +883,44 @@ void SysTick_Handler(void) {
884883
}
885884
```
886885
887-
> The `volatile` specifier is required here becase `s_ticks` is modified by the
888-
> interrupt handler. `volatile` prevents the compiler to optimise/cache
889-
> `s_ticks` value in a CPU register: instead, generated code always accesses
890-
> memory. That is why `volatile` keywords is present in the peripheral struct
891-
> definitions, too.
886+
The `volatile` specifier is required here becase `s_ticks` is modified by the
887+
interrupt handler. `volatile` prevents the compiler to optimise/cache `s_ticks`
888+
value in a CPU register: instead, generated code always accesses memory. That
889+
is why `volatile` keywords is present in the peripheral struct definitions,
890+
too. Since this is important to understand, let's demonstrate that on a simple
891+
function: Arduino's `delay()`. Let is use our `s_ticks` variable:
892+
893+
```c
894+
void delay(unsigned ms) { // This function waits "ms" milliseconds
895+
uint32_t until = s_ticks + ms; // Time in a future when we need to stop
896+
while (s_ticks < until) (void) 0; // Loop until then
897+
}
898+
```
899+
900+
Now let's compile this code with, and without `volatile` specifier for `s_ticks`
901+
and compare generated assembly code:
902+
903+
```
904+
// NO VOLATILE: | // WITH VOLATILE:
905+
// uint32_t s_ticks; | // volatile uint32_t s_ticks;
906+
907+
ldr r3, [pc, #8] | ldr r2, [pc, #12]
908+
ldr r3, [r3, #0] | ldr r3, [r2, #0]
909+
adds r0, r3, r0 | adds r3, r3, r0
910+
| ldr r1, [r2, #0] <---- Update s_ticks
911+
cmp r3, r0 | cmp r1, r3
912+
bcc.n 200000d2 <delay+0x6> | bcc.n 200000d2 <delay+0x6>
913+
bx lr bx lr
914+
```
892915
893-
Note the `volatile` specifier for `s_ticks`. Any variable that is changed
894-
by the IRQ handler, must be marked as `volatile`, in order to prevent the
895-
compiler to optimize the operation by caching its value in a register.
896-
The `volatile` keyword makes generated code always load it from memory.
916+
Long story short: if there is no `volalile`, the `delay()` function will
917+
loop forever and never return. Because it caches (optimises) the value of
918+
`s_ticks` in a register and never updates it. The generated code with
919+
`volatile`, on the other hand, loads `s_ticks` value on each iteration.
920+
So, the rule of thumb: those values in memory that get updated by interrupt
921+
handlers, or by the hardware, declare as `volatile`.
897922
898-
Now we should add this handler to the vector table:
923+
Now we should add `SysTick_Handler()` interrupt handler to the vector table:
899924
900925
```c
901926
__attribute__((section(".vectors"))) void (*tab[16 + 91])(void) = {

0 commit comments

Comments
 (0)