Eine gute Erklärung von VOLATILE kann unter https://blog.regehr.org/archives/28 gefunden werden. Hier ein kleiner Auszug:
For every read from a volatile variable by the abstract machine, the actual machine must load from the memory address corresponding to that variable. Also, each read may return a different value. For every write to a volatile variable by the abstract machine, the actual machine must store to the corresponding address. Otherwise, the address should not be accessed (with some exceptions) and also accesses to volatiles should not be reordered (with some exceptions).
Der generelle Gebrauch wird unter diesen Links gut beschrieben:
- https://barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword
- https://www.embedded.com/electronics-blogs/programming-pointers/4025609/Place-volatile-accurately
- https://embeddedgurus.com/barr-code/2012/11/how-to-combine-volatile-with-struct/
- https://blog.regehr.org/archives/28
1// NEGATIVE EXAMPLE
2 // Main
3 void main()
4 {
5 foo = 0;
6
7 for(;;)
8 {
9 while(!foo); // could be replace by while(true)
10 foo = 0; // -> endless loop
11 }
12 }
13
14 // ISR
15 int foo; // globally shared variable
16
17 void isr()
18 {
19 // ...
20
21 foo = 1;
22 }
23
24// BAD PROGRAMMING STYLE
25 // Main
26 /* The function would need to load the variable from the
27 original location in case of external function declaration.
28 It doesn't know the preceding program code. Thus no
29 optimizations can be made.
30 Anyway in case the compiler optimization is more
31 sophisticated it may fail.
32 */
33 void wait()
34 {
35 while(!foo);
36 }
37
38 // static void main()
39 // inline void wait()
40 /* In case of inline or static function declaration the
41 compiler already knows all preceding program code and
42 could optimize something.
43 */
44
45 void main()
46 {
47 foo = 0;
48
49 for(;;)
50 {
51 wait();
52 foo = 0;
53 }
54 }
55
56 // ISR
57 int foo; // globally shared variable
58
59 void isr()
60 {
61 // ...
62
63 foo = 1;
64 } 1// volatile integer
2 volatile int foo;
3 int volatile foo;
4
5// pointer to volatile integer
6 volatile int * pFoo;
7 int volatile * pFoo;
8
9// volatile pointer to volatile integer
10 volatile int * volatile pFoo;
11 int volatile * volatile pFoo;
12
13// pointer to volatile struct
14 // separate volatile declaration for each variable
15 typedef struct
16 {
17 } foo_s;
18 foo_s volatile *pFoo = 0x10000;
19
20 // disadvantage: have to be placed within
21 // function declarations too
22 void doSmth(foo_s volatile *pFoo);
23
24 // each variable declared volatile
25 typedef volatile struct
26 {
27 } foo_s;
28 foo_s *pFoo = 0x10000;
29
30 // advantage: automatically added at each declaration
31 void doSmth(foo_s *pFoo);
32
33// volatile struct member
34 typedef struct
35 {
36 volatile int a;
37 int b;
38 } foo_s;
39
40 typedef struct
41 {
42 int volatile a;
43 int b;
44 } foo_s;
45
46 // advantage: all other accesses can be optimized
47
48// volatile const variable
49 const volatile int * p;
50
51 // usage: the program isn't allowed to store to it,
52 // however the register value may change from HW side
Beispiel für einen non-volatilen Buffer-Zugriff zw. ISR und Main
Bei einem Austausch über Double-Buffering passiert der Zugriff immer nur von einer Seite (Synchronisation vorausgesetzt). Das ermöglicht Compiler-Optimierungen, da ein direkter Lese/Schreib-Zugriff nicht mehr immer notwendig ist.
1// Main
2 void main()
3 {
4 int * local;
5
6 collect = &a;
7 process = &b;
8 new_data = 0;
9
10 IE(); // interrupts enabled
11
12 for(;;)
13 {
14 while(!new_data);
15 local = process;
16 new_data = 0;
17
18 // read/write access
19 x = *local;
20 y = *local; // could be optimized
21 *local = x/2;
22 }
23 }
24
25// ISR
26 int a,b; // double buffering
27 int * volatile collect;
28 int * volatile process;
29
30 int volatile new_data; // sync variable
31
32 void isr()
33 {
34 int * local = collect;
35
36 // ...
37
38 *local = y;
39 *local += 2; // could be optimized
40
41 // ...
42
43 // swap buffer and inform main
44 // (main should already be within while-loop)
45 if (process == &a)
46 {
47 process = &b;
48 collect = &a
49 }
50 else
51 {
52 process = &a;
53 collect = &b;
54 }
55 new_data = 1;
56 }Beispiel für einen volatilen Zugriff von non-volatile Variablen
Eine Variable, welche nur in der ISR verwendet wird, kann grundsätzlich non-volatile deklariert werden. Wird sie aber während der Initialisierung in der Main-Routine beschrieben, so muss sichergestellt werden, dass der Zugriff volatile passiert (direkter Zugriff an genau diesem Zeitpunkt) und die ISR noch nicht aufgerufen werden kann. Weiterführendes über volatile-casts kann unter https://bytes.com/topic/c/answers/221923-cast-volatile nachgelesen werden.
1// Main
2 void main()
3 {
4 *((int volatile *)&foo) = 1;
5
6 IE(); // interrupts enabled
7
8 // ...
9 }
10
11// ISR
12 int foo = 0; // globally shared variable
13
14 void isr()
15 {
16 if (foo == 0)
17 {
18 foo = 1;
19 }
20 else
21 {
22 foo = 0;
23 }
24
25 // ...
26 }Unterschiede bei der Kernel-Programmierung: