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
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.
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.
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
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.
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.
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.)
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".
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:
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.
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.
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
chardata;
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
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?
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.
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
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.
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.
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_tsreg_save=SREG;
2
cli();
3
__asm__volatile("":::"memory");
4
...waszutunist
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.
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…
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_tglobal_variable=100;
2
3
ISR(TIMER0_OVF_vect){
4
global_variable++;
5
}
6
7
8
for(inti=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
inti=50;
2
3
ISR(TIMER0_OVF_vect){
4
i=256;
5
}
6
7
while(1){
8
if(i==0)tueetwas;
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.
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
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.
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.
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.
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.
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.
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.
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!
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.
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?
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
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ß
Danke Stefan+! Wenn man schon klaut, dann richtig :-)
(das spektakuläre war sogar schon vorgekommen, wenn mal ein ganzer Flash
upload übers Funknetz ging)
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?
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
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.
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.
Falk B. schrieb:>> Wenn es nur innerhalb der ISR verwendet wird, dann nicht.>> Wird sie aber NICHTFalk 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
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.
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.
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.
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.
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!
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.
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.
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
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 ;-)
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.
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.
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.
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.
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.
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.
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!
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.
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.
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
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.
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.
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
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:
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.
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.
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.
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.
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 ...
Ε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.
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.
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
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!
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
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
volatileuint8_tv;
2
3
voidf(){
4
v+=1;
5
}
ist nicht-atomar, so dass Du ggf. ein lost-update hast.
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
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.
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
>volatileuint8_tv;
2
>
3
>voidf(){
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.
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
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.
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.
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).
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
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:
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
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.
Vielleicht wird es anders herum deutlicher.
Hat man etwa
1
voidf(){
2
*uartControlRegister=0x01;// Uart aktivieren
3
*uartControlRegister=0x02;// TX einschalten
4
}
dann darf das nicht zu
1
voidf(){
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.
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.
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.
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?
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
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?
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.
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
volatileuint8_tflag;
6
#else
7
uint8_tflag;
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_tg0,g1,count;
17
18
voidfunc(void){
19
for(uint8_ti=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:
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
*(volatileuint8_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.
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.
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.
900ss D. schrieb:> Zugegeben, die Codeoptimierung funktioniert mit volatile schlechter als> mit einer Memory Barrier.
Yalu hat gerade exakt das Gegenteil demonstriert
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. :-/
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
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?
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: