Forum: Mikrocontroller und Digitale Elektronik ISR Aussetzer durch Volatile-Variablen-Polling ?


von Bernd K. (bkohl)


Lesenswert?

Bei meiner ISR-BLDC-Steuerung kämpfte ich bisher bei Chip-Temperaturen 
>60°C mit seltsamen Aussetzern.
Die Anwendung griff währenddessen hochfrequent auf volatile 8bit 
UART-Statusflags zu. Nachdem ich die Zugriffsraten durch ein 10ms-Delay 
ausbremse, treten die Aussetzer nicht mehr so oft auf.

Das verstehe ich nicht. Zugriffe auf 8bit-Variablen sind doch atomarer 
Natur.
Wieso, kann dadurch die ISR-Welt durcheinander kommen? Welche 
Dreck-Effekte können mit der Temperatur zusammenhängen?

Vielen Dank für Hinweise, Bernd

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Bernd K. schrieb:
> Die Anwendung griff währenddessen hochfrequent auf volatile 8bit
> UART-Statusflags zu.

???

von EAF (Gast)


Lesenswert?

Bernd K. schrieb:
> Das verstehe ich nicht.
Ich auch nicht.

Aber die Logik, in Zeile 42, scheint mir arg bedenklich zu sein.
Das tut sicherlich nicht das, was du dir vorstellst.

von Jens B. (dasjens)


Lesenswert?

EAF schrieb:
> Bernd K. schrieb:
>> Das verstehe ich nicht.
> Ich auch nicht.
>
> Aber die Logik, in Zeile 42, scheint mir arg bedenklich zu sein.
> Das tut sicherlich nicht das, was du dir vorstellst.

Ne, das siehst Du falsch. Die Zeile 23 beinhaltet den Fehler.
Zeile 42 baut nur darauf auf.

(23+42)>60. Das passt mit dem Temperaturproblem.

Helfen kann so schön sein.

von Bernd K. (bernd_k97)


Lesenswert?

A. S. schrieb:
> Bernd K. schrieb:
>> Die Anwendung griff währenddessen hochfrequent auf volatile 8bit
>> UART-Statusflags zu.
>
> ???

Ah ja ich meine damit nicht UART-Register-Flags sondern einfach sowas 
hier:

volatile uint8_t uart0_rx_flag;    // Flag,=0 String komplett empfangen

von A. S. (Gast)


Lesenswert?

Bernd K. schrieb:
> Ah ja ich meine damit nicht UART-Register-Flags sondern einfach sowas
> hier:

Die Zeile sieht OK aus.

von Stefan F. (Gast)


Lesenswert?

Die Problemursache ist woanders.

von EAF (Gast)


Lesenswert?

Bernd K. schrieb:
> volatile uint8_t uart0_rx_flag;    // Flag,=0 String komplett empfangen

Die Salami kommt heute in besonders dünnen scheiben, wie mir scheint.

von Wilhelm M. (wimalopaan)


Lesenswert?

Bernd K. schrieb:

> Ah ja ich meine damit nicht UART-Register-Flags sondern einfach sowas
> hier:
>
> volatile uint8_t uart0_rx_flag;    // Flag,=0 String komplett empfangen

Es gibt überhaupt keinen Grund, diese oder andere globale Variable als 
volatile zu qualifizieren.

Das richtige Instrument im Zusammenhang mit Nebenläufigkeit durch ISRs 
ist eine memory-barrier.

Das löst jetzt Dein Problem wahrscheinlich nicht, ist aber trotzdem ein 
sehr beliebter Fehler, der einige Optimierungen im Code verhindert.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Bernd K. schrieb:
> seltsamen Aussetzern.
> Die Anwendung griff währenddessen hochfrequent auf volatile 8bit
> UART-Statusflags zu. Nachdem ich die Zugriffsraten durch ein 10ms-Delay
> ausbremse, treten die Aussetzer nicht mehr so oft auf.
> Das verstehe ich nicht.
Eigentlich ganz einfach: wenn du etwas weniger oft machst, passiert das 
auch weniger oft. Allein das Verhältnis von "Zahl der 
fehlerverursachenden Zugriffen zu der Gesamtanzahl aller Zugriffe" ist 
relevant.

> seltsamen Aussetzern.
Was setzt da seltsam aus?

Bernd K. schrieb:
> Welche Dreck-Effekte können mit der Temperatur zusammenhängen?
Schlechtes, grenzwertiges Hardwaredesign kann solche 
temperaturabhängigen Effekte zeigen (wackelige Versorgung, schlechtes 
Layout usw.)

: Bearbeitet durch Moderator
von EAF (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Das richtige Instrument im Zusammenhang mit Nebenläufigkeit durch ISRs
> ist eine memory-barrier.

Bitte korrigiere mich, wenn ich falsch liege....

Aber:
Meines bisherigen Wissens nach, wirkt sich eine MB auf ALLE Variablen 
aus, welche gerade in Registern lagern.
Volatile nur auf die "eine".

von Bernd K. (bernd_k97)


Lesenswert?

Danke an alle!
Ich vergaß zu erwähnen, dass es sich um einen ATMega644 handelt. Ich 
verstehe "volatile" als einen MB-Mechanismus zwischen ISR und 
Singletask-App.
Die Platine arbeitet unter harschen Bedingungen durch die direkte 
Verschraubung mit dem Verbrennungsmotor. (Vibrationen, Temperatur, hohe 
Ströme). Möglicherweise hat der MC auch einen Temperaturschaden vom 
Heissluftlöten. Beim Löten ohne Hotplate ist mir letztens ein ACS712 
hops gegangen.

Ich denke der Code ist unspektakulär:
App:
1
char* getcmd(void){
2
int rval=0;
3
    if (uart0_rx_flag==1) 
4
    {uart=UART0;strcpy(cmd,uart0_rx_buffer);uart0_rx_flag=0;rval=1;}
5
    if (rval==1) return cmd; else return NULL;
6
}
ISR:
1
ISR (USART0_RX_vect){ // Empfang bis NL-Terminator auf Funkebene
2
char data;
3
    data = UDR0; // Daten auslesen, dadurch wird das Interruptflag gelöscht   
4
    urti0=0;  // Tout reset
5
    if (!uart0_rx_flag) {// Ist Puffer frei für neue Daten?
6
        if (data==URXTERM) {// ja, ist Ende des Strings (RETURN) erreicht?
7
            uart0_rx_buffer[uart0_rx_cnt]=0;// ja, dann String terminieren           
8
            uart0_rx_flag=1;// Flag für 'Empfangspuffer voll' setzen
9
            uart0_rx_cnt=0;// Zähler zurücksetzen
10
            }
11
        else if (uart0_rx_cnt<(BSIZE-1)){// Pufferüberlauf vermeiden     
12
             uart0_rx_buffer[uart0_rx_cnt]=data; // Daten speichern         
13
             uart0_rx_cnt++;
14
             }    // Zähler erhöhen
15
        else 
16
             uart0_rx_cnt=0;  // Zähler zurücksetzen
17
        }
18
}

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Bernd K. schrieb:
> Danke an alle!
> Ich vergaß zu erwähnen, dass es sich um einen ATMega644 handelt. Ich
> verstehe "volatile" als einen MB-Mechanismus zwischen ISR und
> Singletask-App.

Das ist eine ganz normale Anwendung von volatile und memory barrier.

Aber hört bitte auf, für jeden Scheiß euer privaten Abkürzungen zu 
erfinden! Das nervt und ist Schwachsinn!

kmh ICE und Eschede, PVC FCKW is nich OK!

> Die Platine arbeitet unter harschen Bedingungen durch die direkte
> Verschraubung mit dem Verbrennungsmotor. (Vibrationen, Temperatur, hohe
> Ströme). Möglicherweise hat der MC auch einen Temperaturschaden vom
> Heissluftlöten.

Unwahrscheinlich. Dann eher Vibrationen oder kalte Lötstellen. Der 
heißeste Kandidat ist aber ein Softwarefehler.

> Ich denke der Code ist unspektakulär:

Auch dort können mehr als genug Fehler drinstecken. Betreibe eine 
systematische Fehlersuche. Versuche den Fehler außerhalb des Motors 
ohne Hitze und Vibrationen zu reproduzieren. Stresse deine Schaltung 
LOGISCH so stark wie irgend möglich. Damit kann man Hitze und 
Vibrationen als Fehler ausschließen.

von A. S. (Gast)


Lesenswert?

Ohne Optimierung sollte das meist trotzdem laufen.

Die Empfangsdaten prüfst du ja hoffentlich noch ab, so kann da schnell 
Müll drin stehen. Und je nach Timing jedes Mal.

Ggf 2 Empfangspuffer im Wechsel. Oder ein Startzeichen.

von Falk B. (falk)


Lesenswert?

Bernd K. schrieb:

Dein Code ist schlecht formatiert und fragwürdig. Wenn man schon if-else 
ohne geschweifte Klammern verwendet, muss trotzdem die Einrückung der 
restlichen Klammern stimmen! Schließende Klammern müssen die gleiche 
Ebene wie öffnende haben! Ich empfehle stark, IMMER Klammern für if-else 
zu verwenden, auch wenn da nur eine Zeile drin steht!
1
ISR (USART0_RX_vect) {  // Empfang bis NL-Terminator auf Funkebene
2
    char data;
3
4
    data = UDR0;    // Daten auslesen, Interrupt flag gelöscht
5
    urti0=0;        // Tout reset
6
    if (!uart0_rx_flag) {      // Ist Puffer frei für neue Daten?
7
        if (data==URXTERM) {   // ja, ist Ende des Strings (RETURN)  erreicht?
8
            uart0_rx_buffer[uart0_rx_cnt]=0;    // ja, dann String terminieren
9
            uart0_rx_flag=1;    // Flag für 'Empfangspuffer voll' setzen
10
            uart0_rx_cnt=0;     // Zähler zurücksetzen
11
        }
12
        else {
13
           if (uart0_rx_cnt<(BSIZE-1)) {            // Pufferüberlauf vermeiden
14
                uart0_rx_buffer[uart0_rx_cnt]=data; // Daten speichern
15
                uart0_rx_cnt++;             // Zähler erhöhen
16
           }    
17
           else {
18
                uart0_rx_cnt=0;             // Zähler zurücksetzen
19
           }
20
       }
21
    }
22
}

Mal abgesehen davon, daß zu zuviele Trivialitäten kommentierst, ist 
deine Empfangsroutine fragwürdig.

Was passier, wenn du Daten empfängst, und

a) uart0_rx_flag noch true ist?
b) der Puffer voll ist?

von Stefan F. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Das richtige Instrument im Zusammenhang mit Nebenläufigkeit durch ISRs
> ist eine memory-barrier.

Gleich wirst du uns noch die dazu nötigen C befehle für AVR zeigen, und 
wie der Compiler das in Assembler umsetzt. In der Zwischenzeit hole ich 
mir mal Chips.

von Falk B. (falk)


Lesenswert?

Stefan F. schrieb:
>> Das richtige Instrument im Zusammenhang mit Nebenläufigkeit durch ISRs
>> ist eine memory-barrier.
>
> Gleich wirst du uns noch die dazu nötigen C befehle für AVR zeigen,

Die gibt es sei langem mit ATOMIC_BLOCK(), da ist das garantiert.
1
#include <util/atomic.h>
2
3
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
4
  // gesicherter, atomarer, nicht wegoptimierbarer Zugriff
5
  x = atomic_flag;
6
  y = atomic_long_variable;
7
}

Beitrag #7338488 wurde vom Autor gelöscht.
von Bernd K. (bernd_k97)


Lesenswert?

Falk B. schrieb:
> Dein Code ist schlecht formatiert und fragwürdig.
> a) uart0_rx_flag noch true ist?
> b) der Puffer voll ist?

Bei mir im AVR-Studio sehen die Klammern aus wie sie sein sollen....
zu a) altes Kommando noch nicht ausgewertet -> Nix neues wird gelesen
zu b) Puffer voll -> Puffer löschen, weil nur Müll drin steht (letzes 
else)

