Ja, muss sein.
Zugriff auf Variablen ist nicht Thread Safe, es se denn, sie sind als
"volatile" deklariert.
Alle Variablen, die Du sowohl in der ISR als auch im Hauptprogramm
verwendest, verwendest Du in zwei Threads. Wenn Du sie nicht als
volatile deklarierst, riskierst Du, dass der Optimizer zu viel
optimiert. Beispiel:
1 | int i=0;
|
2 |
|
3 | ISR {
|
4 | i++;
|
5 | }
|
6 |
|
7 | main {
|
8 | while (1) {
|
9 | if (i>12) {
|
10 | tuwas;
|
11 | }
|
12 | }
|
13 | }
|
Während die ISR bei irgendeinem Ereignis die Variable i um eins erhöht,
testes das Hauptprogramm in einer Endlos-Schleife, ob i>12 ist und macht
dann irgend etwas. Ohne volatile könnte der Optimizer folgenden Code
generieren:
1 | ISR {
|
2 | Kopiere Variable i in Register R1 und R2
|
3 | Erhöhe Register R1 um eins
|
4 | Erhöhe Register R2 um eins, wenn R1 übergelaufen ist
|
5 | Kopiere Register R1 und R2 in Variable i
|
6 | }
|
7 |
|
8 | main {
|
9 | Kopiere Variable i in Register R3 und R4
|
10 | :marke
|
11 | Wenn R3 > 12 oder R4 > 0, dann tuwas
|
12 | Springe zu :marke
|
13 | }
|
Variablem werden manchmal in Register kopiert, weil der bestimmte
Operationen in Registern schneller sind. Manche Operationen können auch
nur mit Registern durchgeführt werden.
Lass uns mal einfach annehmen, dass bei Deinem Prozessor ein direkter
Vergleich zwischen einer Variablen (also RAM) und einer Konstanten nicht
geht. Aber der Prozessor kann ein Register mit einer Konstanten
vergleichen.
Schau Dir den obigen Pseudo-Code an. Wenn die ISR zwölf mal ausgeführt
wurde, dann hat die Variable i den Wert 12. Aber das Register R3/R4 hat
immer noch den Wert 0, die main Funktion wird also niemals das tun, was
sie sollte.
Der Optimizier geht von einem Single-Threaded Programm aus. Er geht
davon aus, dass Funktionen niemals untrbrochen werden. Deswegen erzeugt
er (wenn Du Pech hast) nicht funktionierenden Maschinen-Code. Durch die
"volatile" Deklaration sagst Du dem Optimizer, dass er diese Variable
ganz besonders vorsichtig behandeln muss, weil sie jederzeit verändert
werden kann, auch an Stellen, wo der Optimizier es nicht erwartet. Mit
"volatile" würde der Pseudo-Code so aussehen:
1 | ISR {
|
2 | Sperre andere Interrupts
|
3 | Kopiere Variable i in Register R1 und R2
|
4 | Erhöhe Register R1 um eins
|
5 | Erhöhe Register R2 um eins, wenn R1 übergelaufen ist
|
6 | Kopiere Register R1 und R2 in Variable i
|
7 | Erlaube andere Interrupts
|
8 | }
|
9 |
|
10 | main {
|
11 | :marke
|
12 | Sperre alle Interrupts
|
13 | Kopiere Variable i in Register R3 und R4
|
14 | Erlaube alle Interrupts
|
15 | Wenn R2 > 12 oder R4 > 0, dann tuwas
|
16 | Springe zu :marke
|
17 | }
|
Das Sperren anderer Interruptsin der ISR kann entfallen, wenn der
Mikrocontroller konstruktions-bedingt nur einen Interrupt-Level kennt
(also Interrupts nicht wiederum durch andere unterbrochen werden
können).
Im Hauptprogramm dient das Sperren der Interrupts nun dazu, zu
verhindern, dass die beiden Bytes der int Variable sich verändern,
während sie in die Register R3 und R4 kopiert werden. Weiterhin wird die
Variable nun bei jedem Schleifendurchlauf erneut in die Register
kopiert, so dass R3 jetzt tatsächlich irgendwann auch mal den Wert 12
enthalten kann.