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.
:
Bearbeitet durch Moderator
So sieht der Aufruf der IDE aus:
1 | ...7.3.0-atmel3.6.1-arduino7/bin/avr-gcc" -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "C:....elf" "....cpp.o" "...\\core.a" "-LC:\\Windows\\..." -lm |
"-Os" ist doch die Standardoptimerung? Zumindest nutzt Studio die auch
Jürgen schrieb: > 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/ Das da?
1 | void loop() { |
2 | digitalWrite(ledPin, state); |
3 | }
|
4 | |
5 | void blink() { |
6 | state = !state; |
7 | }
|
digitalWrite() ist wohl (effektiv) sowas wie
1 | * (volatile uint8_t*) ADDR = state; |
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.
:
Bearbeitet durch User
Sorry. Nein. Ich habe den falschen Link zum Beispiel geschickt. Das ist ja nur eine IRS. Ich meine das Beispiel: https://www.arduino.cc/reference/de/language/variables/variable-scope-qualifiers/volatile/ :
1 | // Die LED wird angeschaltet, wenn der Interrupt-Pin den Status ändert.
|
2 | volatile byte changed = 0; |
3 | |
4 | void setup() { |
5 | // Deklariere den LED-Pin als Output-Pin
|
6 | pinMode(LED_BUILTIN, OUTPUT); |
7 | // Setze einen Interrupt
|
8 | attachInterrupt(digitalPinToInterrupt(2), toggle, CHANGE); |
9 | }
|
10 | |
11 | void loop() { |
12 | if (changed == 1) { |
13 | // toggle() wurde vom Interrupt aus aufgerufen!
|
14 | // changed auf 0 zurücksetzen
|
15 | changed = 0; |
16 | // LED für 200 ms blinken lassen
|
17 | digitalWrite(LED_BUILTIN, HIGH); |
18 | delay(200); |
19 | digitalWrite(LED_BUILTIN, LOW); |
20 | }
|
21 | }
|
22 | |
23 | void toggle() { |
24 | // Invertiere den Status, dass die LED blinkt
|
25 | changed = 1; |
26 | }
|
Jürgen schrieb: > Ich meine das Beispiel: Mach's mal so
1 | void loop() { |
2 | while(1) { |
3 | if (changed) { |
4 | // toggle() wurde vom Interrupt aus aufgerufen!
|
5 | // changed auf 0 zurücksetzen
|
6 | changed = 0; |
7 | // LED für 200 ms blinken lassen
|
8 | digitalWrite(LED_BUILTIN, HIGH); |
9 | delay(200); |
10 | digitalWrite(LED_BUILTIN, LOW); |
11 | }
|
12 | }
|
13 | }
|
Wie kann ich das verhindern? Das Beispiel ist ja von Arduino und soll eigentlich genau das Phänomen demonstrieren.
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. ;-)
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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
1 | # define cli() __asm__ __volatile__ ("cli" ::: "memory")
|
das ::: "memory" ist dann die Magie: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
1 | The "memory" clobber tells the compiler that the assembly code performs memory |
2 | reads or writes to items other than those listed in the input and output operands |
3 | (for example, accessing the memory pointed to by one of the input parameters). |
4 | To ensure memory contains correct values, GCC may need to flush specific register |
5 | values to memory before executing the asm. Further, the compiler does not assume |
6 | that any values read from memory before an asm remain unchanged after that asm; |
7 | it reloads them as needed. Using the "memory" clobber effectively forms a |
8 | read/write memory barrier for the compiler. |
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_t flag =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...
:
Bearbeitet durch User
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.