> Die gibt es sei langem mit ATOMIC_BLOCK(), da ist das garantiert.
Warum sollte man das bei 8bit-Variablen tun? Bei long ist es ja klar.

von Falk B. (falk)


Lesenswert?

Bernd K. schrieb:
>> Die gibt es sei langem mit ATOMIC_BLOCK(), da ist das garantiert.
> Warum sollte man das bei 8bit-Variablen tun? Bei long ist es ja klar.

Weil in dem Macro die Memory barrier drin steckt, ebenso wie bei cli() 
und sei(). Volatile allein GARANTIERT das NICHT, auch wenn es meistens 
funktioniert. Vor VIELEN Jahren war das selbst mit sei() und cli() NICHT 
garantiert, wurde dann aber im avr gcc repariert.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Dein Code ist Körperverletzung.

von Stefan F. (Gast)


Lesenswert?

Falk B. schrieb:
> Die gibt es sei langem mit ATOMIC_BLOCK(), da ist das garantiert.

OK, ich war voreilig frech.

Das Schlüsselword "volatile" braucht man laut Doku aber trotzdem noch. 
Sehe ich richtig, dass dieses Makro letztendlich nichts anderes macht 
als das?:
1
uint8_t sreg_save = SREG;
2
cli();
3
__asm__ volatile ("" ::: "memory");
4
... was zu tun ist
5
SREG = sreg_save;

Wenn aber cli() und sei() schon die Memory Barrier enthalten, wo ist 
dann der Vorteil dieses Makros? Lesbarkeit könnte ein Argument sein, 
allerdings finde ich es wesentlich geradliniger, direkt cli() und sei() 
hin zu schreiben, als in so einem Makro zu verstecken.

von Jan W. (jan_woj)


Lesenswert?

Interessant das mit dem ATOMIC_BLOCK().
ChatGPT liefert dazu folgendes Beispiel :
1
 
2
#include <avr/interrupt.h>
3
4
volatile int global_variable = 0;
5
6
ISR(TIMER0_OVF_vect)
7
{
8
    global_variable++;
9
}
10
11
int main(void)
12
{
13
    sei();  // Enable global interrupts
14
15
    // Timer0 setup code omitted for brevity
16
17
    while (1)
18
    {
19
        ATOMIC_BLOCK(ATOMIC_FORCEON)
20
        {
21
            // This block of code will be executed atomically
22
            // i.e., it won't be interrupted by other interrupts
23
            int local_copy = global_variable;
24
            // Do something with local_copy
25
        }
26
27
        // Other code here
28
    }
29
}
Ist es richtig das global variable mit volatile qualifiziert wird.
Ist dieses Beispiel korrekt?

von EAF (Gast)


Lesenswert?

Jan W. schrieb:
> Ist dieses Beispiel korrekt?

Jan W. schrieb:
> ATOMIC_FORCEON
Würde mal sagen: Nööö...

von Gerald K. (geku)


Lesenswert?

Bernd K. schrieb:
> mit seltsamen Aussetzern.

Was ist unter seltsame Aussetzer zu verstehen?

von Jan W. (jan_woj)


Lesenswert?

Und wie sieht es damit aus :
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/atomic.h>
4
5
int global_variable = 0;
6
7
ISR(TIMER0_COMPA_vect)
8
{
9
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
10
    {
11
        global_variable++;
12
        __asm__ __volatile__("" ::: "memory");
13
    }
14
}
15
16
int main(void)
17
{
18
    int local_copy;
19
    sei();
20
    while (1)
21
    {
22
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
23
        {
24
            local_copy = global_variable;
25
            __asm__ __volatile__("" ::: "memory");
26
        }
27
        // Do something with local_copy
28
    }
29
    return 0;
30
}
ist es korrekt?

von Alter Sack (Gast)


Lesenswert?

Stefan F. schrieb:
> Falk B. schrieb:
>> Die gibt es sei langem mit ATOMIC_BLOCK(), da ist das garantiert.
>>
> Wenn aber cli() und sei() schon die Memory Barrier enthalten, wo ist
> dann der Vorteil dieses Makros? Lesbarkeit könnte ein Argument sein,
> allerdings finde ich es wesentlich geradliniger, direkt cli() und sei()
> hin zu schreiben, als in so einem Makro zu verstecken.

Denk mal kurz nach, besonders über potentielle Änderungen.

Wenn sich mal etwas ändert, z.B. wegen Compilerupdates, werden die 
Leute, die das Makro warten, es dann anpassen - Du musst nix tun.

Wenn man alles selbst von Hand macht - muss man dann auch selbst alles 
von Hand anpassen.

Ja, ist im vorliegenden Fall nicht so wahrscheinlich, aber…

von EAF (Gast)


Lesenswert?

Jan W. schrieb:
> _asm_ __volatile__("" ::: "memory");
Das brauchst du da nicht, das ist im Atomic Block schon 4 mal drin.

von Stefan F. (Gast)


Lesenswert?

Jan W. schrieb:
> Ist es richtig das global variable mit volatile qualifiziert wird.
> Ist dieses Beispiel korrekt?

So ist das jedenfalls in der avr libc dokumentiert. Hier geht es um 
zweierlei Dinge:

a) Volatile sagt dem Compiler, dass jeder Schreib- und Lesezugriff 
direkt auf die Speicherzelle im RAM stattfinden muss. Bei wiederholtem 
Zugriff wird immer wieder auf das RAM zugegriffen. Der Compiler darf den 
Wert nicht in einem CPU Register cachen.

Wenn im Hauptprogramm z.B. eine Schleife wäre:
1
int8_t global_variable=100;
2
3
ISR(TIMER0_OVF_vect) {
4
    global_variable++;
5
}
6
7
8
for (int i=0; i<10; i++) {
9
  ...
10
  global_variable--;
11
}

Dann könnte der Compiler ohne Volatile die Variable aus dem RAM in 
Register R2 kopieren, dann dieses Register wiederholt decrementieren und 
erst zum Schluss den Wert von R2 zurück ins RAM schreiben. Ohne 
Unterbrechungen hast du am Ende den korrekten Wert 90 in der Variable.

Jetzt stelle dir vor, während die for Schleife läuft kommen drei 
gewollte Interrupts. Dann bekommst du am Ende nicht die erwarteten 93 
sondern 90. Die Änderungen durch den Interrupt wurden während der for 
Schleife völlig ignoriert.

Volatile verbietet solche Abkürzungen, so dass Unterbrechungen der For 
Schleife korrekte Ergebnisse erzeugen. (Solche Fehler passieren nicht 
nur bei for Schleifen.)

b) Die Memory Barrier (also das Sperren von Interrupts) verhindert, dass 
der Interrupt dazwischen "funkt".

Das ist auch bei Variablen wichtig, die mehr als ein Byte groß sind. Auf 
diese muss die CPU in mehreren Schritten zugreifen. Wenn sie dabei durch 
eine ISR unterbrochen wird, und diese ISR ausgerechnet auf die selbe 
Variable Zugreift, kann Kudeelmuddel entstehen.

Beispiel:
1
int i=50;
2
3
ISR(TIMER0_OVF_vect) {
4
    i=256;
5
}
6
7
while (1) {
8
  if (i==0) tue etwas;
9
}

Die CPU holt zuerst die oberen 8 Bit und sieht 0. Dann holt sie die 
unteren 8 Bit und sieht 50. Die Bedingung ist nicht erfüllt. So weit 
alles gut. Wenn aber ein Interrupt dazwischen kommt, passiert folgendes:

Die CPU holt zuerst die oberen 8 Bit und sieht 0. Nun macht die ISR 
i=256. Dann holt die CPU die unteren 8 Bit und sieht 0. Die if-Bedingung 
ist erfüllt, obwohl i überhaupt nicht 0 ist.

Ich habe mir folgendes eingeprägt:

Variablen die in einer ISR geändert werden und außerhalb der ISR gelesen 
werden (oder umgekehrt), müssen volatile sein. Zugriffe auf Variablen, 
die größer als 8 Bit sind, müssen außerdem davor beschützt werden, von 
dem Interrupt unterbrochen zu werden.

Das kann man mit cli() und sei() machen, je nach µC Modell gibt es aber 
eventuell elegantere Methoden wo man nur die kritischen Interrupts 
sperrt anstatt alle.

Alter Sack schrieb:
> Wenn sich mal etwas ändert, z.B. wegen Compilerupdates, werden die
> Leute, die das Makro warten, es dann anpassen - Du musst nix tun.

Ja, das ist ein gutes Argument.

Beitrag #7338555 wurde von einem Moderator gelöscht.
Beitrag #7338556 wurde von einem Moderator gelöscht.
von Sebastian (Gast)


Lesenswert?

Hallo Bernd,

deine Annahme ist richtig. Zugriff und Schreiben einer 8-bit-Variable 
sind auf dem Atmega jeweils atomar. Wenn solche Variablen als volatile 
gekennzeichnet sind, dann kann man sie problemlos zur Kommunikation 
zwischen ISR-Kontext und main-Kontext verwenden.

Dein Problem liegt nicht an dem Code den du bisher gezeigt hast.

LG, Sebastian

von Gerhard O. (gerhard_)


Lesenswert?

Vielleicht könnten die infos in dem Link irgendwie nützlich eingesetzt 
werden:
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html

Beitrag #7338561 wurde vom Autor gelöscht.
von 900ss (900ss)


Lesenswert?

Wilhelm M. schrieb:
> Das löst jetzt Dein Problem wahrscheinlich nicht, ist aber trotzdem ein
> sehr beliebter Fehler,

Durch gebetsmühlenartiger Wiederholung wird es auch kein Fehler. Es ist 
keiner. Erzähl nicht ständig solch einen Unfug.
Es nervt und hat in diesem Fall noch nicht einmal etwas mit dem Problem 
zu tun.
Also halte bitte die Finger still. Wir wissen schon dass du das gerne 
mit memory barrier löst. Danke.

von Gerhard O. (gerhard_)


Lesenswert?

Hier noch etwas besser passend. Das vorherige könnt ihr ignorieren.
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

von Falk B. (falk)


Lesenswert?

Jan W. schrieb:
> Ist es richtig das global variable mit volatile qualifiziert wird.

Ja.

> Ist dieses Beispiel korrekt?

Nicht ganz. Die lokale Variable im Atomic Block ist nur dort gültig. 
Kann man THEORETISCH machen, ist aber praktisch Unsinn. Denn man will ja 
möglichst schnell die lokalen Kopien erstellen und dann die Interrupts 
wieder freigeben. Also muss die lokale Variable wenigstens in main 
definiert werden. Über ATOMIC_FORCEON kann man streiten, meiste wird man 
eher ATOMIC_RESTORESTATE nutzen.

von Falk B. (falk)


Lesenswert?

Jan W. schrieb:
> ist es korrekt?

Du musst das Rad nicht neu erfinden. Die memory barriers stecken in 
ATOMIC_BLOCK! Und in der ISR ist ATOMIC totaler Unsinn. Zumindest beim 
AVR, der von Haus aus KEINE verschachtelten Interrupts hat.

: Bearbeitet durch User
von Bernd K. (bernd_k97)


Lesenswert?

Falk B. schrieb:
> Bernd K. schrieb:
>>> Die gibt es sei langem mit ATOMIC_BLOCK(), da ist das garantiert.
>> Warum sollte man das bei 8bit-Variablen tun? Bei long ist es ja klar.
>
> Weil in dem Macro die Memory barrier drin steckt, ebenso wie bei cli()
> und sei(). Volatile allein GARANTIERT das NICHT, auch wenn es meistens
> funktioniert. Vor VIELEN Jahren war das selbst mit sei() und cli() NICHT
> garantiert, wurde dann aber im avr gcc repariert.

Hab hier den Interrupt-Artikel nochmal gelesen. Auch das FAQ 
https://www.mikrocontroller.net/articles/FAQ#Was_hat_es_mit_volatile_auf_sich

