@@ -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