Ich bin gerade dabei volatile zu erklären. Auf Microchip Studio klappt
mein Beispiel genau wie gewünscht und im Assembler sieht man auch warum.
Die nicht-volatile Variable wird in der ISR inkrementiert und die
Hauptroutine bekommt das nicht mit.
Jetzt wollte ich das auch mit der Arduino IDE zeigen und siehe da: es
ist egal, wie ich die Variable deklariere - es funktioniert. Also das
beispiel von Arduino ausprobiert:
https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
Und auch dem ist egal, was ich mit volatile mache.
Warum??? Wo liegt mein Denkfehler oder vermutlich eher: was macht die
IDE da komisches (mit einer der Compileroptionen?)
Jürgen schrieb:> Und auch dem ist egal, was ich mit volatile mache.
Wenn ich mich recht erinnere, sind bei Arduino die Optimierungen
standardmäßig ausgeschaltet. Ich meine mich zu erinnern, dass man bei
späteren Installationen gefragt wurde, ob man Optimierungen einschalten
will oder nicht.
Bei abgeschalteter Optimierung ist es in der Regel so, dass der Compiler
ziemlich genau das macht, was im Quelltext steht. Dabei liest er
Variablen jedesmal neu ein statt sie in Registern zu vorhalten -
zumindest über einige Programmabschnitte.
Und das ist nach den Strict Aliasing Rules im gleichen Alias Set wie
state.
Falls digitalWrite zu einem Funktionsaufruf compilert, dann muss state
auch immer wieder neu gelesen werden (es sei denn die Funktion ist
const, was sie nicht ist wegen dem volatile Zugriff).
Hinzu kommt noch das loop(), falls nicht geinlinet, ebenfalls ein
Funktionsaufruf darstellt. Also muss state auch immer wieder neu
gelesen werden.
Zuverlässig? Gar nicht.
Der Compiler garantiert (u.a), dass volatile Variablen bei jedem Zugriff
gelesen (also nicht in Registern gehalten) werden.
Andersrum ist aber ganz und gar nicht garantiert, dass das Weglassen von
volatile immer in einer Registervariable resultiert (d.h. ein
vergessenes volatile führt nicht zuverlässig zu einem Fehlverhalten).
Hallo,
zum demonstrieren könnten 2 Zählvariablen miteinander bzw.
"gegeneinander" zählen. Eine Variable ist volatile und die andere nicht
volatile.
Beide werden im gleichen Interrupt inkrementiert, Timer Interrupt o.ä..
Das lässt man sehr zügig machen. Dann lässt man beide Werte bspw. nur
aller 1s anzeigen. Die Chancen sollten gut stehen, dass man beobachten
kann, dass beide Werte auseinander driften. Eine andere Idee habe ich
nicht wie man das Fehlverhalten bewusst sichtbar machen könnte.
Und denke an das atomare "auslesen", sonst hat man andere Effekte. ;-)
Veit D. schrieb:> Die Chancen sollten gut stehen, dass man beobachten> kann, dass beide Werte auseinander driften.
Die Chancen stehen besser, das eine der Variablen sich gar nicht
verändert.
Oliver
Hallo,
nö, gezählt wird, dass habe ich schon vorher gewusst. Was mich nur
wundert ist, das sie nicht wie vermutet auseinander driften. Sowas ist
leider schwer provozierbar. Timer 1 Normalmode und inkrementiert wird
mit Overflow Interrupt.
Veit D. schrieb:> Was mich nur> wundert ist, das sie nicht wie vermutet auseinander driften.
Das wundert garnicht.
das "volatile" verhindert, dass der Compiler (falsche) Annahmen über den
Inhalt der Variablen zur Optimierung verwendet.
Solche Annahmen kann er für die ISR sowieso nicht treffen, da sieht der
Compiler nicht, wo sie aufgerufen wird, insofern macht innerhalb der ISR
das "volatile" keinen Unterschied.
Beim Auslesen von Variablen innerhalb von main(), die auch von der ISR
geändert werden, da macht es einen Unterschied.
Dort kann man dem Compiler mit memory/optimization-Barriers mitteilen,
wo er eine evtl. geänderte Variable lieber neu aus dem RAM liest. Ist
praktischerweise in sei(), ATOMIC_BLOCK usw. schon eingebaut.
Oder man macht die variable "volatile", wenn man eine einfache Lösung
will ohne sich viel mit der Theorie herumzuschlagen.
Hallo,
ich habe innerhalb der ISR kein volatile.
Die beiden Zählvariablen sind global.
ATOMIC_BLOCK verwende ich zum auslesen, damit während des Auslesens kein
Interrupt dazwischen funkt.
Veit D. schrieb:> ich habe innerhalb der ISR kein volatile.
Doch. du verwendest dort die Variable
>> volatile unsigned long countVolatile;Veit D. schrieb:> ATOMIC_BLOCK verwende ich zum auslesen
Damit brauchst du das volatile nicht mehr. Der ATOMIC_BLOCK sorgt schon
dafür, das alles passt.
Hallo,
du siehst aber schon das die Variablen global angelegt sind?
Würde lokal in der ISR sonst auch keinen Sinn machen.
> Damit brauchst du das volatile nicht mehr. Der ATOMIC_BLOCK sorgt schon> dafür, das alles passt.
Ist das immer so? Für mich gehörte volatile und atomic immer zusammen
damit nichts schief geht.
Veit D. schrieb:> Für mich gehörte volatile und atomic immer zusammen> damit nichts schief geht.
der ATOMIC_BLOCK erzwingt schon exakt den Effekt, den du mit volatile
erreichen willst.
Falls du nachschauen willst, warum:
ATOMIC_BLOCK benutzt cli().
avr/interrupt.h definiert cli() als
Hallo,
Danke für die Erklärung.
Gibt es Bsp. wofür man volatile alleine verwendet?
Also ohne cli, ATOMIC_BLOCK usw.
Für mich fällt da gerade jeder Grund weg.
Veit D. schrieb:> Gibt es Bsp. wofür man volatile alleine verwendet?
1
(volatile)uint8_tflag=0;
2
3
ISR(){
4
if(something){
5
flag=1;
6
}
7
}
8
9
main(){
10
print("Warte auf Flag...\n");
11
while(!flag){/* Warte...*/}
12
print("Flag ist true\n");
13
}
Weil flag nur 8 Bit hat, braucht es kein Atomic Block um es sauber zu
lesen. Aber der Compiler macht u.U. ohne volatile eine Endlosschleife
aus dem while und optimiert den Rest von main() weg...
Volatile ist nur für Einzelzugriffe auf 8Bit Variablen sinnvoll.
Für Read/Write oder Mehrbytevariablen bzw. Structs/Arrays muß man Atomic
benutzen.
Eine versteckte Falle sind 16Byte Register. Greift z.B. ein Interrupt
auf OCR1A und das Main auf OCR1B zu, muß man im Main auch atomar
kapseln, denn es wird das gemeinsame 8-bit temporary High Byte Register
(TEMP) von T1 benutzt.