Nirgends ein Hinweis, das Atomic-Makros auch bei 8bit-Variablen nötig 
sind. Wenn ja, wäre der einfache Schreib- oder Lesezugriff auf die Ports 
- oder was alles unter "Memory-Mapped" zählt - nicht so einfach.

@ Gerald K: Mit Aussetzern meine ich ein gestörtes Timing der 
sensorlosen Kommutierung bei erhöhter Temperatur. Leider kommen da 
vielfältige Ursachen infrage. Einen Beweis, dass die ISR wirklich 
Aussetzer hat, würde die Zeitmessung mit einem unabhängigen Timer 
liefern.

von Falk B. (falk)


Lesenswert?

Stefan F. schrieb:
> b) Die Memory Barrier (also das Sperren von Interrupts) verhindert, dass
> der Interrupt dazwischen "funkt".

FALSCH! Eine Memory barrier ist was GANZ ANDERES! Es ist eine Grenze im 
Programmfluß, an der alle durch Optimierung gepufferten Variablen in den 
Speicher geschrieben werden müssen.

von Falk B. (falk)


Lesenswert?

Bernd K. schrieb:
> Hab hier den Interrupt-Artikel nochmal gelesen. Auch das FAQ
> https://www.mikrocontroller.net/articles/FAQ#Was_hat_es_mit_volatile_auf_sich
>
> Nirgends ein Hinweis, das Atomic-Makros auch bei 8bit-Variablen nötig
> sind.

Sind sie streng genommen auch nicht.

> Wenn ja, wäre der einfache Schreib- oder Lesezugriff auf die Ports
> - oder was alles unter "Memory-Mapped" zählt - nicht so einfach.

Ist er teilweise auch nicht. Steht aber im Artikel.

https://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff

von A. S. (Gast)


Lesenswert?

Sebastian schrieb:
> Dein Problem liegt nicht an dem Code den du bisher gezeigt hast.

Doch, vermutlich. Wenn das der ganze Code ist, dann bekommt er zeitweise 
vermutlich nur korrupte Telegramme. Weil er weder Startzeichen noch 
irgendeine Plausibilität prüft.

von Bernd K. (bernd_k97)


Lesenswert?

A. S. schrieb:
> Sebastian schrieb:
>> Dein Problem liegt nicht an dem Code den du bisher gezeigt hast.
>
> Doch, vermutlich. Wenn das der ganze Code ist, dann bekommt er zeitweise
> vermutlich nur korrupte Telegramme. Weil er weder Startzeichen noch
> irgendeine Plausibilität prüft.

Während des kritschen Motorstarts kommen überhaupt keine Telegramme.
Der Motor muss gerade noch mein Haus heizen, weswegen ich zur Zeit mit 
dem Fehler leben muss. Ich hab noch ein Austausch-BHKW mit verbesserter 
Hardware und einer etwas anderen Kommutierungs-ISR. Womöglich löst sich 
so das Problem in Luft auf. Dank des Ausbremsens der getcmd()-Funktion 
mit 10ms delay - warum auch immer - kann ich damit gut leben. Perfekt 
ist aber etwas anderes.
Wenn ich etwas rausgefunden habe, schreibe ich es rein.

Danke nochmal, dass ihr alle auch die Zeit genommen habt!

von A. S. (Gast)


Lesenswert?

Bernd K. schrieb:
> Wenn ich etwas rausgefunden habe, schreibe ich es rein.

A. S. schrieb:
> Ggf 2 Empfangspuffer im Wechsel. Oder ein Startzeichen.

Jedes Mal, wenn das abholen zu lange dauert, ist das nächste Telegramm 
kaputt. Dagegen hilft ein zweiter Empfangspuffer.

Oder kaputte Telegramme verwerfen: wenn ein neues Zeichen kommt, während 
das alte noch nicht abgeholt ist, ein flag setzen.

Solange das gesetzt ist, wird alles außer dem Endezeichen ignoriert. 
Danach das nächste Telegramm Puffern.

Sauberer wäre ein Startzeichen, falls Du ein eigenes Protokoll hast.

von A. S. (Gast)


Lesenswert?

1
static char overrun;
2
3
ISR (USART0_RX_vect){
4
char data = UDR0;
5
6
    urti0=0;  
7
    if (overrun) {
8
        if (data==URXTERM) {
9
            overrun=0;
10
            uart0_rx_cnt=0;
11
        }
12
    }
13
    else if (!uart0_rx_flag) {
14
    ... //Dein Code 
15
    }
16
    else {
17
        overrun = 1;
18
    }
19
}

overrun wird auch gesetzt, wenn der Puffer überläuft, als in Deinem 
letzten Else.

von Jan W. (jan_woj)


Lesenswert?

Müsste overrun nicht volatile sein?

von Stefan F. (Gast)


Lesenswert?

Falk B. schrieb:
> FALSCH! Eine Memory barrier ist was GANZ ANDERES! Es ist eine Grenze im
> Programmfluß, an der alle durch Optimierung gepufferten Variablen in den
> Speicher geschrieben werden müssen.

Warum muss die Variable dann trotzdem volatile sein?

von Stefan F. (Gast)


Lesenswert?

Jan W. schrieb:
> Müsste overrun nicht volatile sein?

Wenn es nur innerhalb der ISR verwendet wird, dann nicht.

von Falk B. (falk)


Lesenswert?

Stefan F. schrieb:
>> FALSCH! Eine Memory barrier ist was GANZ ANDERES! Es ist eine Grenze im
>> Programmfluß, an der alle durch Optimierung gepufferten Variablen in den
>> Speicher geschrieben werden müssen.
>
> Warum muss die Variable dann trotzdem volatile sein?

Um Zugriffsoptimierungen und Verschiebungen über die memory barrier zu 
verhindern. RTFM!

https://www.nongnu.org/avr-libc/user-manual/optimization.html#optim_code_reorder

von Falk B. (falk)


Lesenswert?

Stefan F. schrieb:
>> Müsste overrun nicht volatile sein?
>
> Wenn es nur innerhalb der ISR verwendet wird, dann nicht.

Wird sie aber NICHT!

von Falk B. (falk)


Lesenswert?

Jan W. schrieb:
> Müsste overrun nicht volatile sein?

JA! Static ist bei globalen Variablen in den meisten Fällen unnötig.

von Stefan+ (Gast)


Lesenswert?

Hallo,

Bernd K. schrieb:
> Ich denke der Code ist unspektakulär:
> App:char* getcmd(void){
> int rval=0;
>     if (uart0_rx_flag==1)
>     {uart=UART0;strcpy(cmd,uart0_rx_buffer);uart0_rx_flag=0;rval=1;}
>     if (rval==1) return cmd; else return NULL;
> }

Stell dir vor in "uart0_rx_buffer" steht keine abschliessende 0x00,
was macht dein "strcpy" dann?

wahrscheinlich etwas sehr spektakuläres!!!

Gruß

von Nop (Gast)


Lesenswert?

Falk B. schrieb:

> JA! Static ist bei globalen Variablen in den meisten Fällen unnötig.

Äh? Static ist überaus nützlich zur Scope-Begrenzung.

von Bernd K. (bernd_k97)


Lesenswert?

Danke Stefan+! Wenn man schon klaut, dann richtig :-)

(das spektakuläre war sogar schon vorgekommen, wenn mal ein ganzer Flash 
upload übers Funknetz ging)

von Jan W. (jan_woj)


Lesenswert?

Nop schrieb:
> Falk B. schrieb:
>> JA! Static ist bei globalen Variablen in den meisten Fällen unnötig.
>
> Äh? Static ist überaus nützlich zur Scope-Begrenzung.

eben, scope-Begrenzung. Ist es nicht sinvoll alle globalen Variablen die 
in anderen c Dateien nicht benutzt werden mit static zu markieren?

von Sebastian (Gast)


Lesenswert?

Stefan+ schrieb:
> Stell dir vor in "uart0_rx_buffer" steht keine abschliessende 0x00

Die ISR trägt die Endnull ein bevor sie das uart0_rx_flag setzt. Also 
ist diese Vorstellung nichts als Tagträumerei ...

LG, Sebastian

von Falk B. (falk)


Lesenswert?

Jan W. schrieb:
> eben, scope-Begrenzung. Ist es nicht sinvoll alle globalen Variablen die
> in anderen c Dateien nicht benutzt werden mit static zu markieren?

Wozu? Sie sind doch global. Bei mir liegen alle globalen Variablen in 
einem größeren Projekt in einer eigenen Datei. Einzelne Quelldateien 
haben keine globalen Variablen.

von Nop (Gast)


Lesenswert?

Jan W. schrieb:

> eben, scope-Begrenzung. Ist es nicht sinvoll alle globalen Variablen die
> in anderen c Dateien nicht benutzt werden mit static zu markieren?

Das ist absolut sinnvoll, weil man auf Anhieb sieht, daß die Variable 
nur in dieser Datei (bzw. in dieser Funktion) verändert wird. Aus 
demselben Grund macht man ja auch Funktionen static, sofern möglich, und 
übergibt Pointer-Parameter mit const, sofern möglich.

Falk B. schrieb:

> Wozu? Sie sind doch global. Bei mir liegen alle globalen Variablen in
> einem größeren Projekt in einer eigenen Datei.

OMG. Naja, wenn's nicht wesentlich über LED-Blinkies für Hobbyprojekte 
hinausgeht, wo eh kein anderer Entwickler jemals das Mißvergnügen haben 
wird, sich in so einen Misthaufen einarbeiten zu müssen, ist es auch 
egal.

von A. S. (Gast)


Lesenswert?

Falk B. schrieb:
>> Wenn es nur innerhalb der ISR verwendet wird, dann nicht.
>
> Wird sie aber NICHT

Falk B. schrieb:
>> Müsste overrun nicht volatile sein?
>
> JA! Static ist bei globalen Variablen in den meisten Fällen unnötig.

Falk, Dein Konto wurde gehackt von einem Troll!

Für alle anderen: overrun braucht nicht volatile zu sein, könnte hier 
static in der ISR sein.

Static zur Scopebegrenzung ist immer sinnvoll. Das weiß der echte Falk 
auch

von Jan W. (jan_woj)


Lesenswert?

Falk B. schrieb:
> Wozu? Sie sind doch global. Bei mir liegen alle globalen Variablen in
> einem größeren Projekt in einer eigenen Datei. Einzelne Quelldateien
> haben keine globalen Variablen.

