volatile und Arduino

#7726714
Lesenswert?

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?)

Moderator Persönliche Seite #7726776
Lesenswert?

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.

Persönliche Seite #7726851
Lesenswert?

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.

#7726912
Lesenswert?

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
}
#7727285
Lesenswert?

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).

#7734709
Lesenswert?

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. ;-)

#7735225
Lesenswert?

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.

#7735241
Lesenswert?

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.
#7735250
Lesenswert?

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...

#7735540
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren