Forum: Compiler & IDEs WINAVR, Optimierung, Globale Variable, Volatile


von Peter (Gast)


Lesenswert?

Hallo,
ich bin mir nicht ganz klar darübe wann man volatile Verwenden muss. 
Folgende Situation:
1
char flag = 0;
2
3
timer_int(void){
4
  if(flag){
5
    mache irgendwas
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

von (prx) A. K. (prx)


Lesenswert?

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.

von Peter Diener (Gast)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

Siehe Interrupt

von Peter (Gast)


Lesenswert?

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

von Peter Diener (Gast)


Lesenswert?

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

von Tobi (Gast)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Tobi (Gast)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.