Bei mir gibt es Module, die eigene globale Variablen haben. Auf diese 
Variablen kann man von außen nicht zugreifen. Auf manche 'Modul 
Variablen' wo ein Zugriff von außen notwendig ist, stellt das Modul eine 
lese, bzw. Eine schreibfunktion (ähnliches wie z.B. Get, set bei c#). 
Auf diese Weise kann man Daten kapseln.

von Falk B. (falk)


Lesenswert?

Nop schrieb:

> OMG. Naja, wenn's nicht wesentlich über LED-Blinkies für Hobbyprojekte
> hinausgeht, wo eh kein anderer Entwickler jemals das Mißvergnügen haben
> wird, sich in so einen Misthaufen einarbeiten zu müssen, ist es auch
> egal.

Erzähl kein Blech. Wenn gleich ich sicher kein Softwerker bin und auch 
keine wirklich großen Projekte bearbeite, wage ich zu behaupten, daß 
diese Methode legitim ist und auch gut funktioniert. Vielleicht nicht 
bei ganz großen Projekten.

von Falk B. (falk)


Lesenswert?

A. S. schrieb:
> Static zur Scopebegrenzung ist immer sinnvoll. Das weiß der echte Falk
> auch

Hab ich noch nie benutzt, bin aber auch kein Softwerker. Ich programmier 
auch als Profi bestenfalls 10% meiner Zeit, eher weniger. Und dort auch 
nur kleine Projekte, die praktisch Ein Mann Projekte sind.

von Nop (Gast)


Lesenswert?

Falk B. schrieb:

> Erzähl kein Blech. Wenn gleich ich sicher kein Softwerker bin

Merkt man.

> keine wirklich großen Projekte bearbeite, wage ich zu behaupten, daß
> diese Methode legitim ist und auch gut funktioniert.

"Funktionieren" ist nicht das Thema. Die mangelhafte Wartbarkeit schon - 
und das ist bei professioneller Entwicklung immer ein Thema, weil 
unwartbarer Müll letztlich eine Menge Geld kostet.

Für kleine Einmann-Wegwerfprojekte ist das natürlich egal, wenn Du 
solche Grütze zusammenpfuscht. Du könntest aber auch lernen, Dich zu 
verbessern, statt den Mist damit zu rechtfertigen, daß Du es schon immer 
falsch gemacht hast.

von Falk B. (falk)


Lesenswert?

Nop schrieb:
>> keine wirklich großen Projekte bearbeite, wage ich zu behaupten, daß
>> diese Methode legitim ist und auch gut funktioniert.
>
> "Funktionieren" ist nicht das Thema. Die mangelhafte Wartbarkeit schon -
> und das ist bei professioneller Entwicklung immer ein Thema, weil
> unwartbarer Müll letztlich eine Menge Geld kostet.

Was zum Geier ist daran unwartbar? Es ist weder Spaghetticode noch 
sonstiges Chaos!

von EAF (Gast)


Lesenswert?

Falk B. schrieb:
> Bei mir liegen alle globalen Variablen in
> einem größeren Projekt in einer eigenen Datei. Einzelne Quelldateien
> haben keine globalen Variablen.

Das halte ich auch für eine Irrweg!

Wobei natürlich gilt:
Vermeidbare globale Variablen, sind böse Variablen.

von Stefan F. (Gast)


Lesenswert?

Falk B. schrieb:
> Was zum Geier ist daran unwartbar? Es ist weder Spaghetticode noch
> sonstiges Chaos!

Manche Leute haben recht strenge Vorstellungen davon, welchen 
Programmierstil sie als brauchbar akzeptieren. Man könnte es auch 
"mangelnde Flexibilität" nennen.

Ich habe das Programmieren weitgehend alleine gelernt und kam erst Jahre 
Später in den Genuss von Teamarbeit. Da wir alle so drauf waren, hatten 
wir viel über Programmierstil diskutiert und Regelwerke verfasst. Wir 
fanden das damals ungeheuer wichtig.

Heute beklage ich mich nur noch sehr selten über den Programmierstil 
anderer. Also nur, wenn es wirklich ganz schlimm und wirklich massiv 
Zeit gekostet hat. Hätte-Hätte-Fahrradkette Diskussionen bringen 
hingegen nichts, man schließt sich damit bloß selbst vom Team aus.

von Sebastian (Gast)


Lesenswert?

Falk B. schrieb:
> A. S. schrieb:
>> Static zur Scopebegrenzung ist immer sinnvoll. Das weiß der echte Falk
>> auch
>
> Hab ich noch nie benutzt,

Wenn du eine Bibliothek entwickelst und zur Verfügung stellst, dann 
möchtest du nicht, dass deren dateilokalen privaten globalen Variablen 
extern sichtbar werden, weil es sonst evtl. Namenskonflikte gibt.

LG, Sebastian

von Falk B. (falk)


Lesenswert?

Sebastian schrieb:
>>> Static zur Scopebegrenzung ist immer sinnvoll. Das weiß der echte Falk
>>> auch
>>
>> Hab ich noch nie benutzt,
>
> Wenn du eine Bibliothek entwickelst und zur Verfügung stellst, dann
> möchtest du nicht, dass deren dateilokalen privaten globalen Variablen
> extern sichtbar werden, weil es sonst evtl. Namenskonflikte gibt.

Stimmt, habe ich aber noch nie gemacht ;-)

von Nop (Gast)


Lesenswert?

Falk B. schrieb:

> Was zum Geier ist daran unwartbar?

Daß jeder von überall her auf jede globale Variable zugreifen kann, auch 
wenn das nicht nötig wäre. Das ist kompletter Murks.

Man merkt, daß Du bislang nur Mini-Wegwerfprojekte geschrieben hast - 
als Softwerker muß man auch bestehende Codebasen anderer übernehmen, und 
nach dem ersten Mißvergnügen mit so einer Schrott-Codebasis will man 
sowas nicht nochmal durchmachen müssen.

Es geht nicht um Dich und auch nicht darum, ob der Compiler das frißt. 
Es geht um die Entwickler nach Dir. Daran zu denken ist eine der 
elementaren Überlegungen in professioneller Software-Entwicklung.

von EAF (Gast)


Lesenswert?

Falk B. schrieb:
> Stimmt, habe ich aber noch nie gemacht ;-)

Womit wir an dem Punkt wären, dass deine Verfahren evtl. für dich genehm 
sein mögen, aber sich eher nicht verallgemeinern lassen.

von Bernd K. (bernd_k97)


Angehängte Dateien:

Lesenswert?

Gibt es da eigentlich ein empfehlenswertes Buch, welches das Thema "Gute 
Programmierpraxis" jenseits der Basics sowie die hier diskutierte Themen 
abhandelt?

Ich hab den Eindruck, dass viele das Rad neu erfinden. Ich mache z.B. 
extensiven Gebrauch von kleinen gekapselten State-Machines. Liebgewonnen 
habe ich die durch LabView's "Globale Funktionale Variablen". (Beispiel 
im Anhang)
Dadurch braucht man kaum noch globale Variablen.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Nop schrieb:
>> Was zum Geier ist daran unwartbar?
>
> Daß jeder von überall her auf jede globale Variable zugreifen kann, auch
> wenn das nicht nötig wäre. Das ist kompletter Murks.

Jaja.

> Man merkt, daß Du bislang nur Mini-Wegwerfprojekte geschrieben hast -

Sagt der Names- und gesichtslose NOP.

> als Softwerker muß man auch bestehende Codebasen anderer übernehmen, und
> nach dem ersten Mißvergnügen mit so einer Schrott-Codebasis will man
> sowas nicht nochmal durchmachen müssen.
>
> Es geht nicht um Dich und auch nicht darum, ob der Compiler das frißt.
> Es geht um die Entwickler nach Dir. Daran zu denken ist eine der
> elementaren Überlegungen in professioneller Software-Entwicklung.

Stimmt. Aber keine Bange, das tu ich, auch wenn da nach mir nicht soo 
wirklich viel dran rumgeschraubt werden wird. Sind zu 95% nur kleine Ein 
Mann Projekte, sagte ich schon. Da muss man nicht den ganzen Zirkus der 
Softwarteentwicklung durchexerzieren. Klein aber fein.

von Falk B. (falk)


Lesenswert?

EAF schrieb:
>> Stimmt, habe ich aber noch nie gemacht ;-)
>
> Womit wir an dem Punkt wären, dass deine Verfahren evtl. für dich genehm
> sein mögen, aber sich eher nicht verallgemeinern lassen.

Hab ich gar nicht vor.

von Nop (Gast)


Lesenswert?

Falk B. schrieb:

> Sagt der Names- und gesichtslose NOP.

Wer etwas sagt, spielt keine Rolle dafür, ob es stimmt - und daß Du Dich 
statt des Inhalts jetzt auf sowas konzentrierst, disqualifiziert Dich 
bloß noch mehr.

> Klein aber

... Murks. Und da Du offensichtlich auch nicht lernwillig bist, sondern 
weiterhin Murks fabrizieren möchtest, ist eine weitere Diskussion bei 
soviel geballter Ignoranz Deinerseits wohl auch nicht sinnvoll.

von Falk B. (falk)


Lesenswert?

Nop schrieb:
>> Sagt der Names- und gesichtslose NOP.
>
> Wer etwas sagt, spielt keine Rolle dafür, ob es stimmt -

Stimmt. Aber ich frag ja auch nich den Bäcker, wenn der Wasserhahn 
tropft.

> und daß Du Dich
> statt des Inhalts jetzt auf sowas konzentrierst, disqualifiziert Dich
> bloß noch mehr.

Uhhhh, ich bin tief getroffen!

>> Klein aber
>
> ... Murks. Und da Du offensichtlich auch nicht lernwillig bist,

Wer sagt, daß du definierst was richtig und falsch ist? Und vor allen in 
allen Lebenslagen und Anwendungsbreiten? Mach deinem Namen alle Ehe und 
lass es gut sein, du Oberlehrer.

> sondern
> weiterhin Murks fabrizieren möchtest, ist eine weitere Diskussion bei
> soviel geballter Ignoranz Deinerseits wohl auch nicht sinnvoll.

Und schon wieder erzittere ich in geballter Erfurcht vor dem Inhaber des 
Steins der Weisen. Schönen Abend noch!

von Wilhelm M. (wimalopaan)


Lesenswert?

Stefan F. schrieb:
> Jan W. schrieb:
>> Müsste overrun nicht volatile sein?
>
> Wenn es nur innerhalb der ISR verwendet wird, dann nicht.

Nein, niemals.

Die `volatile`-Qualifizierung wird nur für sog. besondere
Speicherzellen benötigt.
Sie wird nie benötigt und ist auch falsch für normale Speicherzellen
im Sinne
des C/C++-Speichermodells: also auch nicht für Variablen mit
nebenläufigem Zugriff.
Alle Howtos und Tutorials, die derartiges behaupten, sind schlicht
falsch.

In C/C++ ist es grundsätzlich UB, wenn nebenläufig (mit zwei oder mehr
Aktivitätsträger oder
auch `main()` und `ISR`) auf dieselben Speicherzellen zugegriffen
wird, sofern nicht

* atomare Operationen, oder
* eine strenge happens-before Beziehung

garantiert wird.

`volatile` bedeutet nicht atomar!

'volatile' etabliert auch keine strenge happens-before Relation
zwischen konkurrierenden
Zugriffen auf dieselbe Spiecherzelle. Dies kann man nur mit geeigneten
Synchronisationsprimitiven
wie `mutex` erreichen (zwei oder mehr Aktivitätsträger), oder aber mit
mit anderen ausreichenden
Mitteln wie Interrupt-Sperre zusammen mit einer Memory-Barrier (sowohl
Compiler- als auch
CPU-Memory-Barrier). Das letztere ist dann eine implementation-defined
Variante.

Daher: `volatile` ist nicht-geeignet und - allein eingesetzt - falsch
für nebenfäufigen Zugriff auf
dieselben Objekte. In C/C++ heisst das dann ein conflict, der wie oben
gelöst werden muss, um nicht
in UB zu enden.

`volatile` hat die folgenden Eigenschaften:

* kein atomarer Zugriff,
* Verschiebung einer Lese-Operation einer normalen Speicherzelle bzgl.
`volatile` ist möglich,
* Verschiebung von Operationen auf `volatile` untereinander ist nicht
möglich.

Damit eignet sich `volatile` nur für Operationen auf memory-mapped
HW-Registern (und ist auch nur
genau dafür erfunden worden). Denn diese besonderen Speicherzellen
haben folgende Eigenschaften:

* sie haben einen Seiteneffekt, und
* ihre Werte erscheinen nicht stabil: ein Lesen nach einem Schreiben
muss nicht denselben Wert ergeben
wie auch zwei aufeinander folgende Lese-Operationen nicht denselben Wert
ergeben müssen, und
* sie haben eine semantische Abhängigkeit: das Lesen/Schreiben eines
HW-Registers beeinflusst das
Lesen/Schreiben eines anderen HW-Registers.

(Achtung: in anderen Sprachen als C/C++ wie etwa Java hat `volatile`
eine andere Bedeutung.)

Für den nebenläufigen Zugriff auf dieselben Variablen / Datenstrukturen
bleiben also nur

* atomare Datentypen (`_Atomic` bzw. `std::atomic<>`), oder
* explizite Synchronisation durch:
- `pthread_mutex_lock()`/ `pthread_mutex_unlock()` oder `std::mutex`
oder ähnliche, oder
- explizites Abschalten der Nebenläufigkeit zusammen mit einer
Memory-Barrier

Für die Kommunikation zwischen `ISR` und `main()` bzw. weiteren `ISR`
benutzt man daher
eine geeignete Interrupt-Sperre (Abschalten der Nebenläufigkeit) und
eine Memory-Barrier (immer
eine Compiler-Barrier und falls nötig eine CPU-Barrier). `volatile` ist
aus den o.g. Gründen hier
falsch.

Diese ganzen Betrachtungen gelten generell und haben erstmal gar nichts
mit heftigen Optimierungen
eines Compilers zu tun. Aber natürlich werden bestimmte Effekte bei der
Optimierung und damit
der Ausnutzung der Regeln für normale Speicherzellen besonders
sichtbar.

Im übrigen bedeutet `_Atomic` oder `std::atomic<>` nicht, dass Operation
nicht optimiert werden:
Auch manche Operationen bzgl. atomarer Datentypen können zusammengefasst
werden wie etwa
aufeinanderfolgende Schreiboperationen. Nur tun das die meisten Compiler
(derzeit) noch nicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

EAF schrieb:
> Wilhelm M. schrieb:
>> Das richtige Instrument im Zusammenhang mit Nebenläufigkeit durch ISRs
>> ist eine memory-barrier.
>
> Bitte korrigiere mich, wenn ich falsch liege....
>
> Aber:
> Meines bisherigen Wissens nach, wirkt sich eine MB auf ALLE Variablen
> aus, welche gerade in Registern lagern.

Nein. So dumm ist der Compiler nicht.
Eine solche globale MB wie durch das asm-Statement in sei()/cli() bzw. 
_MemoryBarrier() wirkt sich auf alle Objekte aus, deren Adresse den 
aktuellen Scope verlassen haben kann. Nennt sich im Jargon der 
Compiler-Bauer "escape analysis"

> Volatile nur auf die "eine".

Sicher. Aber eben dann auf jeden Zugriff und verhindert alle(!) 
Optimierungen bzgl. dieser Variable, auch die loads und kommt dem Aufruf 
einer non-inline-Funktion gleich.

von Jan W. (jan_woj)


Lesenswert?

Jetzt bin ich verwirrt, der eine sagt : volatile ist völlig falsch an 
dieser Stelle, der andere sagt : volatile + ATOMIC_BLOCK(?)

Ich finde die Diskussion sehr interessant, auch die andren Threads zu 
diesem Thema, lass uns bitte dabei sachlich bleiben.

Könntet ihr ein einfaches AVR-GCC Beispiel liefern für :

Schreiben + lesen einer Variable in einer interrupt routine und in main.

-Spielt es eine Rolle ob es ein 8-Bit oder >8-Bit Variable ist?

-Müssen wir an dieser Stelle zwischen den AVR-GCC Versionen 
unterscheiden?

Es wäre schön wenn wir dort Einigkeit erreichen würden.
Dieses oder ähnliches Konstrukt benutzt nämlichffast jeder von uns.

Ich kann mich einmal errinern (es war gefühlt vor ca. 10 Jahren ) 
volatile vergessen zu haben. Es hat dan nicht funktioniert. Main hat 
nicht mitbekommen das ein Interrupt eine Variable verändert hat.
Ansonsten hatte ich nie Probleme mit volatile beobachtet in meinem 
Projekten (soll nicht heißen das meine Lösung richtig ist und immer 
funktioniert) . Ich habe bis jetzt immer eine 8-Bit Variable verwendet.

Lass uns bitte auf AVR-GCC beschränken.


Gruss,
Jan

von Falk B. (falk)


Lesenswert?

Jan W. schrieb:
> Schreiben + lesen einer Variable in einer interrupt routine und in main.

Siehe Interrupt.

> -Spielt es eine Rolle ob es ein 8-Bit oder >8-Bit Variable ist?

Ja.

> -Müssen wir an dieser Stelle zwischen den AVR-GCC Versionen
> unterscheiden?

Nein.

von Εrnst B. (ernst)


Lesenswert?

Jan W. schrieb:
> Jetzt bin ich verwirrt, der eine sagt : volatile ist völlig falsch an
> dieser Stelle, der andere sagt : volatile + ATOMIC_BLOCK(?)

"Volatile" wird halt den Anfängern gerne hingeworfen, weil es erstmal 
das Problem fixed. Zwar auf eine unschöne Holzhammer-Methode, aber es 
tut.
Und ist immer noch besser als die Vorschlaghammer-Methode mit "Stell den 
Compiler auf -O0".

Ist also eine Bequemlichkeits-Sache auf Seiten der Tipp-Geber und 
Tutorial-Verfasser.
"Schreib volatile davor" sind drei Worte, eine Diskussion über 
Sprach-Freiheiten des C-Standards, Memory-Barriers, Compiler-Barriers, 
Atomic-Blocks usw. füllt viele viele Seiten, wie man hier sieht.

Also, wenn du bei "volatile" bleiben willst, tu das, aber behalte im 
Hinterkopf dass es eben nicht die "pure, korrekte, schöne" Lösung ist, 
sondern eben ein quick&dirty Workaround.

von Sebastian (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Nein, niemals.

Das ist falsch.

Wilhelm, ich bewundere den Evangelismus, mit dem du hier die große Welt 
der Möglichkeiten zur Kommunikation zwischen nebenläufigen Aktoren 
predigst, und finde vieles davon auch sehr interessant.

Aber auf einem Atmega ist ein volatile uint8_t eine garantiert sichere 
Methode, um zwischen ISR und main zu kommunizieren. Da beißt die Maus 
keinen Faden ab.

Und insofern ist "niemals" schlicht falsch.

LG, Sebastian

von Wilhelm M. (wimalopaan)


Lesenswert?

Jan W. schrieb:
> Jetzt bin ich verwirrt, der eine sagt : volatile ist völlig falsch an
> dieser Stelle, der andere sagt : volatile + ATOMIC_BLOCK(?)

Niemals "volatile" dafür.
Wie schon gesagt: "volatile" erfüllt nicht die Garantien, die wir für 
nebenläufigen Zugriff benötigen.

Was wir brauchen in diesem einfachen AVR Szenario ist:

- Abschalten der Nebenläufigkeit
- Memory-Barrier

> Könntet ihr ein einfaches AVR-GCC Beispiel liefern für :

Beispiel in C:
1
#include <stdbool.h>
2
#include <avr/interrupt.h>
3
4
static int v;  
5
6
void isr() __asm__("__vector_5") __attribute__ ((__signal__, __used__, __externally_visible__)); 
7
void isr() {
8
    v += 1;
9
}
10
11
int main(void) {
12
    while(true) {
13
        // KA Anfang (wegen nicht-atomarer Operationen auf DT `int`)
14
        cli(); // beinhaltet notwendigerweise eine memory-barrier, damit die Operationen des KA nicht nach außen verschoben werden können.
15
        if (v == 42) {  
16
            v += 1; 
17
        }
18
        sei(); // beinhaltet notwendigerweise eine memory-barrier, damit die Operationen des KA nicht nach außen verschoben werden können.
19
        // KA Ende
20
    }    
21
}

Dasselbe in C++
1
#include <avr/interrupt.h>
2
#include <atomic>
3
#include <avr/memory.h>
4
5
//[main
6
namespace {
7
    int v; // <> Übersetzungseinheit-globale Variable `v` wird nebenläufig, gemeinsam genutzt von `isr()` und `main()`.
8
}
9
10
void isr() 
11
__asm__("__vector_5") 
12
__attribute__ ((__signal__, __used__, __externally_visible__)); 
13
void isr() {
14
    v += 1; 
15
}
16
17
int main() {
18
    while(true) {
19
        Atomic::DisableInterruptsRestore di; 
20
        if (v == 42) {
21
            v += 1; 
22
        }
23
    }    
24
}
25
//]

Im obigen Beispiel benötigen wir das Abschalten der Nebenläufigkeit in 
main() wegen des nicht-atomaren Zugriffs auf größere DT als Byte-Typen 
bei AVR.

Zudem besteht hier ggf. ein semantisches Problem: stellt das gesamte 
if-statement einen "kritschen Abschnitt" dar? (Typischerweise würde man 
das natürlich in einer Funktion kapseln). In diesem Beispiel soll das so 
sein. Daher kann der Optimizer das "v += 1" durch ein "v = 43" ersetzen. 
Dies ist so absolut korrekt. Fügen wir fälschicherweise ein "volatile" 
bei der Definition der Variablen "v" ein, so erfolgen sowohl im 
Bedingungsteil des if() ein Lesen von v und auch danach noch ein RMW 
Zyklus.

Die sei() und cli() Macros bei AVR beinhalten auch eine Memory-Barrier. 
Dies ist auch nötig, damit Anweisungen nicht aus dem Inneren des KA 
heraus gezogen werden können.

Ein Memory-Barrier hat denselbe Effekt wie der Aufruf einer 
non-inline-Funktion, denn auch dann kann der Compiler nicht mehr davon 
ausgehen, dass "v" stabil bleibt.

Bei den alten AVR ist in der ISR auch keine weitere Maßnahme mehr nötig. 
Bei den neueren gibt es nested-interrupts, und ggf. muss man dann auch 
in den ISRs wieder kritische Abschnitte einfügen.

> Schreiben + lesen einer Variable in einer interrupt routine und in main.

s.o.

> -Spielt es eine Rolle ob es ein 8-Bit oder >8-Bit Variable ist?

Ja, der Zugriff muss atomar erfolgen. Dies ist bei AVR8 nur für 8-Bit 
primitiven DT gegeben.

> -Müssen wir an dieser Stelle zwischen den AVR-GCC Versionen
> unterscheiden?

Nein, das hat weder was mit AVR noch mit gcc zu tun. Es ist ganz normal 
nebenläufiges Programmieren.

> Es wäre schön wenn wir dort Einigkeit erreichen würden.
> Dieses oder ähnliches Konstrukt benutzt nämlichffast jeder von uns.

Wie gesagt: die meisten HowTos sind in diesem Punkt einfach schlicht 
falsch.

von Wilhelm M. (wimalopaan)


Lesenswert?

Sebastian schrieb:
> Wilhelm M. schrieb:
>> Nein, niemals.
>
> Das ist falsch.

Nein.

> Wilhelm, ich bewundere den Evangelismus, mit dem du hier die große Welt
> der Möglichkeiten zur Kommunikation zwischen nebenläufigen Aktoren
> predigst,

Es ist keine Predigt und ich bin kein Evangelist: es sind schlicht 
Grundlagen.

> Aber auf einem Atmega ist ein volatile uint8_t eine garantiert sichere
> Methode, um zwischen ISR und main zu kommunizieren.

Es ist eine Methode, um

- garantiert bei einer Modifikation des Codes auf die Nase zu fallen, 
weil es ein Spezialfall ist.

- garantiert den Optimizer deaktiviert, an wir uns doch so schön gewöhnt 
haben.

> Und insofern ist "niemals" schlicht falsch.

Es ist schlicht immer vollkommen unnötigt und niemals dafür vorgesehen 
gewesen. Daher ist es einfach falsch, auch wenn es unter speziellen 
Annahmen zufälligerweise funktioniert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Oben habe ich vergessen zu sagen, dass das Convenience-Macro ATOMC-BLOCK 
für das C Beispiel dasselbe ist wie die RAII-Style Verriegelung (inkl. 
MB) im C++ Beispiel.

von Εrnst B. (ernst)


Lesenswert?

EAF schrieb:
> Meines bisherigen Wissens nach, wirkt sich eine MB auf ALLE Variablen
> aus, welche gerade in Registern lagern.
> Volatile nur auf die "eine".

Du kannst ja mal einen Vergleich konstruieren.
1
__asm__ __volatile__ ("" : : : "memory");

macht die Optimization Barrier auf alles im RAM,
1
uint8_t flag;
2
...
3
__asm__ __volatile__ ("" : "=m"(flag) );

macht das nur für eine einzige Variable.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Bernd K. schrieb:
> Liebgewonnen
> habe ich die durch LabView's "Globale Funktionale Variablen". (Beispiel
> im Anhang)
> Dadurch braucht man kaum noch globale Variablen.

