Hallo,
ich bin mir nicht ganz klar darübe wann man volatile Verwenden muss.
Folgende Situation:
1
charflag=0;
2
3
timer_int(void){
4
if(flag){
5
macheirgendwas
6
flag=0;
7
}
8
}
9
10
main(void){
11
int();
12
for(;;){
13
flag=1;
14
}
15
}
Warum wird hier die Variable "flag" wegoptimiert so das ich volatile
verwenden muss? Gibt es da irgenwelche leicht verständliche Regeln? Weil
solche Fehler kosten mich beim Programmieren immer wieder viel Zeit... .
Danke schonmal.
Gruß
Peter
Die einfachste Regel: Variablen, die sowohl in Interrupts als auch im
Hauptprogramm verwendet werden, müssen "volatile" sein.
Wenn verschachtelte Interrupts zugelassen sind (bei AVR meist nicht)
erweitert sich diese Regel auch auf Variablen die von verschiedenen
Interrupts verwendet werden.
Grund: Der Compiler weiss nichts vom Ablaufverhalten von Interrupts. Er
weiss nicht, dass timer_int() jederzeit in main() zuschlagen kann. Da
"flag" aus seiner Sicht im Hauptprogramm auf einen festen Wert gesetzt
und danach nicht mehr verändert wird, darf er ohne "volatile" diesen
schleifenunabhängigen Code aus der Schleife hinaus befördern.
Hallo,
volatile bedeutet, dass die Variable immer nach Veränderung sofort an
deren Speicherstelle im Ram rückgeschrieben werden muss und nicht noch
für eine bestimmte Zeit in einem Register "geparkt" werden darf.
Das wird als Optimierung gemacht, um einem Speicherzugriff
hinauszuzögern oder zu vermeiden, wenn die Variable direkt aus dem
Register weiterverwendet werden kann.
Ein Probem tritt auf, wenn diese Variable auch in einem Interrupt
verwendet wird. Hier wird auf die Ram-Adresse zugegriffen und der Wert
ist eventuell nicht aktuell, weil der neue nur im Register steht.
Dann wäre eine Entscheidung auf Basis des Wertes der Variable im
Interrupt vielleicht falsch. Wenn der Interrupt die Variable verändert,
kann es passieren, dass die Hauptschleife danach den vermeintlich
aktuellen Wert aus dem Register drüberschreibt und die Änderung aus dem
Interrupt wäre verloren.
Das liegt daran, dass die meisten Comnpiler nicht verstehen, dass ein
Interrupt zu jeder beliebigen Zeit auf das Ram zugreifen können und dort
deswegen immer aktuelle Werte brauchen.
Deswegen erzwingt man das sofortige aktualisieren der Variable nach
einer Änderung dadurch, dass man sie volatile deklariert.
Daher gilt, alle globalen Variablen, die im Hauptprogramm und im
Interrupt verwendet werden, müssen volatile sein.
Außerdem verhindert volatile, dass die Variable ganz wegoptimiert werden
kann, z.B. wenn sie nur hochgezählt wird und danach nie ausgewertet
wird.
Das will man aber manchmal als Verzögerung in einer for-Schleife
verwenden, die für nichts anderes da ist, als die Variable hochzuzählen
und damit Zeit zu verbrauchen. Wenn die dort verwendete Variable nicht
volatile ist, wird die Schleife komplett wegoptimiert und man wundert
sich dann, warum die Verzögerung nicht funktioniert. Dem Colpiler
erscheint das Hochzählen der Variable sinnlos, weil der Wert später nie
verwendet wird.
Also in Verzögerungsschleifen die Variable immer als volatile
deklarieren.
Das wären Fälle, wo man da braucht.
Vielleicht gibts noch mehr, aber wenn man verstanden hat, was der
Compiler macht, wenn die Variable nicht volatile ist, sieht man meistens
schnell, wo der Fehler liegt, wenn man ein Probem hat, das nur bei
eingeschalteter Optimierung auftritt.
Viele Grüße,
Peter
Hallo,
danke für eure Hinweise. Jetzt ist mir das ganze klarer. Aber wieso hat
das mit den Interupts dem Compiler nicht klargemacht? das kann doch
nicht allzu schwierig sein oder doch?
Gruß
Peter
Hallo,
warum das den meisten C-Compilern nicht klar ist, weiß ich nicht. Meinem
Pascal-Compiler für 51er muss man sowas nicht extra sagen, obwohl der
auch gut optimiert, passieren solche Fehler dort nicht.
Peter
Ich weiß, ist ein wenig her, aber mich interessiert noch was:
@ Peter:
das würde ja bedeuten, dass bei jeder Auswertung oder Modifizierung
einer Variable ausserhalb einer Interruptfunktion die Interrupts
disabled werden müssen (wenn sie volatile deklariert sind, wird ja die
variable aus dem RAM geholt, modifiziert und wieder zurückgeschrieben,
siehe unten).
Denn wenn die Variable ins Register geladen wird (z.B. zur Modifikation)
könnte schon ein Interrupt kommen und die Variable ändern, bevor
ausserhalb der Interruptroutine die Variable wieder zurückgeschrieben
wird.
Etwa so (Pseudo-Assembler) ohne Interrupt:
1
in r10, 0x100 ; Von Adresse 0x100 im RAM ins Register laden
2
inc r10; r10 erhöhen
3
out r10,0x100; Variable wieder zrückschreiben --> ALLES OK
Etwa so (Pseudo-Assembler) mitInterrupt:
1
in r10, 0x100 ; Von Adresse 0x100 im RAM ins Register laden
2
; HIER KOMMT EIN INTERRUPT UND ÄNDERT DIE VARIABLE
3
inc r10; r10 erhöhen
4
out r10,0x100; Variable wieder zrückschreiben
5
; DIE ÄNDERUNG AUS DEM INTERRUPT IST VERLOREN; TROTZ volatile
Peter Diener wrote:
> Hallo,>> warum das den meisten C-Compilern nicht klar ist, weiß ich nicht. Meinem> Pascal-Compiler für 51er muss man sowas nicht extra sagen, obwohl der> auch gut optimiert, passieren solche Fehler dort nicht.
Es ist kein Compilerfehler, sondern ein Anwenderfehler. SO was wie
Synchronisierung oder Absperren von Blöcken/Funktionen/Variablen gibt's
in C eben nicht. Und wenn irgendwas einen Wert im Speicher ändern kann
(ISR, Hardware) dann muss man das auf C-Ebene beschreiben, um es dem
Compiler mitzuteilen.
Ob bestimmte Zugriffe atomar, also ununterbrechbar sein müssen, hängt
von der Anwendung ab und teilweise auch von deren Umsetzung und
Konzeption ab.
@Tobi
In deinem Programm gibt es dann Probleme, wenn du R10 in einer ISR
veränderst und nicht richtig restaurierst (was man niemals nicht tun
sollte) oder wenn in einer ISR der Inhalt von SFR 0x100 veränder wird.
(Beachte, daß IN was anderes macht und andere Adressen verwendet als
LDS/STS).
Falls dem so ist, dann muss jeder Zugriff, der von einer solchen ISR ,
die den RAM/SFR-Wert potentiell verändert, unterbrochen werden kann,
atomar sein.
Das gilt auch für Aktionen in ISRs, in denen IRQs auftreten können, also
wenn IRQs kaskadiert werden.
Tobi wrote:
> das würde ja bedeuten, dass bei jeder Auswertung oder Modifizierung> einer Variable ausserhalb einer Interruptfunktion die Interrupts> disabled werden müssen
Korrekt. Das gilt übrigens genauso für I/O-Ports und I/O-Steuerregister.
Bei AVRs sind da ganz besonders die Register ausserhalb des
bitadressierbaren Bereichs zu beachten.
Danke für die Antworten!
Man muss also bedenken,dass Interrupts jederzeit dazwischenfunken
können, auch wenn man "nur" 8 Bit volatile - Variablen ändert.
Gruß Tobi