Dafür hast Du dann in Deinem Code zustandsbehaftete Funktionen (also 
reinen keine Funktionen mehr), was ihn auch nicht besser macht ...

von Wilhelm M. (wimalopaan)


Lesenswert?

Εrnst B. schrieb:
> EAF schrieb:
>> Meines bisherigen Wissens nach, wirkt sich eine MB auf ALLE Variablen
>> aus, welche gerade in Registern lagern.
>> Volatile nur auf die "eine".
>
> Du kannst ja mal einen Vergleich konstruieren.
>
>
1
> __asm__ __volatile__ ("" : : : "memory");
2
>
>
> macht die Optimization Barrier auf alles im RAM,

Nein.
Lokale Variablen liegen auch im RAM.
Eine globale MB wirkt sich nur auf die Objekte aus, deren Adresse aus 
dem lokalen Block, in dem die MB steht, "entweichen" kann. Dies ist 
natürlich bei globalen Objekten per-definitionem so.

von Εrnst B. (ernst)


Lesenswert?

Wilhelm M. schrieb:
> Nein.

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
1
The "memory" clobber tells the compiler that the assembly code performs memory reads or writes to items other than those listed in the input and output operands (for example, accessing the memory pointed to by one of the input parameters). To ensure memory contains correct values, GCC may need to flush specific register values to memory before executing the asm. Further, the compiler does not assume that any values read from memory before an asm remain unchanged after that asm; it reloads them as needed. Using the "memory" clobber effectively forms a read/write memory barrier for the compiler.

von Sebastian W. (wangnick)


Lesenswert?

Wilhelm M. schrieb:
> auch wenn es unter speziellen
> Annahmen zufälligerweise funktioniert

Unter diesen speziellen Annahmen (Atmega, volatile, uint8_t) 
funktioniert es zufälligerweise immer. Insofern danke für deine 
Bestätigung dass "Nein, niemals." überzogen war.

Ich gebe die recht, dass "volatile" Optimierung verhindert und zu 
unnötigen RMWs führt. Wenn man "volatile" so benutzt solle man daher 
innerhalb und außerhalb von ISRs, falls nötig, möglichst mit Kopien 
solcher Variablen arbeiten. Das ist natürlich etwas unschön, weil der 
unerfahrene Leser den Grund dafür eventuell nicht sofort erkennt.

Andererseits hat dieses _asm__ __volatile_ ("" : "=m"(flag) ) 
Konstrukt einfach zu viele Unterstriche um schön zu sein ... :)

LG, Sebastian

von Wilhelm M. (wimalopaan)


Lesenswert?

Sebastian W. schrieb:
> Ich gebe die recht, dass "volatile" Optimierung verhindert und zu
> unnötigen RMWs führt.

Nicht nur zu unnötigen RMWs, sondern jeder Zugriff (load/store) wird 
materialisiert!

von Jan W. (jan_woj)


Lesenswert?

Danke für euren input.
Eine Frage noch., verstehe ich es richtig das volatile und uint8_t auf 
einem Atmega eine legitime Lösung ist?

Abgesehen davon das es eine Holzhamer Methode ist, die nicht optimal 
ist.

(mit legitim meine ich das der Datenaustausch zwischen Main und 
Interrupt routine immer funktioniert)

Ich habe es nämlich oft so gelöst und kann es bei vielen Projekten nicht 
mehr umstellen.

In der Zukunft würde ich den vom Wilhelm vorgeschlagenen Weg nutzen.

Gruss,
Jan

von Wilhelm M. (wimalopaan)


Lesenswert?

Jan W. schrieb:

> Eine Frage noch., verstehe ich es richtig das volatile und uint8_t auf
> einem Atmega eine legitime Lösung ist?
>
> Abgesehen davon das es eine Holzhamer Methode ist, die nicht optimal
> ist.
>
> (mit legitim meine ich das der Datenaustausch zwischen Main und
> Interrupt routine immer funktioniert)

Jein.
Du hast nach wie vor RMW-Zyklen, denn ein
1
volatile uint8_t v;
2
3
void f() {
4
   v += 1;
5
}

ist nicht-atomar, so dass Du ggf. ein lost-update hast.

von A. S. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Du hast nach wie vor RMW-Zyklen, denn ein
> volatile uint8_t v;
> void f() {
>    v += 1;
> }
>
> ist nicht-atomar, so dass Du ggf. ein lost-update hast.

Wobei das i.d.R. nur gefährlich ist, wenn 2 verschiedenen 
Tasks/Interrupts += machen.

 * wenn nur an einer Stelle auf v geschrieben wird, ist das OK.
 * wenn eine andere Stelle v neu setzt, ist ungewiss, wer "gewinnt".

Nur wenn 2 stellen += (-=, ++, --, |= ...) machen (genauer: v abhängig 
von seinem Wert neu schreiben), kann ein Schritt "verschluckt" werden.

--> Ein ++ (oder +=1) geht nicht als Flag oder Semaphore. Auch nicht 
"if(v==0){v = 1; ...}" an 2 stellen

von Wilhelm M. (wimalopaan)


Lesenswert?

Stefan F. schrieb:
> b) Die Memory Barrier (also das Sperren von Interrupts) verhindert, dass
> der Interrupt dazwischen "funkt".

Schwachsinn: memory-barrier bedeutet nicht das Sperren von Interrupts.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wilhelm M. schrieb:
> Jan W. schrieb:
>
>> Eine Frage noch., verstehe ich es richtig das volatile und uint8_t auf
>> einem Atmega eine legitime Lösung ist?
>>
>> Abgesehen davon das es eine Holzhamer Methode ist, die nicht optimal
>> ist.
>>
>> (mit legitim meine ich das der Datenaustausch zwischen Main und
>> Interrupt routine immer funktioniert)
>
> Jein.
> Du hast nach wie vor RMW-Zyklen, denn ein
>
>
1
> volatile uint8_t v;
2
> 
3
> void f() {
4
>    v += 1;
5
> }
6
>
>
> ist nicht-atomar, so dass Du ggf. ein lost-update hast.

Ich habe das missverständlich geschrieben!

Gemeint war, das es in der Praxis meistens so funktioniert (mir ist 
kein Gegenbeispiel bekannt), wobei das lost-update-Problem natürlich 
immanent ist.

Im Sinne eines language-lawyer muss man natürlich sagen, das auch auf 
dem simplen AVR ohne eine ISR-Sperre keine happens-before Relation 
zwischen f() und ISR() gerantiert werden kann. Daher muss man die 
gemeinsame Variable v als _Atomic (in C), std::atomic (in C++) 
deklarieren, andernfalls ist es formal undefined-behaviour. Leider sind 
die internen Hilfsfunktionen für _Atomic im avr-gcc nicht implementiert. 
In C++ kann man sich natürlich auf Basis der zuvor von mir genannten 
Randbedingungen leicht eine Klasse/Template std::atomic schreiben.
Oder man setzt ISRs und Signal-Handler gleich (das steht so im C 
Standard natürlich nicht drin) und stellt fest, dass main() und die 
ISR() durch denselben(!) Aktivitätsträger ausgeführt werden, dann müsste 
der Typ sig_atomic_t (ebenfalls nicht für avr realisiert, aber 
_SIG_ATOMIC_TYPE_) verwendet werden. Ansonsten ist das Ergebnis 
unspecified-behaviour.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Wilhelm M. schrieb:
> Ich habe das missverständlich geschrieben!
>
> Gemeint war, das es in der Praxis meistens so funktioniert (mir ist
> kein Gegenbeispiel bekannt), wobei das lost-update-Problem natürlich
> immanent ist.

Was aber gerade gefährlich ist, denn solche Fehler sind EXTREM schlecht 
reproduzierbar, treten extrem selten auf und können doch im Extremfall 
viel Schaden machen! Der Software- und Konzeptfehler des Therac 25 
sollte ALLEN Soft- und Hardwerkern ein mahnendes Beispiel sein!

https://de.wikipedia.org/wiki/Therac-25

von Wilhelm M. (wimalopaan)


Lesenswert?

Falk B. schrieb:
> Wilhelm M. schrieb:
>> Ich habe das missverständlich geschrieben!
>>
>> Gemeint war, das es in der Praxis meistens so funktioniert (mir ist
>> kein Gegenbeispiel bekannt), wobei das lost-update-Problem natürlich
>> immanent ist.
>
> Was aber gerade gefährlich ist, denn solche Fehler sind EXTREM schlecht
> reproduzierbar, treten extrem selten auf und können doch im Extremfall
> viel Schaden machen!

Das brauchst Du mir nicht zu sagen!

Er hat gefragt, ob es als Holzhammer funktioniert bei AVR8. Das es nicht 
korrekt ist, habe ich ja schon zu Hauf geschrieben.

von Falk B. (falk)


Lesenswert?

Wilhelm M. schrieb:
>> Was aber gerade gefährlich ist, denn solche Fehler sind EXTREM schlecht
>> reproduzierbar, treten extrem selten auf und können doch im Extremfall
>> viel Schaden machen!
>
> Das brauchst Du mir nicht zu sagen!

Das war nicht explizit an dich gerichtet, mehr so als allgemeine 
Feststellung.

von Stefan F. (Gast)


Lesenswert?

Jan W. schrieb:
> Könntet ihr ein einfaches AVR-GCC Beispiel liefern für :

Wie siehst ist das Thema nicht einfach zu erkläüren. Erklärungsversuche 
gab es genug.

> Spielt es eine Rolle ob es ein 8-Bit oder >8-Bit Variable ist?

Ja, auch das wurde bereits erklärt.

> Müssen wir an dieser Stelle zwischen den AVR-GCC Versionen unterscheiden?

Nein, wenn wir mal Versionen auslassen, die älter als 23 Jahre sind.

> Ich kann mich einmal errinern (es war gefühlt vor ca. 10 Jahren )
> volatile vergessen zu haben. Es hat dan nicht funktioniert. Main hat
> nicht mitbekommen das ein Interrupt eine Variable verändert hat.

Eben dafür deklariert man sie als volatile. Die Variable ist 
"unbeständig", der Compiler muss davon ausgehen, dass sie jederzeit 
verändert wird. Volatile verbietet dem Compiler gewisse Optimierungen, 
die davon ausgehen, dass sonst niemand anderes auf die Variable 
Zugreift.

Wenn diese Variable so groß ist, dass die CPU sie nicht in einem Rutsch 
(atomar) lesen oder schreiben kann, dann musst du Interrupts sperren, 
damit sie den Zugriff nicht unterbrechen können und dabei den Inhalt 
verändern.

Stelle dir einen 16 Bit Zähler vor, der gerade den Wert 255 (0x00ff). 
Die CPU liest zuerst das höherwertige Byte als 0x00. Dann stört ein 
Interrupt und inkremetiert die Variable auf 256 (0x0100). Nun liest die 
CPU das niederwertige Byte aus, also 0x00.

Die CPU hat nun den effektiv Wert 0 (0x0000) gelesen, was völlig falsch 
ist. Richtig wäre entweder 255 (0x00ff) oder 256 (0x0100).

von Jan W. (jan_woj)


Lesenswert?

Vielen Dank für Eure wertvollen Erklärungen,
Ich sehe, es ist nicht ganz so einfach, vor allem wenn man es 
wasserdicht machen möchte.

Stefan F. schrieb:
> Eben dafür deklariert man sie als volatile. Die Variable ist
> "unbeständig", der Compiler muss davon ausgehen, dass sie jederzeit
> verändert wird. Volatile verbietet dem Compiler gewisse Optimierungen,
> die davon ausgehen, dass sonst niemand anderes auf die Variable
> Zugreift.

Mit volatile markiert man 'Daten' als flüchtig, und hindert den 
compilier an dieser Stelle zu optimieren. Mann sieht z.B. volatile immer 
in header Dateien wo hardware (ports, etc) an Adressen 'gebunden' wird.
Oder bei c#, dort wird es in Zusammenhang mit multithreading verwendet., 
das ist an dieser Stelle aber anders als bei C.

Bei C, hat es aber in Zusammenhang mit interrupts nichts zu suchen, zu 
mindestens so verstehe ich Wilhelm. In seinen Beispielen ist kein 
volatile zu sehen, dafür benutzt er cli() und sei() welche die MB 
beinhalten. Verstehe ich es richtig das durch die Verwendung von cli() 
der compiler gezwungen wird die Variable aus dem sram zu lesen? Es 
verbietet ihm ein temporere kopie im register zu verwenden.
Anstatt von cli(), sei() kann das Makro ATOMIC_BLOCK() verwendet werden.

Ich glaube und hoffe es verstanden zu haben :-)
Danke allen.
Gruss,
Jan

von Stefan F. (Gast)


Lesenswert?

Jan W. schrieb:
> Bei C, hat es aber in Zusammenhang mit interrupts nichts zu suchen

Falsch. Nochmal wiederhole ich die Erklärung aber nicht. Du kannst 
diesen Threads ja im laufen der nächsten Monate 20x durchlesen, dann 
macht es vielleicht "klick".

Jan W. schrieb:
> Oder bei c#, dort wird es in Zusammenhang mit multithreading verwendet.,
> das ist an dieser Stelle aber anders als bei C.

Wie kommst du jetzt plötzlich auf C#? Willst du uns verarschen?

> Verstehe ich es richtig das durch die Verwendung von cli()
> der compiler gezwungen wird die Variable aus dem sram zu lesen?

Ja, weil das eine Memory Barrier beinhaltet. Siehe Quelltext der 
interrupt.h:
1
# define cli()  __asm__ __volatile__ ("cli" ::: "memory")

> Anstatt von cli(), sei() kann das Makro ATOMIC_BLOCK() verwendet werden.

von Oliver S. (oliverso)


Lesenswert?

Stefan F. schrieb:
> Jan W. schrieb:
>> Bei C, hat es aber in Zusammenhang mit interrupts nichts zu suchen
>
> Falsch. Nochmal wiederhole ich die Erklärung aber nicht. Du kannst
> diesen Threads ja im laufen der nächsten Monate 20x durchlesen, dann
> macht es vielleicht "klick".

Auch 30 mal durchlesen ändert nichts daran, daß Wilhelm recht damit hat, 
daß volatile dafür offiziell das falsche Werkzeug ist, und du nur in dem 
Sinne, daß es im einfachen Fall eines AVRs in der Praxis trotzdem 
funktioniert.

Oliver

von Stefan F. (Gast)


Lesenswert?

Oliver S. schrieb:
> Auch 30 mal durchlesen ändert nichts daran, daß Wilhelm recht damit hat,
> daß volatile dafür offiziell das falsche Werkzeug ist, und du nur in dem
> Sinne, daß es im einfachen Fall eines AVRs in der Praxis trotzdem
> funktioniert.

Falls es dir nicht aufgefallen ist: Es geht hier um AVR.

Bernd K. schrieb:
> Ich vergaß zu erwähnen, dass es sich um einen ATMega644 handelt.

Unabhängig davon: Siehe und staune, was die Profis im ARM Umfeld 
empfehlen:

https://www.keil.com/pack/doc/CMSIS/Core/html/group__SysTick__gr.html

Das steht kein "volatile" im Beispiel-Code. Das sieht nur so aus.

Hinweis für Jan: Die Variable kann hier ohne Interrupt-Sperre gelesen 
werden, weil das eine 32 Bit CPU ist.

von Wilhelm M. (wimalopaan)


Lesenswert?

Vielleicht wird es anders herum deutlicher.

Hat man etwa
1
void f() {
2
   *uartControlRegister = 0x01; // Uart aktivieren
3
   *uartControlRegister = 0x02; // TX einschalten
4
}

dann darf das nicht zu
1
void f() {
2
   *uartControlRegister = 0x02;
3
}

optimiert werden.
Das würde aber passieren, wenn *uartControlRegister non-volatile wäre.

Auf der anderen Seite möchte man diese Art der Optimierung jedoch für 
"normale" Variablen haben. Daher sind diese non-volatile.

Atomarität erhält man mit Interrupt-Sperre, Konsistenz mit 
memory-barrier.

von Wilhelm M. (wimalopaan)


Lesenswert?

Stefan F. schrieb:
> Unabhängig davon: Siehe und staune, was die Profis im ARM Umfeld
> empfehlen:
>
> https://www.keil.com/pack/doc/CMSIS/Core/html/group__SysTick__gr.html
>
> Das steht kein "volatile" im Beispiel-Code. Das sieht nur so aus.

Ist trotzdem Murks.

> Hinweis für Jan: Die Variable kann hier ohne Interrupt-Sperre gelesen
> werden, weil das eine 32 Bit CPU ist.

Mag sein, dass das für den Keil-Compiler gilt: laut C-Standard ist es 
undefined-behaviour oder maximal unspecified-behaviour (s.a. mein 
Beitrag von oben).

Diese Lücke des unspecified-behaviour kann der Keil-Compiler natürlich 
füllen.

von Stefan F. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Diese Lücke des unspecified-behaviour kann der Keil-Compiler natürlich
> füllen.

Es funktioniert auch mit dem arm-gcc.

von Wilhelm M. (wimalopaan)


Lesenswert?

Jan W. schrieb:
> Bei C, hat es aber in Zusammenhang mit interrupts nichts zu suchen, zu
> mindestens so verstehe ich Wilhelm.

Genau!

> In seinen Beispielen ist kein
> volatile zu sehen, dafür benutzt er cli() und sei() welche die MB
> beinhalten. Verstehe ich es richtig das durch die Verwendung von cli()
> der compiler gezwungen wird die Variable aus dem sram zu lesen? Es
> verbietet ihm ein temporere kopie im register zu verwenden.

Zumindest nicht über die mit cli()&sei() gezogenen Grenzen hinweg.

> Anstatt von cli(), sei() kann das Makro ATOMIC_BLOCK() verwendet werden.

Ja, ist dann wie C++ RAII-Style.

Makroskopisch ist das also richtig ;-) Glückwunsch, Du hast es 
verstanden.

von Maxe (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Daher muss man die
> gemeinsame Variable v als _Atomic (in C), std::atomic (in C++)
> deklarieren, andernfalls ist es formal undefined-behaviour. Leider sind
> die internen Hilfsfunktionen für _Atomic im avr-gcc nicht implementiert.

Was heißt das? Was ist nicht implementiert? Ergeben sich daraus 
Einschränkungen?

von Oliver S. (oliverso)


Lesenswert?

Stefan F. schrieb:
> Unabhängig davon: Siehe und staune, was die Profis im ARM Umfeld
> empfehlen:
>
> https://www.keil.com/pack/doc/CMSIS/Core/html/group__SysTick__gr.html
>
> Das steht kein "volatile" im Beispiel-Code. Das sieht nur so aus.

Ich habe mit im Zusammenhang mit der Ankündigung der fast vollständigen 
Abkündigung ;) von volatile in C++ einige Beispiele angesehen, und dabei 
mehr als nur gestaunt. Zunächst hatte ich das C++-Standardkomitee ja für 
völlig durchgeknallt gehalten, aber was da an Code unterwegs ist, in dem 
volatile zur „Beherrschung“ von Nebenläufigkeit 
(Multithreading/Multitasking) auch und gerade im PC-Umfeld missbraucht 
wird, war selbst für mich als Dilettanten erschreckend.

Der Ansatz: „es ist zwar irgendwie nicht richtig, scheint aber heute und 
jetzt zu funktionieren“ ist niemals gut und schon gar nicht 
professionell.

Oliver

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Oliver S. schrieb:
> Der Ansatz: „es ist zwar irgendwie nicht richtig, scheint aber heute und
> jetzt zu funktionieren“ ist niemals gut und schon gar nicht
> professionell.

Sollen wir lieber das nicht implementierte _Atomic benutzen?

von Jim M. (turboj)


Lesenswert?

Bernd K. schrieb:
> Welche
> Dreck-Effekte können mit der Temperatur zusammenhängen?

Wenn Du keinen Quarz am µC dran hast (und den auch aktiv als Taktquelle 
nutzt) kann Dir bei 60°C der interne Takt soweit daneben liegen das UART 
nicht mehr fehlerfrei funktioniert.

Da kannste dann an der Software schrauben bis Du schwarz wirst.

Oszi müsste das veränderte Timing aber sichtbar machen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> Jan W. schrieb:
>> Jetzt bin ich verwirrt, der eine sagt : volatile ist völlig falsch an
>> dieser Stelle, der andere sagt : volatile + ATOMIC_BLOCK(?)
>
> Niemals "volatile" dafür.

Da ist der Standard aber anderer Meinung, bspw. hier:
1
5.1.2.3 Program execution
2
[…]
3
EXAMPLE 1
4
[…]
5
Alternatively, an implementation might perform various optimizations
6
within each translation unit, such that the actual semantics would agree
7
with the abstract semantics only when making function calls across
8
translation unit boundaries.
9
[…]
10
In this type of implementation, objects referred to by interrupt service
11
routines activated by the signal function would require explicit
12
specification of volatile storage, as well as other implementation-
13
defined restrictions.

und hier:
1
6.7.3 Type qualifiers
2
[…]
3
136) A volatile declaration may be used to describe an object
4
     corresponding to a memory-mapped input/output port or an object
5
     accessed by an asynchronously interrupting function.

Wilhelm M. schrieb:
> Wie schon gesagt: "volatile" erfüllt nicht die Garantien, die wir für
> nebenläufigen Zugriff benötigen.

Es gibt Fälle, wo volatile alleine nicht ausreicht (bspw. bei Objekten,
die keinen lock-freien atomaren Zugriff erlauben), das heißt aber noch
lange nicht, dass volatile grundsätzlich falsch ist.

Ein häufig vorkommendes  Muster für das Interrupthandling auf einem
Mikrocontroller sieht folgendermaßen aus:

Die ISR setzt beim Erreichen eines bestimmten Zustands ein Flag. Das
Vordergrundprogramm fragt zwischen anderen Aktivitäten dieses Flag ab
und reagiert entsprechend darauf. Das Flag ist eine 1-Byte-Variable, so
dass darauf ohne weitere Maßnahmen atomar zugegriffen werden kann.

Auch das Programm des TE fällt in dieses Muster, das Flag heißt dort
uart0_rx_flag.

Hier ist ein anderes Beispiel, an dem sehr schön die Unterschiede
zwischen volatile und einer Memory Barrier gezeigt werden können:

irqtest.c
1
#include <stdint.h>
2
#include <avr/interrupt.h>
3
4
#if VARIANT == 3
5
volatile uint8_t flag;
6
#else
7
uint8_t flag;
8
#endif
9
10
ISR(USART_RXC_vect) {
11
  // Setze unter bestimmtmen Bedingungen ein Flag
12
  if (UDR == 'S')
13
    flag = 1;
14
}
15
16
uint8_t g0, g1, count;
17
18
void func(void) {
19
  for (uint8_t i=0; i<20; i++) {
20
    // Mach etwas Lustiges mit globalen Variablen
21
    g1 += g0++;
22
23
    // Allgemeine Memory Barrier
24
#   if VARIANT == 1
25
      __asm__ __volatile__ ("" : : : "memory");
26
#   endif
27
28
    // Memory Barrier spezifisch für Variable "flag"
29
#   if VARIANT == 2
30
      __asm__ __volatile__ ("" : "=m" (flag));
31
#   endif
32
33
    // Führe jedesmal, wenn das Flag gesetzt wurde, eine Aktion aus
34
    if (flag) {
35
      count++;
36
      flag = 0;
37
    }
38
  }
39
}

Es kann durch Setzen des Makros VARIANT auf 1, 2 oder 3 in drei
Varianten übersetzt werden:
1
for ((v=1; v<=3; v++)) do
2
  avr-gcc -mmcu=atmega8 -Os -DVARIANT=$v -S -o irqtest$v.s irqtest.c
3
done

Variante 1: Allgemeine Memory Barrier
Variante 2: Memory Barrier spezifisch für das Flag
Variante 3: Flag als volatile deklariert

Alle drei Varianten sorgen dafür, dass das von der ISR geschriebene Flag
in Vordergrundprogramm korrekt gelesen und zurückgesetzt wird.

In der Variante 1 wird func wie folgt übersetzt:
1
func:
2
  ldi r24,lo8(20)
3
4
.L5:
5
  lds r18,g0              <--
6
  ldi r25,lo8(1)
7
  add r25,r18
8
  sts g0,r25              <--
9
  lds r25,g1              <--
10
  add r25,r18
11
  sts g1,r25              <--
12
  lds r25,flag            <-
13
  tst r25
14
  breq .L4
15
  lds r25,count           <--
16
  subi r25,lo8(-(1))
17
  sts count,r25           <--
18
  sts flag,__zero_reg__   <--
19
.L4:
20
  subi r24,lo8(-(-1))
21
  cpse r24,__zero_reg__
22
  rjmp .L5
23
24
  ret

Die Memory Barrier sorgt richtigerweise dafür, dass in jedem Durchlauf
der For-Schleife das Flag erneut gelesen wird. Allerdings werden auch
die Variablen g0, g1 und count in jedem Durchlauf jeweils einmal
gelesen und beschrieben, obwohl sie überhaupt nichts mit dem Interrupt
zu tun haben.

In Variante 2 wird die Barrier auf die Variable flag beschränkt,
weswegen ich erwartet hätte, dass die Anzahl der Speicherzugriffe für
die anderen Variablen deutlich reduziert wird. Tatsächlich ist das
Kompilat aber exakt das gleiche wie in Variante 1. Speziell innerhalb
von Schleifen scheinen solche eingeschränkten Barriers denselben
(subopotimalen) Effekt wie allgemeine Barriers zu haben.

In Variante 3 werden keine Memory Barriers verwendet, sondern flag als
volatile deklariert. Der Code wird jetzt sehr stark optimiert:
1
func:
2
  lds r25,g0
3
  lds r24,g1
4
  lds r18,count
5
  ldi r19,lo8(20)
6
7
.L5:
8
  lds r20,flag            <--
9
  tst r20
10
  breq .L4
11
  subi r18,lo8(-(1))
12
  sts flag,__zero_reg__   <--
13
.L4:
14
  subi r19,lo8(-(-1))
15
  cpse r19,__zero_reg__
16
  rjmp .L5
17
18
  sts count,r18
19
  ldi r18,lo8(20)
20
  mul r25,r18
21
  add r24,r0
22
  clr __zero_reg__
23
  subi r24,lo8(-(-66))
24
  sts g1,r24
25
  subi r25,lo8(-(20))
26
  sts g0,r25
27
  ret

Ein großer Teil des Schleifenrumpfs wird jetzt als Invariante nach außen
verlagert, so dass innerhalb der Schleife nur noch zwei Speicherzugriffe
übrig bleiben, nämlich das (zwingend erforderliche) Lesen und Schreiben
von flag.

In dem Beispiel behindert die Memory Barrier die Optimierung massivst,
weil sich ihre Wirkung nicht auf die gewünschte Variable beschränkt,
sondern sich auf alle globalen Variablen erstreckt. Natürlich kann man
sie als volatile-Ersatz verwenden, aber ich würde das eher als Hack
sehen, weil sie eigentlich für ganz andere Dinge gedacht ist.

Auch volatile kann die Optimierung behindern, nämlich dann, wenn auf die
entsprechende Variable mehrfach lesend oder schreibend zugegriffen wird
(auch wenn das beim Interrupt-Handling eher selten vorkommt). Wenn man
nicht möchte, dass dabei jedesmal ein Speicherzugriff erfolgt, arbeitet
man einfach mit einer lokalen Kopie der Variable.

Eine andere Möglichkeit besteht darin, die Variable nicht als volatile
zu deklarieren und sie stattdessen nur an den Stellen, wo man den
Speicherzugriff erzwingen möchte, in volatile umzucasten:
1
  *(volatile uint8_t *)&flag

Mit diesen beiden Vorgehensweisen kann man sehr feingranular festlegen,
wann Speicherzugriffe erzwungen werden sollen und wann man lieber den
Compiler optimieren lassen möchte.


Bei der Entscheidung, welchen Weg man beim Interrupt-Handling gehen
möchte, ist es das Beste, sich nicht so sehr von Aussagen, die die
Wörter "immer" oder "niemals" enthalten, beeinflussen zu lassen, sondern
sich selber vor Augen zu führen, was die entsprechenden Hilfsmittel wie
volatile, Memory Barriers und die Dinge in stdatomic.h (sofern
verfügbar) genau tun, um darauf basierend eine dem jeweiligen Kontext
bestmöglich angepasste Lösung zu finden.

von 900ss (900ss)


Lesenswert?

900ss D. schrieb:
> Wilhelm M. schrieb:
>> Das löst jetzt Dein Problem wahrscheinlich nicht, ist aber trotzdem ein
>> sehr beliebter Fehler,
>
> Durch gebetsmühlenartiger Wiederholung wird es auch kein Fehler. Es ist
> keiner.

Volatile funktioniert doch in allen Fällen, wo eine Variable, egal wie 
breit sie ist, mit einer(!) nicht unterbrechbaren Maschineninstruktion 
gelesen oder geschrieben werden kann (atomar gelesen/geschrieben werden 
kann).
Beispiele: Beim AVR 8 Bit, beim ARM Cortex-M3 32 Bit, beim SPARC V8 32 
Bit. Auch der Z80 mit 8 Bit :)
Zweite Bedingung, die CPU/Compiler macht kein reordering an den 
relevanten Stellen, was ich bei diesen "einfachen" Maschinen bisher 
nicht gesehen habe. Und bei den meisten Fragen hier geht es doch um 
solche einfachen Maschinen.
Deshalb würde ich es pauschal nicht als Fehler bezeichnen, volatile zu 
benutzen.
Zugegeben, die Codeoptimierung funktioniert mit volatile schlechter als 
mit einer Memory Barrier. Aber wenn es in der SW schon so kneift, dass 
diese schlechtere Codeoptimierung zu einem ernsten Problem führt, dann 
ist viel früher schon ein Fehler gemacht worden (HW/SW System design).
Ja, es ist eine Holzhammermethode aber ob es falsch ist, hängt von der 
Architektur ab.

von 900ss (900ss)


Lesenswert?

Yalu X. schrieb:
> Bei der Entscheidung, welchen Weg man beim Interrupt-Handling gehen
> möchte, ist es das Beste, sich nicht so sehr von Aussagen, die die
> Wörter "immer" oder "niemals" enthalten, beeinflussen zu lassen, sondern
> sich selber vor Augen zu führen, was die entsprechenden Hilfsmittel wie
> volatile, Memory Barriers und die Dinge in stdatomic.h (sofern
> verfügbar) genau tun, um darauf basierend eine dem jeweiligen Kontext
> bestmöglich angepasste Lösung zu finden.

Und sich zusätzlich vielleicht noch den jeweils generierten 
Assembler-Output anzuschauen. Dann wird man tatsächlich schlau wie dein 
Beispiel gut zeigt.

von Stefan F. (Gast)


Lesenswert?

900ss D. schrieb:
> Zugegeben, die Codeoptimierung funktioniert mit volatile schlechter als
> mit einer Memory Barrier.

Yalu hat gerade exakt das Gegenteil demonstriert

von 900ss (900ss)


Lesenswert?

900ss D. schrieb:
> Zugegeben, die Codeoptimierung funktioniert mit volatile schlechter als
> mit einer Memory Barrier.

Das muss ich nach dem Beispiel von Yalu
Beitrag "Re: ISR Aussetzer durch Volatile-Variablen-Polling ?"
widerrufen ;)
Ich hatte es auch ausprobiert aber das der Code mit der Memory Barrier 
größer wurde, hatte ich nicht gesehen. :-/

von 900ss (900ss)


Lesenswert?

Stefan F. schrieb:
> Yalu hat gerade exakt das Gegenteil demonstriert

Ja, hab ich damit auch bemerkt, siehe oben ;)

von Sebastian W. (wangnick)


Lesenswert?

Wilhelm M. schrieb:
> Im Sinne eines language-lawyer muss man natürlich sagen, das auch auf
> dem simplen AVR ohne eine ISR-Sperre keine happens-before Relation
> zwischen f() und ISR() gerantiert werden kann. Daher muss man die
> gemeinsame Variable v als _Atomic (in C), std::atomic (in C++)
> deklarieren, andernfalls ist es formal undefined-behaviour.

Wilhelm, du behauptest also ernsthaft, dass folgendes AVR-Beispiel ohne 
eine ISR-Sperre:
1
volatile uint8_t flag = 0, data;
2
ISR(USART_RXC_vect) {
3
    if (!flag) {
4
        data = UDR;
5
        flag = 1;
6
    }
7
}
8
void f () {
9
    if (flag) {
10
        uint8_t mydata = data;
11
        flag = 0;
12
        process(mydata);
13
    }
14
}
laut C Standard undefined-behaviour beinhaltet? Dass also auf die hier 
gezeigte Weise mit volatile uint8_t flag keine 
happens-before-relationship zwischen ISR und f() für den Zugriff auf 
data hergestellt werden kann?

Da würde ich von dem language lawyer jetzt aber gerne mal die 
Paragraphen des C Standards genannt haben, die die Behauptung des 
undefined-behaviour für dieses Beispiel belegen ...

LG, Sebastian

: Bearbeitet durch User
von Maxe (Gast)


Lesenswert?

Yalu X. schrieb:
> Eine andere Möglichkeit besteht darin, die Variable nicht als volatile
> zu deklarieren und sie stattdessen nur an den Stellen, wo man den
> Speicherzugriff erzwingen möchte, in volatile umzucasten:
>   *(volatile uint8_t *)&flag

Wie ist das, wenn ich dem Flag einen Wert zuweisen möchte? Linksseitiges 
Casten geht nicht, oder?

von Yalu X. (yalu) (Moderator)


Lesenswert?

900ss D. schrieb:
> 900ss D. schrieb:
>> Zugegeben, die Codeoptimierung funktioniert mit volatile schlechter als
>> mit einer Memory Barrier.
>
> Das muss ich nach dem Beispiel von Yalu
> Beitrag "Re: ISR Aussetzer durch Volatile-Variablen-Polling ?"
> widerrufen ;)

Das hängt natürlich ganz stark vom Anwendungsfall ab. Ich wollte mit dem
Beispiel nur zeigen, dass Memory Barriers nicht immer die bessere
Lösung sind.

Maxe schrieb:
> Yalu X. schrieb:
>> Eine andere Möglichkeit besteht darin, die Variable nicht als volatile
>> zu deklarieren und sie stattdessen nur an den Stellen, wo man den
>> Speicherzugriff erzwingen möchte, in volatile umzucasten:
>>   *(volatile uint8_t *)&flag
>
> Wie ist das, wenn ich dem Flag einen Wert zuweisen möchte? Linksseitiges
> Casten geht nicht, oder?

Der obige Ausdruck ist ein L-Value, d.h. du kannst ihn links oder rechts
vom Zuweisungsoperator benutzen:
1
uint8_t f;
2
f = *(volatile uint8_t *)&flag;  // lesen
3
*(volatile uint8_t *)&flag = f;  // schreiben

von Maxe (Gast)


Lesenswert?

Danke!

von Stefan F. (Gast)


Lesenswert?

Yalu X. schrieb:
> uint8_t f;
> f = *(volatile uint8_t *)&flag;  // lesen
> *(volatile uint8_t *)&flag = f;  // schreiben

Das schreit regelrecht nach einem Makro.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Ja, solche Makros werden bspw. auch im Linux-Kernel verwendet.

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.