Hallo zusammen, ich habe timing-Probleme bei einem Programm für den AVR128DB64 (Atmel Studio, GCC) Mit einem 1ms-Timer-Interrupt zähle ich einen Millisekunden-Counter hoch. Alle 1000 ms zähle ich in der Main-Loop einen Sekunden-Counter hoch. Das Problem: Manche der gezählten Sekunden sind kürzer! Ein externer Logic-Analyzer zählt alle etwa 16s nur 768ms. Der anghängte Code enthält folgendes Snippet, das eine LED blinken lässt, wenn der Fehler auftritt. [c] if (ms_counter_ticker > 999) { if (ms_counter_ticker < 999) { LED_red_on(); <- Dieser Abschnitt sollte niemals ausgeführt werden LED_red_off(); } ms_counter_ticker = 0; ms_counter++; } [\c] if (ms_counter >= 1) { LED_red_on(); ms_counter = 0; LED_red_off(); } Mit dem Logic-Analyzer sieht man also bei der roten LED einen Puls, wenn alles richtig läuft und 2 Pulse, wenn der "unmögliche" Code-Abschnitt ausgeführt wird. In der Zeile "grüne LED" wird angezeigt, wann der Interrupt für die ms ausgeführt wird. Ergänzend habe ich den Logic-Analyzer auch die ms-Pulse zählen lassen. Es wirkt, als würde der Wert der Variable ms_counter_ticker zwischen den beiden if-Anweisungen geändert. In dieser Zeit wird aber anscheinend kein Interrupt oder ähnliches ausgeführt (keine grüne LED) Der angehängte Code ist vollständig. Hat jemand eine Idee, wie ich da weiterkommen könnte?
Glaskugel: 16 oder 32-Bit Variable, mglw. volatile, wird in der ISR hochgezählt und in der main() ohne ATOMIC_BLOCK o.Ä. gelesen.
Ohne irgendwas vom Code zu kennen, kann es sein daß hier ein Watchdog Reset regelmäßig zuschlägt?
Zwischen deinen Abfragen if <> 999 kann gut möglich der Interrupt kommen, das siehst du nicht, weil du erst danach LED rot schaltest. Versuch mal ein atomic Zugriff auf deine Variable: Bsp.:
1 | volatile uint32_t sys_tick_ms; |
2 | |
3 | uint32_t sys_tick_get_ms(void) |
4 | {
|
5 | uint32_t ms; |
6 | |
7 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) |
8 | {
|
9 | ms = sys_tick_ms; |
10 | }
|
11 | |
12 | return ms; |
13 | }
|
@Jems (Firma: oA) (timobomm) Oder benötigst du mehr Info? Hast du den Zusammenhang verstanden?
:
Bearbeitet durch User
Adam P. schrieb: > Versuch mal ein atomic Zugriff auf deine Variable: Kleine Kosmetik-Verschönerung: Der ATOMIC_BLOCK funktioniert auch, wenn man ihn mit return verlässt:
1 | uint32_t sys_tick_get_ms(void) { |
2 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { |
3 | return sys_tick_ms; |
4 | }
|
5 | }
|
(erzeugt aber denselben Code, insofern: Wirklich reine Kosmetik) zweite Verschönerung:
1 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { |
2 | if (ms_counter_ticker > 999) { |
3 | ms_counter_ticker -= 1000; |
4 | ms_counter++; |
5 | }
|
6 | }
|
==> wenn die Schleife erst z.B. bei ms 1010 vorbeikommt, gehen die 10 ms nicht verloren, die Sekunden bleiben gleich lang.
Hallo Adam, Hallo Ernst, Vielen Dank erstmal! Adam P. schrieb: > Zwischen deinen Abfragen if <> 999 kann gut möglich der Interrupt Müsste ich das nicht anhand der "grünen LED" im Logic Analyzer sehen?! (es gibt ja nur diesen einen Interrupt) Ich habe mal die erste Funktion von Ernst bei mir eingefügt. Verschönerungen gibt es später ;-)
1 | uint16_t tick = sys_tick_get_ms(); |
2 | if (tick > 999) |
3 | {
|
4 | if (tick < 999) |
5 | {
|
6 | LED_red_on(); |
7 | LED_red_off(); |
8 | }
|
9 | ms_counter_ticker = 0; |
10 | ms_counter++; |
11 | }
|
12 | |
13 | if (ms_counter >= 1) |
14 | {
|
15 | LED_red_on(); |
16 | ms_counter = 0; |
17 | LED_red_off(); |
18 | }
|
Die Variable tick sollte ja jetzt "von außen" nicht mehr verändert werden können. Verschiedene Optimierungsstufe ausprobiert. Der "unmögliche" Teil des Codes wird nicht mehr angesprungen. Das ist allerdings auch der Fall, wenn ich den Wert ms_counter_ticker vorher einer Variable zuweise, anstatt direkt zu vergleichen.
1 | uint16_t tick = ms_counter_ticker; |
2 | if (tick > 999) |
3 | {...
|
Aber es läuft in beiden Versionen immer noch nicht wie gedacht, einige Sekunden sind weiterhin nur 768ms lang. Warum gilt manchmal angeblich auch in dieser Konstellation "768 > 999 = true" ?
Dein tick ist 16bit und dein ms_counter 32bit. Beim Draufspeichern schmeißt du die oberen Bits weg. Mach mal testweise eine 32bit tick Variable und schau obs noch vorkommt.
Jems schrieb: > einige Sekunden sind weiterhin nur 768ms lang. Meine Vermutung: 768 = 2^16 - (32 * 1000)
Jems schrieb: > Aber es läuft in beiden Versionen immer noch nicht wie gedacht Irgendwie hast du da jetzt einiges durcheinander gewürfelt, was die verwendeten Variablen betrifft. Schau mal die Datei im Anhang und probier die aus, hab sie nur zusammengetippt, nicht getestet.
Peter D. schrieb: > Jems schrieb: > >> ms_counter_ticker = 0; > > Das Löschen muß natürlich auch atomar erfolgen. Junge Junge. Was für Umstände in Hochsprache. In Asm wär die Sache schnell klar, viel kürzer und ohne Atomarbimbamborium erledigt gewesen 🙊
Ich würd ja lachen wenns sich doch noch als Compilerfehler rausstellt
Julian R. schrieb: > Was für Umstände in Hochsprache. Nö, man muß es nur verstehen, was da passiert. In ASM muß Du auch atomar klammern, nur heißt es dann "cli, st, st, sei". Ich finde das keineswegs besser lesbar.
Peter D. schrieb: > In ASM muß Du auch atomar klammer Unfug. Man muß es nur richtig anstellen. Hochsprache macht sich viele Probleme selber.
Ralf schrieb: > Unfug. Man muß es nur richtig anstellen. Dann zeige mal.... "Unfug" kann man leicht rufen. Der Nachweis ist da schon schwieriger! Kannst du ihn führen?
Ralf schrieb: > Man muß es nur richtig anstellen. Dann zeig doch mal. 16-Bit Zähler in ISR, Auswertung im Hauptprogramm, "cli" (und dessen SREG-Bitpfriemel-Äquivalent) ist verboten.
> Dann zeig doch mal. 16-Bit Zähler in ISR ...
Vielleicht meint Ralf etwas mit 'adiw' bei reservierten Registern
r24&25.
S. Landolt schrieb: > Vielleicht meint Kann sein... Damit endet es aber fix, wenn z.B. 5 solcher Zähler verwaltet werden sollen. Oder diese Zähler 32Bit breit werden sollen, oder man genau diese Register für andere Zwecke benötigt.
Nun, es stehen ja 32 Arbeitsregister zur Verfügung - irgendetwas wird sich da immer deichseln lassen. Übrigens vergaß ich 'movw', das wird wohl auch benötigt.
Nur so als Idee, vllt. sollten wir den TO nicht mit asm und all dem verwirren, auch wenn es dem Verständnis hilft (aber nicht so) Anscheinend hat er noch nicht verstanden das einzelne C Befehle, mehrere Takte benötigen und somit "Trennbar" sind. So das da ein Interrupt zwuschendrin einschlagen kann. "Längenvergleich" ist hier irgendwie nicht angebracht.
> ... Verständnis ... Okay, dann wollen wir aber präzise formulieren: > einzelne C Befehle, mehrere Takte benötigen und somit "Trennbar" sind Es sind nicht die Takte - es liegt daran, dass sich ein C-Befehl aus mehreren Assembler-Instruktionen zusammensetzen kann.
S. Landolt schrieb: > Okay, dann wollen wir aber präzise formulieren: >> einzelne C Befehle, mehrere Takte benötigen und somit "Trennbar" sind > Es sind nicht die Takte - es liegt daran, dass sich ein C-Befehl aus > mehreren Assembler-Instruktionen zusammensetzen kann. Genau das meine ich und zwischen 2 Assembler-Instruktionen kann nun mal ein Interrupt entstehen. Falls der TO aber keine Ahnung von asm hat, kann es auch einfacher veranschaulicht werden, als nur um asm Optimierung zu diskutieren, oder?
S. Landolt schrieb: > Es sind nicht die Takte - es liegt daran, dass sich ein C-Befehl aus > mehreren Assembler-Instruktionen zusammensetzen kann. Ach und was sind mehrere Assembler-Instruktionen...genau mehr Takte, siehe Takte je ASM Befehl im Datenblatt.
:
Bearbeitet durch User
> ... einfacher veranschaulicht ...
Dann beschreiben Sie die Problematik anschaulich, ohne auf Assembler
zurückzugreifen. Das vermisse ich bislang, und Jerns offenbar auch.
Und auf Ihren letzten Beitrag gehe ich lieber nicht ein: der ist zwar
richtig, geht aber am entscheidenden Punkt vorbei - und dann wären wir
schon wieder bei Assembler.
S. Landolt schrieb: >> ... einfacher veranschaulicht ... > > Dann beschreiben Sie die Problematik anschaulich, ohne auf Assembler > zurückzugreifen. Das vermisse ich bislang, und Jerns offenbar auch. > > Und auf Ihren letzten Beitrag gehe ich lieber nicht ein: der ist zwar > richtig, geht aber am entscheidenden Punkt vorbei - und dann wären wir > schon wieder bei Assembler. Bin ich grad dran... dauert halt ein wenig. edit: falls der TO das überhaupt noch braucht?
:
Bearbeitet durch User
Ich habe durch Eure Ratschläge viel gelernt! Durch sehr kurzzeitige Unterdrückung der Interrupts während ich mir den benötigten Wert aus der Variable hole und in einer anderen speichere, kann diese nicht mehr zwischendurch verändert werden. Ich habe das in dieser Form beherzigt: (schien mir einfach und ich kann es mir für das nächste Mal merken, ohne dass ich danach suchen muss)
1 | cli(); |
2 | uint16_t tick = ms_counter_ticker; |
3 | sei(); |
4 | if (tick > 999) |
5 | {...
|
6 | cli(); |
7 | ms_ticker = 0; |
8 | sei(); |
9 | ...}
|
Scheint zu funktionieren :-) (Ich hatte aber nur sehr kurz Zeit das anzutesten) Die Macros aus der ATOMIC lib werde ich mir natürlich auch mal genauer angucken. Ganz herzlichen Dank an alle für die vielen Erklärungen!
Adam P. schrieb: > Ach und was sind mehrere Assembler-Instruktionen...genau mehr Takte, > siehe Takte je ASM Befehl im Datenblatt. Nicht jeder Assembler-Befehl wird in einem Takt abgearbeitet. Bei asm-Befehlen, die mehrere Takte benötigen, kann, obwohl sie mehrere Takte benötigen, trotzdem niemand dazwischen grätschen.
Die Nutzung von volatile für ms_counter_ticker ist hier unnötig, das verhindert unnötigerweise Optimierungen. volatile sollte nur für Memory-Mapped-I/O eingesetzt werden, also hier für die HW-Register des AVR (s.a. mitgelieferte Header). Denn die Zugriffe auf die HW-Register dürfen nicht durch Optimierungen verändert (e.g. zusammengefasst) werden. Was für eine gemeinsam genutzte Datenstruktur wie ms_counter_ticker benötigt wird ist: - Atomarität für rmw-Operationen, und - Verhinderung von Optimierungen über die Grenzen von kritischen Bereichen (atomarer Block) hinaus. Die einzige Form von Nebenläufigkeit, die es auf AVR gibt, ist die Ausführung von ISRs. Damit kann Atomarität durch Abschalten der Interrupts erfolgen. Eine Verhinderung von Optimierungen über die Grenzen eines kritischen Bereiches erreicht man durch eine memory-barrier. Beides erledigt das ATOMIC_BLOCK Macro. Wie vorgeschlagen ist dies also in main() zu verwenden. Allerdings ist beim AVR-DB zu beachten, dass die Interrupts in einer ISR nicht via SREG abgeschaltet werden. Daher kann eine ISR weiterhin durch eine andere ISR höherer Prio (oder NMI) unterbrochen werden. Gibt es also solche weiteren ISRs dafür, müssen die nieder-prioren ISRs ebenfalls als ATOMIC_BLOCK für den Zugriff auf die gemeinsam genutzten Datenstrukturen verwenden. Anderfalls reicht dort eine memory-barrier. Dafür gibt es das Macro _MemoryBarrier() in <avr/cpufunc.h>. Auf das volatile sollte für die gemeinsam genutzte Datenstruktur verzichtet werden. Es ist zwar nicht falsch im dem Sinne, dass es zu Fehlern führt, aber es verursacht zuviel Aufwand im Maschinencode, da Optimierungen unterdrückt werden können.
Forist schrieb: > Bei asm-Befehlen, die mehrere Takte benötigen, kann, obwohl sie mehrere > Takte benötigen, trotzdem niemand dazwischen grätschen. Das ist falsch. Der ARM Multiregister-push/pop ist unterbrechbar.
Am besten ist doch man verzichtet auf gegenseitige Abhängigkeiten Main<>Interrupt indem man das Wesentliche (hier: die Sekunden zählen) gleich mit im Interrupt erledigt! Oder zweite Möglichkeit ist der Informationsaustausch über Nachrichten (ein Bit, ein Byte, ein Register-Flag, ein Register). Bei Schwierigkeiten wie den diesen sollte man den Hebel an der grundsätzlichen Programmstruktur ansetzen und nicht mit Atomar-Aktionen rumdoktern die letztlich nur den Programmfluss aufhalten.
MaWin schrieb: > Das ist falsch. > Der ARM Multiregister-push/pop ist unterbrechbar. Das ist falsch. Wir sind nämlich hier beim unvergleichlichen AVR und nicht bei ARM.
Ralf schrieb: > Bei Schwierigkeiten wie den diesen sollte > man den Hebel an der grundsätzlichen Programmstruktur ansetzen und nicht > mit Atomar-Aktionen rumdoktern die letztlich nur den Programmfluss > aufhalten. Wenn 0,35µs Interruptpause den Programmfluß aufhalten, dann hast Du die völlig falsche CPU gewählt. Das Atomar-Macro wurde erstellt, um die Programmstruktur übersichtlich zu halten und nicht selber was mit cli/sei basteln zu müssen. Es ist ja gerade der Vorteil von Interrupts, daß sie eine weitere Ausführungsinstanz erstellen. Willst Du das nicht, mußt Du Interrupts generell verbieten. MCs ohne Interrupts kommen bei mir nicht auf den Tisch. Gibt es die überhaupt noch?
Peter D. schrieb: > Wenn 0,35µs Interruptpause den Programmfluß aufhalten Dazu fällt mir nur noch ein: Kleinvieh macht auch Mist. > Das Atomar-Macro bläst nur sinnlos das Programm auf wenn es auch anders geht. > Es ist ja gerade der Vorteil von Interrupts, daß sie eine weitere > Ausführungsinstanz erstellen. Wer hat hier was gegen Interrupts? Manch ein Programm besteht nur daraus. Im Hauptprogramm wird dann gepflegt geschlafen.
Ralf schrieb: >> Das Atomar-Macro > > bläst nur sinnlos das Programm auf wenn es auch anders geht. Dann zeig doch einfach mal Deine Lösung ;-) Wir wollen alle lernen.
Wilhelm M. schrieb: > Auf das volatile sollte für die gemeinsam genutzte Datenstruktur > verzichtet werden. Es ist zwar nicht falsch im dem Sinne, dass es zu > Fehlern führt, aber es verursacht zuviel Aufwand im Maschinencode, da > Optimierungen unterdrückt werden können. Weißt Du was? Ich verzichte bei solchen Simpel-Programmen gleich ganz auf Hochsprache. Da braucht man u.a. auf solche Befindlichkeiten keine Rücksicht nehmen und schreibt exakt das was für den Zweck wirklich erforderlich ist (im Beispiel des TO sogar mit wesentlich weniger getippten Zeichen). Wilhelm M. schrieb: > Dann zeig doch einfach mal Deine Lösung ;-) Was hast Du an der beschriebenen Alternativ- Strategie nicht verstanden??? Mikrosekunden/Sekundenzählen reiche ich aber gerne nach. Obs dem TO und diesem C- Thema in Asm aber nützt?
Ralf schrieb: > Wilhelm M. schrieb: >> Auf das volatile sollte für die gemeinsam genutzte Datenstruktur >> verzichtet werden. Es ist zwar nicht falsch im dem Sinne, dass es zu >> Fehlern führt, aber es verursacht zuviel Aufwand im Maschinencode, da >> Optimierungen unterdrückt werden können. > > Weißt Du was? Ich verzichte bei solchen Simpel-Programmen gleich ganz > auf Hochsprache. 1. Ist das Beispiel des TO ein Beispiel, was sicher aus einem größeren Kontext entstammt. 2. Geht es hier um C/C++ und nicht um AVR-Assembler > Wilhelm M. schrieb: >> Dann zeig doch einfach mal Deine Lösung ;-) > > Was hast Du an der beschriebenen Alternativ- Strategie nicht > verstanden??? Nichts. > Mikrosekunden/Sekundenzählen reiche ich aber gerne nach. Obs dem TO und > diesem C- Thema in Asm aber nützt? S.o., gefragt ist hier C/C++ und nicht Assembler.
:
Bearbeitet durch User
Beitrag #7252892 wurde von einem Moderator gelöscht.
Platin-Thermometer-Nutzer schrieb im Beitrag #7252892: > Wilhelm M. schrieb: >> Dann zeig doch einfach mal Deine Lösung ;-) Wir wollen alle lernen. > > Du nicht. Das lügst Du. Danke ;-) Ich lerne sehr gerne, aber nur, wenn es etwas zu lernen gibt ;-) Schau'n wir mal was so kommt ...
Ich nehme bei sowas lieber ein sequential lock (zum Lesen) als die Interrupts auszuschalten. Also die Variable solange lesen, bis zweimal nacheinander derselbe Wert rauskommt, und das in einer Getter-Funktion kapseln. Ein Rücksetzen des Counters hat IMO gar nichts in der main verloren, sondern der Counter sollte im Interrupt mit Überlauf einfach immer weiter hochgezählt werden. Zeitvergleiche dann wie üblich mit Subtraktion.
Was mich bei der ganzen Diskussion wundert: warum wurde dem TO bislang noch nicht die Verwendung des RTC-Timers vorgeschlagen? Dank dessen TEMP-Registers kann der 16-Bit-Timerwert jederzeit auch bei aktiven Interrupts gelesen werden. Grüßle, Volker
Volker B. schrieb: > Was mich bei der ganzen Diskussion wundert: warum wurde dem TO bislang > noch nicht die Verwendung des RTC-Timers vorgeschlagen? Dank dessen > TEMP-Registers kann der 16-Bit-Timerwert jederzeit auch bei aktiven > Interrupts gelesen werden. Weil das nicht das Problem des TOs ist: er hatte nicht verstanden, dass es bei nebenläufigem Zugriff auf eine gemeinsam genutzte Datenstruktur zu Wettlaufsituationen kommen kann.
Wilhelm M. schrieb: > Weil das nicht das Problem des TOs ist: er hatte nicht verstanden, dass > es bei nebenläufigem Zugriff auf eine gemeinsam genutzte Datenstruktur > zu Wettlaufsituationen kommen kann. Das Problem des TO ist m.E. dass seine Verzögerungszeit unregelmäßig ist. Ich zitiere: "Das Problem: Manche der gezählten Sekunden sind kürzer!" Genauso kann argumentiert werden, dass der TO nicht verstanden hat, dass der RTC-Timer genau für diese Aufgabe, die der TO sucht, implementiert wurde. Grüßle, Volker
Volker B. schrieb: > Wilhelm M. schrieb: > >> Weil das nicht das Problem des TOs ist: er hatte nicht verstanden, dass >> es bei nebenläufigem Zugriff auf eine gemeinsam genutzte Datenstruktur >> zu Wettlaufsituationen kommen kann. > > Das Problem des TO ist m.E. dass seine Verzögerungszeit unregelmäßig > ist. > > Ich zitiere: "Das Problem: Manche der gezählten Sekunden sind kürzer!" Allerdings schreibt er dann: Timo schrieb: > Es wirkt, als würde der Wert der Variable ms_counter_ticker zwischen den > beiden if-Anweisungen geändert. Und genau da lag sein Problem. Statt die RTC im AVR-DB zu nutzen, könnte man ihm auch vorschlagen, ganz auf Interrupts zu verzichten, oder einen anderen µC zu nutzen. Doch hätte das seinem Verständnis für das Problem hier nicht gedient.
:
Bearbeitet durch User
Volker B. schrieb: > dass seine Verzögerungszeit unregelmäßig > ist. Und genau das deutet auf konkurrierende Zugriffe hin. Denn ein Quarz/Timer/Counter mag ein paar Takte daneben liegen, aber dann stabil. Klar kann man die RTC Baustelle auch aufmachen. Aber dennoch muss auch dieses Thema abgearbeitet werden, sonst hakts beizeiten woanders. Und dann soll wieder der Compiler kaputt sein.
Volker B. schrieb: > Genauso kann argumentiert werden, dass der TO nicht verstanden hat, dass > der RTC-Timer genau für diese Aufgabe, die der TO sucht, implementiert > wurde. Nein, wurde er nicht. Auch wenn es hier zufällig um ganze Sekunden geht, di zufällig auch per RTC abgehandelt werden könnten, sind die Timer (u.a.) genau für die Aufgabe eines in regelmäßigen Zeitabständen feuernden Interrupts gedacht. Und das Problem, was der TO hat, ist ja unabhängig vom Zeitintervall. Oliver
Hallo, @ Wilhelm: Warum soll eine Variable die außerhalb des normalen Programmablaufes geändert wird nicht als volatile deklariert werden? Die Optimierung muss doch genau deswegen aktiv verhindert werden, eben mit volatile. So mein Kenntnisstand.
:
Bearbeitet durch User
Veit D. schrieb: > Die Optimierung muss > doch genau deswegen aktiv verhindert werden, Nicht unbedingt verhindert! Es muss zudem eine garantierte Abfolge sichergestellt werden. DAS sind 2 Probleme, mindestens 2. 1. Dass die richtigen aktuellen Values verwendend werden, 2. und dass der Optimizer nicht die Statements (in diesem Fall unerwünscht) verwürfelt (was er in der Regel ja durchaus darf, das würfeln). https://en.wikipedia.org/wiki/Memory_barrier
Veit D. schrieb: > Warum soll eine Variable die außerhalb des normalen Programmablaufes > geändert wird nicht als volatile deklariert werden? Die Optimierung muss > doch genau deswegen aktiv verhindert werden, eben mit volatile. So mein > Kenntnisstand. Das mit dem "volatile" ist halt das was man Anfängern erstmal mit auf den Weg gibt. Ist nicht falsch und funktioniert, und man hat wieder Ruhe vor nervigen Nachfragen. Mit Erfahrung kommt dann das Wissen, was mit dem volatile erreicht wird, warum es eigentlich nicht ganz das richtige Sprachkonstrukt an der Stelle ist, und wie man es besser&eleganter machen kann. Oder auch nicht. Ist egal, volatile tut ja. Außer wenn, wie hier, die Variablen > 8 Bit werden... Dann muss man eben früher ins kalte Wasser.
:
Bearbeitet durch User
Veit D. schrieb: > Hallo, > > @ Wilhelm: > Warum soll eine Variable die außerhalb des normalen Programmablaufes > geändert wird nicht als volatile deklariert werden? Die Optimierung muss > doch genau deswegen aktiv verhindert werden, eben mit volatile. So mein > Kenntnisstand. Das habe ich oben schon geschrieben. Hier (etwas) ausführlicher: volatile verhindert (fast) jede Art von Optimierung, denn es muss jedes load/store tatsächlich so ausgeführt werden, wie es im C/C++-Code steht. Dies ist für HW-Register erforderlich, da hier jedes load/store einen erwünschten Seiteneffekt auslöst. Für reine Datenstrukturen wie etwa einen integralen Zähler oder ähnliches ist dies aber nicht erforderlich. Beispiel:
1 | namespace { |
2 | uint8_t g; |
3 | volatile uint8_t h; |
4 | }
|
5 | void foo() { // kann auch eine ISR sein |
6 | g = 1; |
7 | g = 2; |
8 | h = 1; |
9 | h = 2; |
10 | }
|
Hier werden tatsächlich die beiden Zuweisungen an g zu einer zusammengefasst, was nach der as-if Regel erlaubt ist. Das ist eine der simpelsten Optimierungen. Bei h wird hingegen jedes store ausgeführt (obwohl nicht notwendig). Das volatile macht den Code nicht falsch, aber eben wenig effizient. Man beachte hier, dass foo() nicht-static ist, so dass der Zugriff auf g nicht ganz wegoptimiert werden darf, obwohl g in einem anonymen namespace steht. Um nun zu verhindern, dass ggf. der Compiler doch noch weitere Optimierungen durchführt, die ggf. dazu führen könnten, dass für g evtl. gar kein store mehr ausgeführt wird (nicht in diesem Beispiel), kann man dann noch eine memory-barrier - wie eben oben schon geschrieben - (mit dem Macro _MemoryBarrier()) eingebaut werden. Das kostet keine Instruktion, da das ja nur eine Anweisung an den Compiler ist, das load/store tatsächlich auszuführen, wie es (mit Optimierung) notwendig ist. Um es deutlich zu sagen: die memory-barrier kostet keine unnötige Performance! Das Thema Atomarität habe ich hier jetzt erstmal ausgeklammert, aber das steht ja auch schon in meinem Beitrag von oben.
Wilhelm M. schrieb: > die memory-barrier kostet keine unnötige Performance! MB sind aber doch tatsächliche Instruktionen? Das, was keine Instruktion bewirkt, ist eine Compiler-Barrier, die lediglich den Compiler im Zaum hält, etwa mit dem üblichen "asm volatile". Übrigens ist das übliche Idiom für volatile-Variablen in der ISR ja, die in eine lokale Variable zu laden, auf der zu arbeiten und am Ende wieder zurückzuschreiben. Das kann der Compiler optimieren.
Nop schrieb: > Wilhelm M. schrieb: >> die memory-barrier kostet keine unnötige Performance! > > MB sind aber doch tatsächliche Instruktionen? Nicht bei einer simpelst CPU wir AVR, um die es hier geht. Nop schrieb: > Das, was keine Instruktion bewirkt, ist eine Compiler-Barrier, Ich bin mir nicht sicher, aber ich denke, der Begriff memory-barrier ist ein Oberbegriff für Techniken, ein Compile-time-reorder und/oder ein Runtime-reorder (CPU) zu unterdrücken. Für CPUs, die ein Instruction-Reorder machen (die AVR wie hier gehört nicht dazu) muss natürlich auch eine Instruktion emittiert werden.
:
Bearbeitet durch User
Bevor sich der TO diese Vorträge anhört kann man ihm nur schleunigst raten bei den Grundlagen und mit Asm zu beginnen um schlicht ein Verständnis der ablaufenden Vorgänge zu entwickeln. An diesem Beispiel zeigen sich die Versäumnisse die entstehen wenn gleich mit Hochsprache eingestiegen wird wieder einmal besonders drastisch.
Ralf schrieb: > Bevor sich der TO diese Vorträge anhört kann man ihm nur schleunigst > raten bei den Grundlagen und mit Asm zu beginnen um schlicht ein > Verständnis der ablaufenden Vorgänge zu entwickeln. Wie ein super sinnvoller Beitrag von Dir ;-) Wilhelm M. schrieb: > Ralf schrieb: > >>> Das Atomar-Macro >> >> bläst nur sinnlos das Programm auf wenn es auch anders geht. > > Dann zeig doch einfach mal Deine Lösung ;-) Wir wollen alle lernen. Und?
Wilhelm M. schrieb: > Wie ein super sinnvoller Beitrag von Dir ;-) Sinnvoller für den TO als Deine fachlichen Erläuterungen allemal. Wilhelm M. schrieb: > Wilhelm M. schrieb: >> Ralf schrieb: >> >>>> Das Atomar-Macro >>> >>> bläst nur sinnlos das Programm auf wenn es auch anders geht. >> >> Dann zeig doch einfach mal Deine Lösung ;-) Wir wollen alle lernen. > > Und? Wenn es der TO ausdrücklich wünscht was Ralf schrieb: > Am besten ist doch man verzichtet auf gegenseitige Abhängigkeiten > Main<>Interrupt indem man das Wesentliche (hier: die Sekunden zählen) > gleich mit im Interrupt erledigt! Oder zweite Möglichkeit ist der > Informationsaustausch über Nachrichten (ein Bit, ein Byte, ein > Register-Flag, ein Register). Bei Schwierigkeiten wie den diesen sollte > man den Hebel an der grundsätzlichen Programmstruktur ansetzen und nicht > mit Atomar-Aktionen rumdoktern die letztlich nur den Programmfluss > aufhalten. konkret in Asm bedeutet gerne. Mich speziell für Dich hinzusetzen wäre doch die Zeit zu schade, da stimmst Du sicher zu :)
Please find attached is working code from Arduino world. The advantage here is you do not need reset the variable to zero. Just let it overflow.
Ralf schrieb: > > konkret in Asm bedeutet gerne. Das interessiert den TO (und andere) nicht wirklich. > Mich speziell für Dich hinzusetzen wäre doch die Zeit zu schade, da > stimmst Du sicher zu :) Ja, da geb ich Dir Recht. Du hast gar nichts zu bieten, was sich lohnen würde, sich damit zu beschäftigen. Nur Geschwafel.
Hallo, Danke für eure Antworten an mich. Ich weiß leider immer noch nicht warum ich kein volatile verwenden soll. Volatile macht load/store zum Zwang. Okay ist ja Absicht. Nur wird das Macro _MemoryBarrier()) ja genauso erklärt mit zwangsweise load/store. Was ist jetzt der Unterschied zwischen volatile und dem Macro? Ich drehe mich irgendwie im Kreis. Sind mit dem Load / Store die Macros gemeint die man bei volatile Variablen ganz frisch modern verwenden soll, die statt der Kurzschreibweisen-Operationen/Zuweisung (+=, -= usw.) verwendet werden sollen? Oder hau ich jetzt was komplett durcheinander?
Veit D. schrieb: > Was ist jetzt der Unterschied > zwischen volatile und dem Macro? Eine MB lässt dem Compiler mehr Freiheiten zur Optimierung. Da volatile keine ordering-Semantik zwischen volatile und nicht-volatile hat, muss man oft alle globalen Variablen, auf die dort zugegriffen wird, volatile machen. Die MB ist ein Synchronizationspunkt vor und nach dem der Compiler wieder beliebig (as-if) optimieren kann.
Wilhelm M. schrieb: > Das interessiert den TO (und andere) nicht wirklich. > Du hast gar nichts zu bieten, was sich lohnen würde, sich damit zu > beschäftigen. Nur Geschwafel. Stehn Dir nicht wirklich gut solche Äußerungen. Sehr schade. gam01hr schrieb: > Just let it overflow. Eine Lösung mehr, Atomar-Operationen überflüssig zu machen. Wenn es auch manch großem Experten überhaupt nicht passt die Dinge einfach zu halten :)
gam01hr schrieb: > Please find attached is working code from Arduino world. The advantage > here is you do not need reset the variable to zero. Just let it > overflow. Das ist etwas von hinten durch die Brust ins Auge. Ein Reset auf 0 macht man sowieso nicht. Der Überlauf korrigiert sich selbst. Vorrausgesetzt die Speichervariable hat den gleichen Datentyp wie der Rückgabewert von millis. uint32_t. Der seit Jahren bewährte Standard ist:
1 | unsigned long lastMillis {0}; |
2 | const unsigned int intervall {1000}; |
3 | ...
|
4 | ...
|
5 | |
6 | if (millis() - lastMillis >= intervall) |
7 | {
|
8 | lastMillis = millis(); |
9 | // lastMillis += intervall; alternativ je nach Ausführungseffekt
|
10 | ...
|
11 | ...
|
12 | }
|
MaWin schrieb: > Veit D. schrieb: >> Was ist jetzt der Unterschied >> zwischen volatile und dem Macro? > > Eine MB lässt dem Compiler mehr Freiheiten zur Optimierung. > Da volatile keine ordering-Semantik zwischen volatile und nicht-volatile > hat, muss man oft alle globalen Variablen, auf die dort zugegriffen > wird, volatile machen. > Die MB ist ein Synchronizationspunkt vor und nach dem der Compiler > wieder beliebig (as-if) optimieren kann. Okay. Beim Makro darf irgendwie trotz load/store Zwang irgendwas optimiert werden. Ich lasse das erstmal sacken. ;-) Danke.
Veit D. schrieb: > Okay. Beim Makro darf irgendwie trotz load/store Zwang irgendwas > optimiert werden. Ich lasse das erstmal sacken. ;-) Genau.
1 | A; |
2 | B; |
3 | memorybarrier; |
4 | C; |
5 | D; |
A und B können getauscht werden. C und D können getauscht werden. B und C aber nicht (und alle anderen Kombinationen über die Barrier hinweg auch nicht). Wenn A, B, C, D volatile sind, kann gar nichts getauscht werden.
MaWin schrieb: > Wenn A, B, C, D volatile sind, kann gar nichts getauscht werden. Das ist leider nur eine der unschönen urban legends in C, bei der man sich letztendlich wundert, warum es überhaupt funktionierende Programme gibt. https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html Oliver
Oliver S. schrieb: > Das ist leider nur eine der unschönen urban legends in C, bei der man > sich letztendlich wundert, warum es überhaupt funktionierende Programme > gibt. Ja. offiziell ist da fast gar nichts garantiert. Trotzdem machen vernünftige Compiler kein Reordering zwischen volatile. Es ist also praktisch implementation defined, was volatile macht. Alleine deshalb ist es für die breite Nutzung im Programm abzulehnen.
MaWin schrieb: > Alleine deshalb > ist es für die breite Nutzung im Programm abzulehnen. Das C++-Standardisierungskomitee hat ja eine massive Einschränkung vorgeschlagen, allerdings hat das dann doch zu einigem Aufruhr geführt. Ist halt alles nicht so einfach. Oliver
Veit D. schrieb: > Okay. Beim Makro darf irgendwie trotz load/store Zwang irgendwas > optimiert werden. Ich lasse das erstmal sacken. ;-) Hast Du Dir mein Beispiel von oben einmal genauer angesehen? Dort ist eine Optimierungsmöglichkeit angegeben, die möglich und sinnvoll wäre, aber von volatile verhindert wird.
Oliver S. schrieb: > MaWin schrieb: >> Alleine deshalb >> ist es für die breite Nutzung im Programm abzulehnen. > > Das C++-Standardisierungskomitee hat ja eine massive Einschränkung > vorgeschlagen, allerdings hat das dann doch zu einigem Aufruhr geführt. Das betraf vor allem var++, etc. wie auch var += expr, weil eben die konkreten Auswirkungen (Anzahl der der Load/Store-Operationen) nicht klar (IB) definiert sind.
Ralf schrieb: > Wilhelm M. schrieb: >> Das interessiert den TO (und andere) nicht wirklich. > >> Du hast gar nichts zu bieten, was sich lohnen würde, sich damit zu >> beschäftigen. Nur Geschwafel. > > Stehn Dir nicht wirklich gut solche Äußerungen. Sehr schade. Ich habe Deine bisherigen Posts gelesen und sie für mich bewertet. Mit dem gesagten Ergebnis. Dann werde doch mal konkret! > gam01hr schrieb: >> Just let it overflow. > > Eine Lösung mehr, Atomar-Operationen überflüssig zu machen. Wenn es auch > manch großem Experten überhaupt nicht passt die Dinge einfach zu halten > :) Das beschreibt aber nicht eine Lösung für das Problem des TO. Aber wahrscheinlich hast Du das auch nicht verstanden.
Ralf schrieb: > Eine Lösung mehr, Atomar-Operationen überflüssig zu machen. Wenn es auch > manch großem Experten überhaupt nicht passt die Dinge einfach zu halten Nur weil es versteckt ist, ist es nicht überflüssig. Wirf mal einen Blick in die wiring.c, wie "millis()" implementiert ist:
1 | unsigned long millis() |
2 | {
|
3 | unsigned long m; |
4 | uint8_t oldSREG = SREG; |
5 | |
6 | // disable interrupts while we read timer0_millis or we might get an
|
7 | // inconsistent value (e.g. in the middle of a write to timer0_millis)
|
8 | cli(); |
9 | m = timer0_millis; |
10 | SREG = oldSREG; |
11 | |
12 | return m; |
13 | }
|
Compiliert exakt zum selben ASM-Code wie die ATOMIC_BLOCK(ATOMIC_RESTORESTATE)-Variante.
Εrnst B. schrieb: > Ralf schrieb: >> Eine Lösung mehr, Atomar-Operationen überflüssig zu machen. Wenn es auch >> manch großem Experten überhaupt nicht passt die Dinge einfach zu halten > > Nur weil es versteckt ist, ist es nicht überflüssig. > Wirf mal einen Blick in die wiring.c, wie "millis()" implementiert ist: > >
1 | > unsigned long millis() { |
2 | > ... |
3 | >
|
> > Compiliert exakt zum selben ASM-Code wie die > ATOMIC_BLOCK(ATOMIC_RESTORESTATE)-Variante. Und man sollte dazu sagen, dass natürlich timer0_millis als volatile qualifiziert ist (was eigentlich nicht notwendig ist, s.a. mein Beitrag von oben).
Εrnst B. schrieb: > Nur weil es versteckt ist, ist es nicht überflüssig. Dann war es versteckt. Und bleibt trotzdem nicht minder überflüssig. Der Zugriff auf Variablen von zwei verschiedenen Ausführungsebenen (Main/Interrupt), zumal schreibend, kann und sollte vermieden werden. Spezifische Aufgaben sind möglichst vollständig auf nur einer Ebene abzuhandeln. Es spricht nur dann nichts gegen globalen Zugriff auf globale Variablen wenn diese stets gültige Werte annehmen und sich dabei eine Programmebene nicht auf ihr eigenes Variablengeschreibsel verlässt.
Ralf schrieb: > Εrnst B. schrieb: >> Nur weil es versteckt ist, ist es nicht überflüssig. > > Dann war es versteckt. > Und bleibt trotzdem nicht minder überflüssig. Dann Aufgabe an Dich: zeige eine Lösung ohne volatile oder ohne memory-barrier bzw. ohne Interrupt-Sperre.
:
Bearbeitet durch User
Ralf schrieb: > Es spricht nur dann nichts gegen globalen Zugriff auf globale Variablen > wenn diese stets gültige Werte annehmen und sich dabei eine Häh? d.H. in deiner Welt darf man nur 8-Bit-Variablen verwenden, wenn man auf einem 8-Bit-AVR unterwegs ist? Es ist nunmal eine Hardware-Limitierung, dass der AVR nur 8 Bit auf einmal schreiben kann. Völlig egal, ob du den in ASM, in C++, oder mit handgemeißelten Binärcode fütterst. Sonst zeig mal deinen magischen Code für einen glitch-freien 16- oder 32-Bit Zähler ohne atomaren Zugriff.
Εrnst B. schrieb: > Es ist nunmal eine Hardware-Limitierung, dass der AVR nur 8 Bit auf > einmal schreiben kann. Und weil das so ist verzichtet man in diesem Fall auch drauf. Wo ist hier eigentlich das Problem? Der TO zählt seine Sekunden idealerweise mit im Interrupt- oder dieserjenige signalisiert den Ablauf von 1000ms dem Hauptprogramm (was dann aber meist nicht punktgenau reagieren können wird). Alles andere ist in meinen Augen schlicht Pfusch.
Wilhelm M. schrieb: > was eigentlich nicht notwendig ist Aber auch an der Stelle keinen einzigen Takt, oder Byte, verschwendet. Also durchaus völlig ok ist. gam01hr schrieb: > Please find attached is working code from Arduino world. The advantage > here is you do not need reset the variable to zero. Just let it > overflow. Aus mehreren Gründen gefällt mir das überhaupt nicht! Aus diesen Gründen: Nicht empfehlenswert. Zudem: Arduino ist C++, in der Hauptsache
EAF schrieb: > Wilhelm M. schrieb: >> was eigentlich nicht notwendig ist > > Aber auch an der Stelle keinen einzigen Takt, oder Byte, verschwendet. > Also durchaus völlig ok ist. Ja, so wie es dort steht, ist das Ergebnis mit volatile nicht schlechter als mit memory-barrier.
1 | #if defined(TIM0_OVF_vect)
|
2 | ISR(TIM0_OVF_vect) |
3 | #else
|
4 | ISR(TIMER0_OVF_vect) |
5 | #endif
|
6 | {
|
7 | // copy these to local variables so they can be stored in registers
|
8 | // (volatile variables must be read from memory on every access)
|
9 | unsigned long m = timer0_millis; |
10 | unsigned char f = timer0_fract; |
11 | |
12 | m += MILLIS_INC; |
13 | f += FRACT_INC; |
14 | if (f >= FRACT_MAX) { |
15 | f -= FRACT_MAX; |
16 | m += 1; |
17 | }
|
18 | |
19 | timer0_fract = f; |
20 | timer0_millis = m; |
21 | timer0_overflow_count++; |
22 | }
|
Aber: mit memory-barrier statt volatile wäre die Erweiterbarkeit des Codes wesentlich besser gewesen, denn man hätte auf den Kommentar und auf die lokalen Variablen verzichten können. Denn eine unvorsichtige Erweiterung des obigen Codes führt dann zu einer nicht-optimalen Performance, etwa wenn jemand ein
1 | if (timer0_millis > 42) ... |
einfügt. Dieser Effekt ist umso wahrscheinlicher, als timer0_millis zur öffentlichen Schnittstelle (non-static global) des Moduls gehört. Das Problem an volatile ist eben (wie oben in meinem Beitrag schon gesagt), dass es ein Bestandteil der Variablendeklaration ist und nicht der Zugriffsoperation. Daher sollte volatile eben ausschließlich HW-Registern vorbehalten bleiben, und die Zugriffe auf nebenläufig genutzte Datenstrukturen über memory-barrier vor dem Optimizer schützt werden (Atomarität natürlich ebenfalls nicht vergessen).
Ralf schrieb: > Εrnst B. schrieb: >> Es ist nunmal eine Hardware-Limitierung, dass der AVR nur 8 Bit auf >> einmal schreiben kann. > > Und weil das so ist verzichtet man in diesem Fall auch drauf. > Wo ist hier eigentlich das Problem? Der TO zählt seine Sekunden > idealerweise mit im Interrupt- oder dieserjenige signalisiert den Ablauf > von 1000ms dem Hauptprogramm (was dann aber meist nicht punktgenau > reagieren können wird). Du setzt als in der ISR ein flag. Dieses flag fragst Du außerhalb der ISR ab, und setzt es ggf. zurück? Ohne memory-barrier / volatile wird die entweder das if-statement komplett weg-optimiert oder der Wert des flag wird gecached in einem Register und eine Wertänderung wird nie stattfinden.
Wilhelm M. schrieb: > Dieser Effekt ist umso wahrscheinlicher, als timer0_millis zur > öffentlichen Schnittstelle (non-static global) des Moduls gehört. Ist eigentlich nicht sonderlich öffentlich. Wäre sie öffentlich gedacht, wäre eine extern Deklaration z.B. in Arduino.h erfolgt. Ist aber nicht. Gegenbeispiel: Serial Serial ist als extern deklariert, mit der Absicht es öffentlich zu machen. Öffentlich sind millis(), delay() und Konsorten. Die verhalten sich auf allen (Arduino) Systemen vergleichbar. Es gibt keine Notwendigkeit, dass es auf allen Systemen eine Variable namens timer0_millis gibt. Ehr ist das Gegenteil zu erwarten. timer0_millis dient also zur internen Verwaltung der Zeiten, auf AVR Arduinos.
EAF schrieb: > Ist eigentlich nicht sonderlich öffentlich. Natürlich ist das öffentlich: jeder kann darauf zugreifen! Arduino-Framework ist C++, daher verstehe ich nicht, warum man dann die Möglichkeiten nicht nutzt - oder zumindest die Möglichkeiten, die auch C bietet.
:
Bearbeitet durch User
Wilhelm M. schrieb: > Natürlich ist das öffentlich: jeder kann darauf zugreifen! Wie gesagt: Es mag erreichbar sein, ist aber dennoch nicht für die öffentliche Verwendung vorgesehen. Steht nicht auf jedem System zur Verführung, im Gegensatz zu millis() Wilhelm M. schrieb: > oder zumindest die Möglichkeiten, die auch C > bietet. Ich vermute mal, dass am Anfang nicht abzusehen war, wie doll das ganze aufblüht. Die Bedingungen unter dem es entstanden ist, haben sich über die Zeit verändert. Heutzutage, wäre man sicherlich an vielen Stellen "sorgfältiger" vorgegangen. Oder anders: Nicht jeder/jede kann so klug/genial/perfekt/sorgfältig wie du sein. Dir würde das natürlich nicht passieren, irgendwas unabsichtlich öffentlich zu halten.
EAF schrieb: > Ich vermute mal, dass am Anfang nicht abzusehen war, wie doll das ganze > aufblüht. > Die Bedingungen unter dem es entstanden ist, haben sich über die Zeit > verändert. > Heutzutage, wäre man sicherlich an vielen Stellen "sorgfältiger" > vorgegangen. Dann sollte man es eben ändern. Wenn Du der Meinung bist, dass die volatiles nicht öffentlich sein sollten, kann man das ja ohne weiters korrigieren. Die Klienten, die dann darauf zugegriffen haben, hätten dann einen Bug des Arduino-FW ausgenutzt, und müssten mit dem breaking-change leben.
Wilhelm M. schrieb: > Dann sollte man es eben ändern. Mach doch..... Wird sich dann zeigen, ob du damit durch kommst.
Hallo, ich habe das Gefühl wir reden aneinandervorbei. Die memory order bezieht sich doch auf die Verwendung von atomic. Das verwendet man um eine Variable ungestört zu lesen / zu schreiben ohne das bspw. ein Interrupt dazwischen funkt. Soweit war das schon lange klar. Das hat jedoch nichts mit volatile zu tun. Volatile verbietet dem Compiler das er die Variable wegoptimiert, was er wegen ISR nicht darf, weil Änderungen außerhalb des normalen Programmflusses stattfinden können. Damit bin ich wieder am Anfang und kein Stück schlauer. Ich kann doch volatile nicht weglassen und mich auf atomic bei der Variablenverwendung verlassen. Das reicht doch nicht aus. Der Compiler muss doch irgendwie wissen das er den Variableninhalt immer frisch lesen muss und nicht aus irgendeinem veralteten Cache holt. Ansonsten wäre es nicht falsch wenn jemand einen Code zeigt der alles ohne volatile richtig macht. Erklären und verstehen muss zusammenpassen.
Veit D. schrieb: > ich habe das Gefühl wir reden aneinandervorbei. Punkt 1: Wenn du einfache Antworten erwartest, solltest du nicht nach komplizierten Dingen fragen. Punkt 2: Sowohl sei() als auch cli() haben Memory Barrieren im Bauch. Und die Atomic Macros auch. Kann man in den betreffenden *.h Dateien nachlesen. Punkt 3: ......
Hallo, jetzt brauch ich wirklich gedanklichen Abstand. Ich gehe das später nochmal an. Danke für eure Mühen, scheinbar bei mir der falsche Zeitpunkt oder so ähnlich.
Veit D. schrieb: > Ich gehe das später nochmal an. Lies doch einfach mal die gegebenen Antworten. Ich habe in einem einfachen Beispiel gezeigt was eine einfache memory barrier ist und wie sie sich aufs Ordering auswirkt. Mit Atomics hat das überhaupt nichts zu tun. Um Atomic und Ordering muss man sich separat kümmern. Auch bei volatile! Volatile macht praktisch überhaupt nichts, was atomic angeht. (Vielleicht verhindert es maximal write-tearing oder sowas)
Veit D. schrieb: > Hallo, > > ich habe das Gefühl wir reden aneinandervorbei. Die memory order bezieht > sich doch auf die Verwendung von atomic. Nein, völlig andere Baustelle. > Das verwendet man um eine > Variable ungestört zu lesen / zu schreiben ohne das bspw. ein Interrupt > dazwischen funkt. Soweit war das schon lange klar. Atomare Operationen sind unteilbare Operationen. Eine Folge von Anweisungen, die nicht unterbrochen werden soll, bezeichnet man so. Das benötigt man für einen konsistenten, nebenläufigen Zugriff auf Datenstrukturen, die von unterschiedlichen Aktivitätsträgern genutzt werden sollen. Stellt Dir eine verkettete Liste vor. Wenn dort ein Knoten eingefügt werden soll, sind ein paar Zeigermanipulationen an den Listenknoten notwendig. Wenn Du Dir vorstellst, dass Du diese Operationen mittendrin unterbrichst, und ein anderer Aktivitätsträger in diesem Moment versucht die Liste zu traversieren, wird es wahrscheinlich zu Problemen kommen, da die Zeigermanipulationen an den Listenknoten einfach nich abgeschlossen sind: die Traversion bricht ggf. vorzeitig ab oder referenziert einen ungültigen Zeiger. Diese Anweisungsfolge um einen Listenknoten einzufügen nennt man auch kritischen Abschnitt, und diese Anweisungsfolge darf nicht unterbrochen werden (auf AVR ist die einzige Art von Nebenläufigkeit das Ausführen von ISRs, echte Parallelität durch mehrere Cores oder Quasi-Parallelität durch ein OS haben wir hier nicht). Auf den AVRs reicht es daher, den kritischen Bereich durch das Abschalten der Interrupts vor einer Unterbrechung zu schützen. Auf AVR reicht also cli/sei als Maßnahmen. Echte Schloßvariablen (mutual exclusion lock) haben wir beim AVR nicht (nötig). > Das hat jedoch nichts > mit volatile zu tun. Genau. > Volatile verbietet dem Compiler das er die Variable > wegoptimiert, Jein. Nicht die Variable wird ggf. wegoptimiert (die nur dann, wenn sie definitiv nicht benutzt wird), sondern der Compiler darf ggf. Operationen auf einer non-volatile Variable (memory-location) weglassen, zusammen fassen.
1 | uint8_t g; |
2 | |
3 | void foo() { |
4 | g = 1; |
5 | g = 2; |
6 | }
|
In der obigen Funktion wird nur g = 2 ausgeführt, denn g = 1 hat keinen beobachtbaren Effekt (as-if Regel). Wird die Variable g mit volatile qualifiziert, so werden beide(!) Zuweisungen ausgeführt. So etwas will man (normalerweise) nur mit MCU-Registern, weil eine Zuweisung (oder auch Lesen) einen Seiteneffekt hat. > was er wegen ISR nicht darf, weil Änderungen außerhalb des > normalen Programmflusses stattfinden können. ISR ist auf AVR die einzige Form der Nebenläufigkeit, deswegen "ja" > Damit bin ich wieder am > Anfang und kein Stück schlauer. Ich kann doch volatile nicht weglassen > und mich auf atomic bei der Variablenverwendung verlassen. Das reicht > doch nicht aus. Genau. Auch bei einem volatile kann der Zugriff nicht-atomar sein:
1 | volatile uint16_t g; |
2 | |
3 | void foo() { |
4 | g = 1; |
5 | g = 2; |
6 | }
|
Jede Zuweisung wir bei AVR auf ggf. zwei Maschinen-Instruktionen aufgeteilt, also nicht atomar (obwohl volatile). > Der Compiler muss doch irgendwie wissen das er den > Variableninhalt immer frisch lesen muss und nicht aus irgendeinem > veralteten Cache holt. Genau. Und das ist die Aufgabe einer memory-barrier: sie weist den Compiler an, alle ge-cache-ten Inhalte (etwa in Registern der CPU) zurückzuschreiben und bei Lese-Operationen frisch aus dem RAM zu lesen. > Ansonsten wäre es nicht falsch wenn jemand einen > Code zeigt der alles ohne volatile richtig macht. Erklären und verstehen > muss zusammenpassen. Nä. Post ...
Veit D. schrieb: > Ansonsten wäre es nicht falsch wenn jemand einen > Code zeigt der alles ohne volatile richtig macht. Erklären und verstehen > muss zusammenpassen. Ich habe mal was zusammengestellt:
1 | #include <avr/cpufunc.h> |
2 | #include <util/atomic.h> |
3 | #include <stdint.h> |
4 | |
5 | namespace Memory { |
6 | struct Barrier final { |
7 | inline Barrier() { |
8 | _MemoryBarrier(); |
9 | }
|
10 | inline ~Barrier() { |
11 | _MemoryBarrier(); |
12 | }
|
13 | };
|
14 | void barrier(const auto f) { |
15 | Barrier b; |
16 | f(); |
17 | }
|
18 | }
|
19 | |
20 | namespace Atomic { |
21 | struct DisableInterruptsRestore final { |
22 | inline DisableInterruptsRestore() { |
23 | cli(); |
24 | }
|
25 | inline ~DisableInterruptsRestore() { |
26 | SREG = save; |
27 | }
|
28 | private:
|
29 | uint8_t save{SREG}; |
30 | };
|
31 | |
32 | void access(const auto f) { |
33 | DisableInterruptsRestore di; |
34 | Memory::barrier([&]{ |
35 | f(); |
36 | });
|
37 | }
|
38 | }
|
39 | |
40 | volatile uint8_t mcuRegister; |
41 | |
42 | namespace { |
43 | uint8_t g; |
44 | bool flag; |
45 | }
|
46 | |
47 | ISR(TCA1_CMP0_vect) { |
48 | Memory::barrier([]{ |
49 | ++g; |
50 | if (g > 100) flag = true; |
51 | });
|
52 | }
|
53 | |
54 | int main(void) { |
55 | while (true) { |
56 | Atomic::access([]{ |
57 | if (flag) { |
58 | flag = false; |
59 | mcuRegister = 0x01; |
60 | }
|
61 | });
|
62 | }
|
63 | }
|
Vielleicht hilft es zum Verständnis.
Beitrag #7254796 wurde vom Autor gelöscht.
EAF schrieb: > Wilhelm M. schrieb: >> Dann sollte man es eben ändern. > Mach doch..... > Wird sich dann zeigen, ob du damit durch kommst. Nö, warum? Ich nutze doch gar kein Arduino-FW. Mir liegt nichts ferner als das Arduino-FW, für mich ist das broken-by-design.
:
Bearbeitet durch User
Wilhelm M. schrieb: > Ohne memory-barrier / volatile wird die entweder das if-statement > komplett weg-optimiert oder der Wert des flag wird gecached in einem > Register und eine Wertänderung wird nie stattfinden. In Asm wird weder weg-optimiert noch gecached. Selbstverständlich arbeitet da auch eine Flag-Lösung. Wenn die in Hochsprache verhindert würde (traurig genug) bleibt aber immer noch die optimale Lösung, alles in der ISR abzuhandeln, wie ich es nun schon mehrfach begründet habe. Das sollte doch nun wirklich auch in C gehen. Wer aber hier mit Gewalt Probleme konstruiert damit er sich intellektuell drin verbeißen kann hat andere Ziele, Wilhelm M. schrieb: > Mir liegt nichts ferner als das Arduino-FW, für mich ist das > broken-by-design. Wilhelm M. schrieb: > Nicht bei einer simpelst CPU wir AVR vor allem aber darf es nicht zu einfach sein :)
Ralf schrieb: > Wilhelm M. schrieb: >> Ohne memory-barrier / volatile wird die entweder das if-statement >> komplett weg-optimiert oder der Wert des flag wird gecached in einem >> Register und eine Wertänderung wird nie stattfinden. > > In Asm wird weder weg-optimiert noch gecached. Irgendwie kapierst Du es nicht: niemand in diesem Thread bezweifelt, dass das in ASM nicht wegoptimiert wird. Wie auch ... Aber: es ist in diesem Thread niemand an Deinem ASM Code interessiert. Um es nochmal deutlich zu sagen: es geht um C/C++ und das abstrakte Speichermodell darin. > *Selbstverständlich* > arbeitet da auch eine Flag-Lösung. Wenn die in Hochsprache verhindert > würde (traurig genug) bleibt aber immer noch die optimale Lösung, alles > in der ISR abzuhandeln, wie ich es nun schon mehrfach begründet habe. Das ist sicher nicht die optimale Lösung. > Das sollte doch nun wirklich auch in C gehen. Geht ja auch. Siehe all die Beiträge oben, die Du wohl nicht verstanden hast. > > Wilhelm M. schrieb: >> Mir liegt nichts ferner als das Arduino-FW, für mich ist das >> broken-by-design. > > Wilhelm M. schrieb: >> Nicht bei einer simpelst CPU wir AVR > > vor allem aber darf es nicht zu einfach sein :) Wenn man es verstanden hat, ist es einfach.
:
Bearbeitet durch User
Wilhelm M. schrieb: > Das ist sicher nicht die optimale Lösung. Guten Morgen! Dann entfalte doch bitte Deine Kapazitäten das hier nicht bloß zu behaupten sondern im Detail zu begründen.
Hallo, @ Wilhelm (und natürlich alle anderen) Danke für die nochmalige Erklärung(en). Ist schon verständlicher für mich formuliert. Muss mir auch nochmal die anderen Antworten durchlesen und gedanklich zusammenbringen. Das Bsp. gehe ich noch durch. Eine Frage gibts aber schon. Warum ist mcuRegister volatile? Ich kann keinen Grund erkennen.
Veit D. schrieb: > Hallo, > > @ Wilhelm (und natürlich alle anderen) Danke für die nochmalige > Erklärung(en). Ist schon verständlicher für mich formuliert. Muss mir > auch nochmal die anderen Antworten durchlesen und gedanklich > zusammenbringen. Das Bsp. gehe ich noch durch. Eine Frage gibts aber > schon. Warum ist mcuRegister volatile? Ich kann keinen Grund erkennen. In dem Beispiel soll eben mcuRegister ein tatsächlcihes HW-Register der MCU darstellen, als Beispiel. Ich hätte auch z.B. GPIOR0 schreiben können.
Ralf schrieb: > Guten Morgen! Dann entfalte doch bitte Deine Kapazitäten das hier nicht > bloß zu behaupten sondern im Detail zu begründen. Es geht hier nicht um Asm. Und es geht auch nicht darum die optimalste Lösung zu finden. Begründung Ende.
MaWin schrieb: > Und es geht auch nicht darum die optimalste Lösung zu finden. Es heißt die 'optimale' MaWin. Und ja, das stand ohnehin zu vermuten. Warum einfach wenn's auch kompliziert geht.
Ralf schrieb: > MaWin schrieb: >> Und es geht auch nicht darum die optimalste Lösung zu finden. > > Es heißt die 'optimale' MaWin. > Und ja, das stand ohnehin zu vermuten. > Warum einfach wenn's auch kompliziert geht. Und warum zeigst Du uns nicht die bzw. Deine optimale Lösung in C / C++?
:
Bearbeitet durch User
Ralf schrieb: > Es heißt die 'optimale' MaWin. Ach? Schon einmal daran gedacht, dass ich das extra so geschrieben habe?
Wilhelm M. schrieb: > Und warum zeigst Du uns nicht die bzw. Deine optimale Lösung in C / C++? Sag bloß das verhindert jetzt eine ordentliche Begründung :)
Ralf schrieb: > Wilhelm M. schrieb: >> Und warum zeigst Du uns nicht die bzw. Deine optimale Lösung in C / C++? > > Sag bloß das verhindert jetzt eine ordentliche Begründung :) Ziemlich. Ja. Und natürlich, dass es vollkommen offtopic ist. Niemand ist an deiner optimalstesten Lösung interessiert. Schon einmal daran gedacht, dass es nicht nur ein Optimumse gibt?
MaWin schrieb: > Niemand ist an deiner optimalstesten Lösung interessiert. > Schon einmal daran gedacht, dass es nicht nur ein Optimumse gibt? Ihr beide seid mir schon ein paar Spezialisten, selbst wenn ihr woanders vielleicht konstruktivere Rollen spielen mögt. Ich will Euch aber hier nicht weiter an Schwachstellen (auch von C) piesacken solange der TO nicht ausdrücklich zustimmt :)
Ralf schrieb: > MaWin schrieb: >> Niemand ist an deiner optimalstesten Lösung interessiert. >> Schon einmal daran gedacht, dass es nicht nur ein Optimumse gibt? > > Ihr beide seid mir schon ein paar Spezialisten, selbst wenn ihr woanders > vielleicht konstruktivere Rollen spielen mögt. Danke für die Blumen. In meinen Augen bist Du hier der Oberspezialist, schwafelst die ganze Zeit rum ohne irgendwie konkret zu werden. Der TO wollte eine Erklärung und eine Lösung für sein Problem in C / C++. Du redest die ganze Zeit rum, dass das alles Quatsch sei und Du eine viel bessere Lösung hast. Auf Nachfragen dazu kommt von Dir überhaupt gar nichts. Das nenne ich wirklich mal "konstruktiv". Solche Kollegen, die einem nur ein Ohr abkauen, ohne irgendwie etwas sinnvolles beizutragen wünscht man sich wirklich. Im echten Leben fliegen die aus dem Team. Hier können leider Gäste immer wieder herum trollen.
Wilhelm M. schrieb: > das alles Quatsch sei Nein. Aber in diesem Fall suboptimal, unnötig und überflüssig weil es eine bessere Lösung gibt: Einfach die Sekunden mit in der ISR zählen! Das kannst Du nun gerne zum x.Mal nicht zur Kenntnis nehmen oder endlich die Begründung liefern warum es so einfach nicht geht.
Ralf schrieb: > Wilhelm M. schrieb: >> das alles Quatsch sei > > Nein. Aber in diesem Fall suboptimal, unnötig und überflüssig weil es > eine bessere Lösung gibt: Einfach die Sekunden mit in der ISR zählen! Damit hast Du dann dasselbe Problem. Und damit wir nicht aneinander vorbei reden: zeige uns den Code, und nimmt gerne das Minimalbeispiel, was ich oben schon geschrieben habe. Aber bleibe in der Struktur vom TO: die ISR zählt und der non-ISR-Code wertet aus (bspw. alle 4711 Sekunden ein Ausgabe oder irgendein MCU-Register setzen reicht auch).
Wilhelm M. schrieb: > Ich habe mal was zusammengestellt: Das mag meist funktionieren, scheint mir aber nicht 100%ig wasserdicht zu sein. Wenn ich die AVR-Dokumentation (https://www.nongnu.org/avr-libc/user-manual/group__avr__cpufunc.html) von _MemoryBarrier() lese, sehe ich einen expliziten Hinweis (Link "Problems with reordering code") darauf, dass die _MemoryBarrier() lediglich Zugriffe auf volatile Variablen vor dem (u.U. durch Optimierungen vom Compiler "beschlossenen") Reordering schützt. Ich würde daraus schliessen, dass lediglich die Kombination von _MemoryBarrier() und volatile Variablenzugriffen 100%ig "wasserdicht" ist?
Wilhelm M. schrieb: > Damit hast Du dann dasselbe Problem. Bitte erzähle doch keinen Blödsinn. Weshalb denn? Wilhelm M. schrieb: > Aber bleibe in der Struktur vom TO: die ISR zählt und der non-ISR-Code > wertet aus Die ist ja hier das Problem. Begreifst Du das nicht?
Markus F. schrieb: > dass die _MemoryBarrier() > lediglich Zugriffe auf volatile Variablen vor dem (u.U. durch > Optimierungen vom Compiler "beschlossenen") Reordering schützt. Falsch übersetzt! Die Google Transe macht daraus: Implementieren Sie eine Lese-/Schreibspeicherbarriere. Eine Speicherbarriere weist den Compiler an, keine Speicherdaten in Registern jenseits der Barriere zwischenzuspeichern. Dies kann manchmal effektiver sein, als bestimmte Optimierungen zu blockieren, indem ein Objekt mit einem flüchtigen Qualifizierer deklariert wird.
Ralf schrieb: > Die ist ja hier das Problem. Genau, und das ist die Vorgabe. > Begreifst Du das nicht? Natürlich. Und ich verstehe auch, dass Du ein LED-Blinki komplett in der ISR laufen lassen willst und der non-ISR Code macht nur die Initialisierungen. Nur kommst Du damit eben im echten Leben nicht weit. Deswegen hat es sich etabliert, einen sys-tick zu haben, der bspw. eben Sekunden zählt. Das war die Anforderung des TO. Jetzt bist Du drann. Aber mit Code ...
EAF schrieb: > Falsch übersetzt! Huh? Hast Du dir den "Problems with reordering code"-Link durchgelesen?
Wilhelm M. schrieb: > Genau, und das ist die Vorgabe. Falsch. Der TO möchte seine Sekunden richtig gezählt bekommen. Was "Vorgabe" ist legst sicher nicht Du fest. Wilhelm M. schrieb: > Nur kommst Du damit eben im echten Leben nicht weit. Klasse Begründung. Hut ab. > Deswegen hat es sich etabliert, einen sys-tick zu haben, der bspw. eben > Sekunden zählt. So ist das auch richtig. Nichts anderes bezeichne ich hier als richtige Lösung: Vollständige Verwaltung einer Aufgabe auf einer Programmebene- Hier im Systick Interrupt.
Ralf schrieb: > So ist das auch richtig. Und warum willst du diese richtige Lösung dann auf Biegen und brechen verhindern? Wenn der Systick mehr als 8 Bit breit ist, braucht es eben atomaren Zugriff darauf. Auch wenn es eine Sekunden-Variable ist, die in der ISR hochgezählt wird.
Ralf schrieb: > Nichts anderes bezeichne ich hier als richtige Lösung: Vollständige > Verwaltung einer Aufgabe auf einer Programmebene- Hier im Systick > Interrupt. Da es immer noch kein Code ist, den Du zeigst, denke ich mal, dass Du von C / C++ oder ASM überhaupt keine Ahnung hast. Du hast noch nie eine Zeile Code geschrieben und liest Computer-Bild. Daraus kennst Du nur ein paar Buzzwords.
Εrnst B. schrieb: > Auch wenn es eine Sekunden-Variable ist, die in der ISR hochgezählt > wird. Die aber jetzt stets konstant zählt, das ist der Unterschied. Die in diesem Fall übrigens auch 8bittig bleibt. Da beißt die Maus keinen Faden dran ab: >8bittige Schreibzugriffe von Main auf Interrupt-Variablen sind Pfusch und daher zu vermeiden, wenn das Programm nicht komplizierter werden soll als nötig.
Ralf schrieb: > Εrnst B. schrieb: > >> Auch wenn es eine Sekunden-Variable ist, die in der ISR hochgezählt >> wird. > > Die aber jetzt stets konstant zählt, das ist der Unterschied. Die in > diesem Fall übrigens auch 8bittig bleibt. > Da beißt die Maus keinen Faden dran ab: >8bittige Schreibzugriffe von > Main auf Interrupt-Variablen sind Pfusch und daher zu vermeiden, wenn > das Programm nicht komplizierter werden soll als nötig. Wilhelm M. schrieb: > Du hast noch nie eine Zeile Code geschrieben und liest Computer-Bild. > Daraus kennst Du nur ein paar Buzzwords. Mehr als solche unsachlichen Aussagen erwarte ich von Dir leider nicht mehr. Verständlich wenn man fachlich so in der Sackgasse steckt.
Ralf schrieb: > Die in > diesem Fall übrigens auch 8bittig bleibt. d.H. Alle Anwendungen die Zeitintervalle > 255 Sekunden benötigen sind Pfusch? Seltsame Einstellung.
Εrnst B. schrieb: > Alle Anwendungen die Zeitintervalle > 255 Sekunden benötigen sind > Pfusch? > Seltsame Einstellung. Man muss es nur richtig machen Ernst B. Die Zeitintervalle nämlich vollständig im Interrupt verwalten.
Ralf schrieb: > Εrnst B. schrieb: >> Alle Anwendungen die Zeitintervalle > 255 Sekunden benötigen sind >> Pfusch? >> Seltsame Einstellung. > > Man muss es nur richtig machen Ernst B. > Die Zeitintervalle nämlich vollständig im Interrupt verwalten. Code?
Ralf schrieb: > Die Zeitintervalle nämlich vollständig im Interrupt verwalten. Super. Wenn ich haufenweise unterschiedliche Zeitintervalle brauche, die evtl. sogar noch online konfiguriert werden sollen, dann muss ich jedes mal das Programm neu Kompilieren, damit die ISR die richtigen Zeitintervalle enthält? Statt einfach sekunden hochzuzählen, und meine Tasks daran Auszurichten? Was ist da wohl komplizierter? Ralf schrieb: > wenn > das Programm nicht komplizierter werden soll als nötig. Echt? Beispiel, aus einem Projekt mit Stromspar-AVR + Uhrenquarz:
1 | static uint32_t seconds; |
2 | |
3 | static uint32_t getSeconds() { |
4 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { |
5 | return seconds; |
6 | }
|
7 | return 0; // Unreachable, Silence Compiler warning |
8 | }
|
9 | |
10 | ISR(TIMER2_OVF_vect) { |
11 | seconds += 1; |
12 | }
|
Und das findest du jetzt so extrem unfassbar kompliziert?
:
Bearbeitet durch User
Εrnst B. schrieb: > Wenn ich haufenweise unterschiedliche Zeitintervalle brauche, die evtl. > sogar noch online konfiguriert werden sollen, dann muss ich jedes mal > das Programm neu Kompilieren, damit die ISR die richtigen Zeitintervalle > enthält Variable ISR-Zeitintervalle stehen hier gar nicht zur Debatte. Wenn es sie aber braucht und diese vom Programmierer nicht vorhersehbar sind kann man immer noch auf kompliziertere Übergabeverfahren zurückgreifen. Εrnst B. schrieb: > extrem unfassbar kompliziert Wer redet von "unfassbar"? Ich rede von Ralf schrieb: > suboptimal, unnötig und überflüssig So, ich bin dann mal bei der "electronica"... :)
Ralf schrieb: > Da beißt die Maus keinen Faden dran ab: >8bittige Schreibzugriffe von > Main auf Interrupt-Variablen sind Pfusch und daher zu vermeiden, wenn > das Programm nicht komplizierter werden soll als nötig. Aha. 4 kleine unbedeutende Anmerkungen. 1. halbwegs Moderne AVR´s haben auch durchaus atomare 16-Bit Operationen (z.B. movw). Wenn diese gezielt genutzt werden, dann muß man auch bei gemischter Verwendung ISR/main keine Klimmzüge machen. 2. Lesezugriffe sind nicht mehr oder weniger gefährdet als Schreibzugriffe 3. Pfusch ist der falsche Begriff. Man muß wissen, was man tut. Aber das haben schon viele vorher getan, und es gibt haufenweise HowTos. 4. Auch 8-Bit kann fehlschlagen. (z.B. Bitmanipulationen). Gruß Robert
Ralf schrieb: > Unfug. Man muß es nur richtig anstellen. Hochsprache macht sich viele > Probleme selber. Äähmm ... Moby? Bist du's?
Ralf G. schrieb: > Äähmm ... Moby? Bist du's? Natürlich ist er das. Das war doch schon lange klar. Oliver
So, jetzt habe ich mir das hier mal angeschaut: https://www.nongnu.org/avr-libc/user-manual/optimization.html#optim_code_reorder >However, memory barrier works well in ensuring that all volatile accesses > before and after the barrier occur in the given order with respect to the > barrier. However, it does not ensure the compiler moving > non-volatile-related statements across the barrier. > Peter Dannegger provided a nice example of this effect: Dieser Absatz ist leider kompletter Blödsinn. Tut mir leid Peter, aber du hast Barriers nicht verstanden. In dem Beispiel wandert eine lokale Variable über die Barrier hinweg. Was natürlich erlaubt ist. Bei *memory*-Barriers geht es natürlich um *memory*-Zugriffe. Und *memory*-Zugriffe, auch nicht-volatile, werden natürlich nicht über die Barrier hinweg verschoben. Lokale Variablen können per Definition keine Data-Races oder Orderingprobleme mit ISRs oder anderen CPUs erzeugen, weil sie eben lokal sind. (Solange es keinen globalen Zeiger darauf gibt. Aber dann greift die Memory Barrier wieder)
der_eine schrieb: > Wenn diese gezielt genutzt werden, dann muß man auch bei gemischter > Verwendung ISR/main keine Klimmzüge machen. Kann man, muß man aber hier nicht. > Lesezugriffe sind nicht mehr oder weniger gefährdet als Schreibzugriffe Lesezugriffe ändern nichts an einer schreibverwalteten Variable der ISR. Man kann sie auch ggf. zu einem unbedenklichen Zeitpunkt ausführen wenn kein Überlauf zu erwarten ist und hinterher auf Plausibilität prüfen. > Man muß wissen, was man tut. Sehr diplomatisch weil immer zutreffend. > Auch 8-Bit kann fehlschlagen. (z.B. Bitmanipulationen). Nicht beim Schreiben von tatsächlich 8 Bits. MaWin schrieb: > Lokale Variablen können per Definition keine Data-Races oder > Orderingprobleme mit ISRs oder anderen CPUs erzeugen, Gut erkannt. Die Regel dann bitte hier nur noch praktisch anwenden und schon sind wir uns einig :)
Ralf schrieb: >> Auch 8-Bit kann fehlschlagen. (z.B. Bitmanipulationen). > > Nicht beim Schreiben von tatsächlich 8 Bits. Ach?
1 | uint8_t var; |
2 | |
3 | void main_func(void) |
4 | {
|
5 | var = var ^ 0x03; |
6 | }
|
7 | |
8 | void ISR(void) |
9 | {
|
10 | var = var ^ 0x30; |
11 | }
|
Nur 8-Bit Schreib- und Lesezugriffe. Trotzdem nicht atomisch.
Ralf schrieb: > Gut erkannt. Die Regel dann bitte hier nur noch praktisch anwenden und > schon sind wir uns einig :) Und wozu? Wenn ich es gerne anders machen möchte oder anders machen muss, dann mache ich es halt anders. Du darfst gerne mir die Bewertung überlassen, was besser und was schlechter ist. Deine Lösung ist eine Lösung. Aber ob sie besser ist, das bewertest nicht du für andere.
Ralf schrieb: > Lesezugriffe [..] > zu einem unbedenklichen Zeitpunkt ausführen wenn > kein Überlauf zu erwarten ist und hinterher auf Plausibilität prüfen. Und das soll besser sein, als einen "unbedenklichen Zeitpunkt" einfach per CLI/SEI zu erzwingen, und dann ohne extra Plausibilitätsprüfung immer garantiert valide Daten zu lesen? Kriegst du Geld zurück, wenn du die CLI-Instruktion ungenutzt lässt? Hat dir CLI irgendwann mal was Böses angetan, dass du jetzt soviel Angst davor hast? Glaubst du die Grünen schmeißen dich aus der Partei, weil du "ATOMIC_BLOCK" in deinem Quelltext stehen hast?
MaWin schrieb: > Nur 8-Bit Schreib- und Lesezugriffe. Trotzdem nicht atomisch. Richtig, wegen der unklaren Reihenfolge. Ein Grund mehr, die Variable VAR schreibend entweder in Main ODER in der ISR zu verwalten. > Aber ob sie besser ist, das bewertest nicht du für andere. Das ist ganz rational entscheidbar: Sie konstruiert keine Gründe um diese hinterher wieder ausbügeln zu müssen. Einfacher ist besser. Das sollte hinter jedem Programmierer- Ohr geschrieben stehen! Εrnst B. schrieb: > Und das soll besser sein, als einen "unbedenklichen Zeitpunkt" einfach > per CLI/SEI zu erzwingen Es ging um die Behauptung der_eine schrieb: > Lesezugriffe sind nicht mehr oder weniger gefährdet als Schreibzugriffe Das macht nämlich durchaus einen Unterschied. Εrnst B. schrieb: > Glaubst du die Grünen schmeißen dich aus der Partei, weil du > "ATOMIC_BLOCK" in deinem Quelltext stehen hast? Was sich durch durch anderes Herangehen an ein Problem vermeiden lässt muss nicht hinterher mit zusätzlicher Bürokratie bekämpft werden. Aber letztere mögen wir Deutsche ja so sehr, vielleicht weht daher der Wind.
Alles klar, du verstehst es wirklich gut, deine ASM Begeisterung vorzüglich rüber zu bringen. Mit deinen Argumenten wirst du sicherlich 80% aller C++ Schreiber hier überzeugen. (natürlich auch die C Helden) Alternativ: Möchtest du nicht einen Youtubekanal eröffnen um als gottgleicher Influencer die Welt von deinen Ansichten zu überzeugen? Dann hast du was zu tun, und wir sind dich los.
EAF schrieb: > ASM Begeisterung > C++ Schreiber hier überzeugen Falls Du es noch nicht mitbekommen hast: Es geht hier nicht primär um die Programmiersprache. Aber natürlich hat eine jede ihr Licht und ihren Schatten und ihren sinnvollen Anwendungsbereich. Wenn ich hier auf Asm zu sprechen kam dann weil der TO auf dieser Ebene sein Problem viel besser durchschaut hätte.
Ralf schrieb: > Aber natürlich hat eine jede ihr Licht und ihren > Schatten und ihren sinnvollen Anwendungsbereich. > Wenn ich hier auf Asm zu sprechen kam dann weil der TO auf dieser Ebene > sein Problem viel besser durchschaut hätte. Richtig. Und in einer modernen Sprache wie Rust sind solche Data-Races, wie die vom TO, gar nicht erst formulierbar. Dort wäre er gezwungen worden es zu durchschauen, weil das Programm nicht compilierbar gewesen wäre. Und nun? Asm scheiße? C scheiße? Alles verbrennen? Sicher nicht. Die Übergänge und Entscheidungen sind praktisch nie so schwarz/weiß, wie du sie hier darstellst. Ich stimme dir ja durchaus zu, dass Einfachheit oft besser ist. Aber halt nicht immer. Einfachheit kann auch dazu führen, dass das Programm in der späteren Weiterentwicklung nicht mehr skaliert. Dein Vorschlag funktioniert schließlich nicht weit über das Trivialbeispiel hinaus.
MaWin schrieb: > Und in einer modernen Sprache wie Rust sind solche Data-Races, wie die > vom TO, gar nicht erst formulierbar. Geht Datentausch zwischen Interrupt und Application in Rust ohne "unsafe"? Wenn ja, was ist die zugrundeliegende Logik, auf die das hinausläuft? Interrupt in der Application sperren?
Nop schrieb: > Geht Datentausch zwischen Interrupt und Application in Rust ohne > "unsafe"? Selbstverständlich. > Wenn ja, was ist die zugrundeliegende Logik, auf die das > hinausläuft? Kein Aliasing. Nur ein Schreibzugriff auf Variablen wird erzwungen. Synchronisation oder atomic zwischen Threads und/oder ISRs wird statisch erzwungen. Also praktisch alles das, was man in C sowieso einhalten muss, wird erzwungen. > Interrupt in der Application sperren? Natürlich. Oder atomic. Und zwar statisch erzwungen. Aber ich glaube das ist hier definitiv offtopic :)
Ralf schrieb: > EAF schrieb: >> ASM Begeisterung >> C++ Schreiber hier überzeugen > > Falls Du es noch nicht mitbekommen hast: Es geht hier nicht primär um > die Programmiersprache. Aber natürlich hat eine jede ihr Licht und ihren > Schatten und ihren sinnvollen Anwendungsbereich. > Wenn ich hier auf Asm zu sprechen kam dann weil der TO auf dieser Ebene > sein Problem viel besser durchschaut hätte. Bla bla bla ...
MaWin schrieb: > Dein Vorschlag > funktioniert schließlich nicht weit über das Trivialbeispiel hinaus. Was erstmal zu beweisen wäre. Die Behauptung steht mir zu einsam im Raum. Selbst für variabele Zeitperioden und für die zeitliche Synchronisation von Prozessen bin ich bei AVR bislang sehr gut mit der Verwaltung in einem zentralen Systick-Interrupt gefahren der eine benötigte Anzahl von zweckgebundenen 32-Bit Variablen zum Beispiel mit 100Hz schlicht immer nur stur auf Null herunterzählt. Zugehörige Prozesse stellen nun einen beliebigen Byte-Anteil ihres Zählers auf das was sie brauchen und überwachen beliebige Byte-Anteile die sie brauchen. Auf schön einfache eindeutig-atomare Weise ohne irgendwelche Interrupts aufzuhalten oder sonstiges Management. Das macht sich natürlich besonders schön & transparent in Asm. Diese Vorgehensweise hat mir bislang noch jedes zeitliche Steuerungsproblem in Anwendungen erschlagen - auch wenn sie mit einer gewissen Ungenauigkeit behaftet ist. Für noch genauere Messungen stehen dann die diversen AVRxDx Hardwaretimer exklusiv zur Verfügung. Und für noch höhere Ansprüche nimmt man ohnehin andere Controller. Dass wir hier (etwas sehr von oben herab) von einer "Simpelst" MCU für einfachere Aufgaben sprechen sollte schon noch im Hinterkopf bleiben.
Ralf schrieb: > Und für noch höhere Ansprüche nimmt man ohnehin andere Controller. > Dass wir hier (etwas sehr von oben herab) von einer "Simpelst" MCU für > einfachere Aufgaben sprechen sollte schon noch im Hinterkopf bleiben. Jetzt fängt der ASM Priester auch noch über die glänzende Portabilität von seinem unsichtbaren Code an zu schwafeln. Portabel zwischen verschiedensten Systemen... Ein Genie in Tüten. Viele Grüße aus Absurdistan.
Ralf schrieb: > Selbst für variabele Zeitperioden und für die zeitliche Synchronisation > von Prozessen bin ich bei AVR bislang sehr gut mit der Verwaltung in > einem zentralen Systick-Interrupt gefahren der eine benötigte Anzahl von > zweckgebundenen 32-Bit Variablen zum Beispiel mit 100Hz schlicht immer > nur stur auf Null herunterzählt. Zugehörige Prozesse stellen nun einen > beliebigen Byte-Anteil ihres Zählers auf das was sie brauchen und > überwachen beliebige Byte-Anteile die sie brauchen. Auf schön einfache > eindeutig-atomare Weise ohne irgendwelche Interrupts aufzuhalten oder > sonstiges Management. Das ist zwar kein Code, aber irgendwie der Ansatz eines Algorithmus. Verstehe ich das richtig: Du hast für jedes benötigte Intervall einen eigenen 32Bit-Zähler, der von der periodischen ISR@100Hz dekrementiert wird. Die Startwerte dieser Zähler entstehen durch einen 8Bit-Wert, der entsprechend geschoben wird, also etwa um 1Bit, dann kannst Du Intervalle von 0,02...5,1s mit Quantisierung 0,2s abbilden, oder etwa um 9bit, dann kannst Du Intervalle von 5,12...1305,6s mit Quantisierung 5,12s abbilden. In der main()-loop machst Du dann nun ein polling? Von was?
EAF schrieb: > Viele Grüße aus Absurdistan Mit solchen "Beiträgen" ganz sicher. Wilhelm M. schrieb: > Das ist zwar kein Code, aber irgendwie der Ansatz eines Algorithmus Das war zwar wieder nicht die geforderte Begründung, aber irgendwie der Ansatz einer Rückkehr zur Sache. > Verstehe ich das richtig Ich hab noch nicht den Eindruck. Ihr denkt per default zu kompliziert. Bei 100Hz zählt das niederwertigste Byte1 bis 256* 10ms = 2,56 Sek das zweite deckt einen Bereich von 2,56Sek *256 Sek= 10,92min ab, das dritte 10,92min *256= 46,6 Stu, das vierte Byte bis 497 Tage ab. Die angesprochene Ungenauigkeit ergibt sich bei 100Hz zu +-10ms startend beim ersten Byte, sollte das nicht langen tut dem AVR meist auch die 10fache Zählfrequenz nicht weh, was ich aber noch nie gebraucht habe. 1 Tag abzubilden bedeutet also z.B. einen Zähler von 83D600H, und da ist dann das nicht-atomare Stellen UND Abfragen auch herzlich wurscht. Main-Programme prüfen einfach auf Null oder jeden anderen gewünschten Zählerwert ihres zugeordneten Zählers, idealerweise nur eines der 4 Zälerbytes. Zusätzlich zählen 100Hz Timer-Interrupt oder auch ein Sekunden-Interrupt der RTC einen Byte- oder Wordzähler aufwärts, um dem Programm eine globale Zeit-Rasterung zur Abstimmung unterschiedlichster Prozesse anzubieten. Prophylaktisch sei noch ganz gelassen gesagt: Wichtig ist, daß dieses Timingprocedere funktioniert, weniger wichtig, hier die Zustimmung noch des letzten Experten (oder gar Trolls) zu erhalten :)
Ralf schrieb: > und da ist > dann das nicht-atomare Stellen UND Abfragen auch herzlich wurscht. Ach. Du ignorierst das Problem also einfach nur. Das ist ja mal innovativ.
MaWin schrieb: > Du ignorierst das Problem also einfach nur. Da gibts beim Stellen kein Problem solange das erste Byte ungleich Null bleibt (maximal ein ZählerInterrupt dazwischen, besagte Ungenauigkeit 10ms) und beim Nulltesten genauso wenig: Einfach mit dem höchsten Byte anfangen. Je nach Genauigkeitsanspruch (der in aller Regel mit längeren Zeiträumen über Minuten, Stunden, Tage sinkt) langt aber meist, nur ein einzelnes der 4 Zählerbytes dem Zeitraum entsprechend in den Blick zu nehmen.
MaWin schrieb: > Du ignorierst das Problem also einfach nur. > Das ist ja mal innovativ. Ich stelle hier ein allgemeines Zeitmanagement- System vor MaWin. Die Lösung für den TO bleibt deshalb dieselbe: Sekunden in der ISR zählen!
Igor schrieb: > Da gibts beim Stellen kein Problem solange das erste Byte ungleich Null > bleibt (maximal ein ZählerInterrupt dazwischen, besagte Ungenauigkeit > 10ms) und beim Nulltesten genauso wenig: Einfach mit dem höchsten Byte > anfangen. Die Reihenfolge, in der die Bytes geschrieben werden, ist implementation defined und damit nicht portabel. Super "Lösung". Zähler ist 0. main stellt Zähler auf 0x100. Erst MSB, dann LSB, weil der Compiler das halt so entschieden hat. Unterbrechung zwischen MSB- und LSB-Schreiben. ISR dekrementiert zu 0xFF main schreibt LSB. Zähler ist 0 statt der erwarteten Werte 0x100 oder 0xFF. Das ist ein Fehler von nahezu 100%.
MaWin schrieb: > weil der Compiler das halt so entschieden hat Tja das wäre mit Asm nicht passiert :) Die Lösung für den TO bleibt trotzdem die Gleiche.
Ralf schrieb: > Tja das wäre mit Asm nicht passiert :) Ach. Echt? Dann lass uns mal einen umgekehrten Schreibzugriff betrachten. Zähler ist 0x300. main stellt Zähler auf 0xF00. Erst LSB, dann MSB. Unterbrechung zwischen LSB- und MSB-Schreiben. ISR dekrementiert zu 0x2FF main schreibt MSB. Zähler ist 0xFFF statt der erwarteten Werte 0xF00 oder 0xEFF. Echt super Verfahren. Funktioniert in keiner Schreibreihenfolge korrekt.
MaWin schrieb: > Zähler ist 0x300. > main stellt Zähler auf 0xF00. Erst LSB, dann MSB. > Unterbrechung zwischen LSB- und MSB-Schreiben Igor schrieb: > Da gibts beim Stellen kein Problem solange das erste Byte ungleich Null > bleibt (maximal ein ZählerInterrupt dazwischen, besagte Ungenauigkeit > 10ms)
Igor schrieb: > solange das erste Byte ungleich Null Ja genau. Und wenn doch, dann knallt es eben massiv. Diese "Lösung" ist maximal eine Speziallösung, die in ganz bestimmten Anwendungsfällen, wo man wirklich die zwei Takte für die Interruptsperre sparen muss, Anwendung finden kann. Einmal nicht "aufgepasst" und den Timer vor Ablauf neugestartet und schon knallt es. Oder es knallt halt ständig, weil der Compiler die Schreibreihenfolge selbst festlegt. Da muss man also selbst von Hand herumfrickeln. Alles unschön und unelegant.
Es ist keine gute Lösung wenn man unter den Beschränkungen von C zu leiden hat, zugegeben. Die meisten Timingaufgaben sind aber erstens mit nur einem, dem ersten Zählerbyte in der Spanne bis 2,56 Sek erledigt und bei seltener verwendeten, längeren Zeiträumen sind die Ansprüche an die Genauigkeit geringer daß man meist doch nur mit einem betrachteten Byte des Zählers auskommt. Die Lösung, seine Sekunden gleich mit in der ISR zu zählen bleibt trotzdem die bessere und ist ebenso in C realisierbar.
Ralf schrieb: > Es ist keine gute Lösung Na da bin ich ja froh, dass du doch noch einsichtig bist. Ralf schrieb: > Die Lösung, seine Sekunden gleich mit in der ISR > zu zählen bleibt trotzdem die bessere Die Bessere (tm). In Jedem Fall (tm). Ultimativ Beste (tm).
MaWin schrieb: > Interruptsperre = > unschön und unelegant! Würde ich gleich hinter dumpfen Polling-Schleifen im Hauptprogramm einordnen.
Igor schrieb: > Würde ich gleich hinter dumpfen Polling-Schleifen im Hauptprogramm > einordnen. Möglichst komplexe und möglichst trickreiche Programme mit vielen verborgenen Bedingungen (z.B. laufenden Zähler niemals neustarten) ist für dich also elegant? Was spricht gegen eine "dumpfe Polling-Schleife", wenn damit das Problem auf einfache und gut nachvollziehbare Art und Weise vollständig gelöst wird. Hängt es vielleicht von der Situation ab, was elegant ist?
MaWin schrieb: > Interruptsperre Die Un-schönheit von Notbremsen liegt nicht nur im Auge des Betrachters. Sollten halt für den Notfall dienen und nicht zum Fahren auf der Autobahn. > Hängt es vielleicht von der Situation ab, was elegant ist? Was ein System ohne Not aufhält ist objektiv messbar.
Hat denn schon jemand den magischen Zauber-Code von Ralph oder Igor gesehen, der ohne Interrupt-Sperre auskommt? Egal in welcher Programmiersprache? Oder existiert der nur in deren Phantasie? So als reine Behauptung, dass es irgendwie möglich sein könnte, ist das ja nicht mehr wert als die Behauptung, dass Einhörner Regenbogen kotzen.
Εrnst B. schrieb: > Hat denn schon jemand den magischen Zauber-Code von Ralph Du siehst Dich also außerstande das Zählen der Sekunden der ISR hinzuzufügen? Was hast Du denn daran noch nicht verstanden?
Ralf schrieb: > Du siehst Dich also außerstande das Zählen der Sekunden der ISR > hinzuzufügen? > Was hast Du denn daran noch nicht verstanden? d.H. du bist nicht in der Lage, die paar Zeilen für die ISR hinzuschreiben? Ist es, weil du überhaupt nicht programmieren kannst sondern nur trollst, oder ist es, weil dir beim ersten Versuch aufgefallen ist, dass du da in einer Sackgasse steckst?
Beitrag #7257641 wurde von einem Moderator gelöscht.
Elektrischer Reiter schrieb im Beitrag #7257641: > Ralf schrieb: >> Du siehst Dich also außerstande das Zählen der Sekunden der ISR >> hinzuzufügen? > > Willst Du Dich wirklich mit solchen Leuten weiter unterhalten? Das ist > nicht Dein Ernst... Stimmt. Viel Sinn macht es wohl nicht wenn der Angesprochene nicht verstehen sondern nur polemisieren will.
Ralf schrieb: > Stimmt. Viel Sinn macht es wohl nicht wenn der Angesprochene nicht > verstehen sondern nur polemisieren will. Einsicht ist der erste Schritt zur Besserung. Gute Besserung, Ralf.
Ich möchte Dir ja Deine wartenden Pollingschleifen und globalen Internetblockaden gar nicht wegnehmen Mawin! Jeder wie er kann!
Ach, jetzt hast du's mit Ralf schrieb: > zu einem unbedenklichen Zeitpunkt ausführen wenn > kein Überlauf zu erwarten ist und hinterher auf Plausibilität prüfen. so spannend gemacht. Das würde ich echt gern sehen, wie du den Zeitpunkt bestimmst (ohne Polling? oder mit?) und wie du die Plausibilitätsprüfung implementierst. Aber war wohl nur heiße Luft.
Εrnst B. schrieb: > Das würde ich echt gern sehen, wie du den Zeitpunkt > bestimmst (ohne Polling? oder mit?) und wie du die Plausibilitätsprüfung > implementierst. Ich vermute, dass Magie bei der Implementierung involviert ist. Aber genaueres kann wohl nur Ralf sagen, wenn er endlich seinen Code zeigt.
Εrnst B. schrieb: > Das würde ich echt gern sehen, wie du den Zeitpunkt > bestimmst Der Zeitpunkt ist recht genau mit 1000ms definiert lieber Ernst. Aber das wolltest Du vermutlich jetzt nicht hören :)
Ralf schrieb: > Der Zeitpunkt ist recht genau mit 1000ms definiert lieber Ernst. 1000 ms ist eine Zeitspanne. Ein Intervall. Aber kein Zeitpunkt. Also: An welchem Zeitpunkt findet deine Abfrage statt? Gerne auch mit Beispielcode.
Ralf schrieb: > Der Zeitpunkt ist recht genau mit 1000ms definiert lieber Ernst. > > Aber das wolltest Du vermutlich jetzt nicht hören :) d.H. du hast eine ISR, die einmal pro Sekunde läuft, und im Hauptprogramm nochmal eine Polling-Schleife, die ebenfalls exakt eine Sekunde Zeit vertrödelt, damit du im Hauptprogramm exakt weißt wann die ISR durch ist, und du gefahrlos deine Variable auslesen kannst? Ok. Ist eine Lösung.
MaWin schrieb: > Ralf schrieb: >> Der Zeitpunkt ist recht genau mit 1000ms definiert lieber Ernst. > > 1000 ms ist eine Zeitspanne. Ein Intervall. Aber kein Zeitpunkt. > > Also: An welchem Zeitpunkt findet deine Abfrage statt? Gerne auch mit > Beispielcode. Der Zeitpunkt ist nach 1000ms erreicht, Mawin. Das zu erkennen hätte ich Dir schon zugetraut. Und auch auf die Gefahr hin daß es beim 115. Mal nicht begriffen wird: Bei 1000ms wird noch in der ISR die Sekunde weitergezählt (und meinetwegen ein Signal zum Messgerät des TO gegeben). Gerne können wir an exakt dieser Stelle weiter diskutieren. Die Idee ist so bestechend einfach, das schafft Ihr in der Programmiersprache Euer Wahl! Wer es freilich nicht bis zu dieser Stelle schafft hat meine Zeit nicht verdient :)
Der Vollständigkeit halber: Bei dem Code, den ich oben gepostet hatte Εrnst B. schrieb: > static uint32_t getSeconds() { ... Könnte ich tatsächlich auf den ATOMIC_BLOCK verzichten. Einfach weil der µC immer im Sleep steckt, und nur von exakt der ISR aufgeweckt wird, die auch den Sekunden-Zähler inkrementiert. Da dann ein main-Schleifendurchlauf immer weniger als eine Sekunde braucht bevor der µC wieder schlafen geht, wäre die Bedingung vom "unbedenklichen Zeitpunkt" durchgängig erfüllt. Aber: Warum sich auf solche Nebenbedingungen verlassen, wenn man's auch einfach "robust" programmieren kann?
Ralf schrieb: > Der Zeitpunkt ist nach 1000ms erreicht, Mawin. Und wie erfährt das Hauptprogramm davon? Gerne mit Beispielcode. Ralf schrieb: > Die Idee ist so bestechend einfach, das schafft Ihr in der > Programmiersprache Euer Wahl! Dann schaffst du es ja sicher auch hier Beispielcode zu posten. Εrnst B. schrieb: > Aber: Warum sich auf solche Nebenbedingungen verlassen, wenn man's auch > einfach "robust" programmieren kann? Weil man auf Krampf zwei Takte sparen muss, die man eh nicht anderweitig brauchen kann. Jedenfalls habe ich Ralf so verstanden. Oder damit das Programm garantiert kaputt geht, wenn man es in 6 Jahren noch einmal anfasst und die ganzen Randbedingungen nicht mehr kennt. Nur so findet Ralf das spannend, nehme ich an.
MaWin schrieb: > Und wie erfährt das Hauptprogramm davon? Wozu im Beispiel des TO? Bei Bedarf könnte es den 8-bittigen Sekundenzähler ja problemlos auslesen. Die Strategie ist richtig: Alles an Ort und Stelle zu erledigen spart ggf. problematischen Datenaustausch. Das würde ich "robust" nennen.
Εrnst B. schrieb: > Hat denn schon jemand den magischen Zauber-Code [...] > gesehen, der ohne Interrupt-Sperre auskommt? Ist sehr einfach mit dem sequential lock machbar, das ich schon vor vier Tagen vorgeschlagen habe: Beitrag "Re: AVR DB - oder doch ein Compiler-Fehler?"
Nop schrieb: > Ist sehr einfach mit dem sequential lock machbar, das ich schon vor vier > Tagen vorgeschlagen habe: Und das ist dann einfacher und besser als kurz IRQs abzuschalten?
Hallo, ich finde es zwar schon traurig wenn Palph immer nur um den heiße Brei redet, sowas macht man nicht, aber ... Wenn ich Ralph richtig verstehe, dann ist seine gesamte Zählerverwaltung in der ISR und nirgends woanders. Wenn er verschiedene "Zähler" benötigt stehen die alle in der ISR. Am Ende wird Ralph ein 'bool' für jeden Zähler zurückgeben und in der Main auswerten und ggf. auch in der Main zurücksetzen. Da bool 8Bit/1Byte sind ist das automatisch atomar und Bedarf keiner weiteren Behandlung. Korrekt? Nur falls Ralph Zählerwerte größer 1 Byte in die Main zurückgibt Bedarf es atomarer Behandlung. Wenn er das nicht macht hat er ein Problem.
MaWin schrieb: > Nop schrieb: >> Ist sehr einfach mit dem sequential lock machbar, das ich schon vor vier >> Tagen vorgeschlagen habe: > > Und das ist dann einfacher und besser als kurz IRQs abzuschalten? Nö. Das ist komplizierter, langsamer, und bietet nochmal Fallstricke, z.B. bzgl. Compiler-Optimierung. Stattdessen könnte man es auch einfach richtig machen, und sich 100%ig sicher sein, dass man immer und jederzeit einen gültigen Wert lesen kann. Aber da geht das Abenteuer verloren. Muss ja spannend bleiben.
Εrnst B. schrieb: > Könnte ich tatsächlich auf den ATOMIC_BLOCK verzichten. Hab einen Luftsprung gemacht. Εrnst B. schrieb: > Da dann ein main-Schleifendurchlauf immer weniger als eine Sekunde > braucht bevor der µC wieder schlafen geht, wäre die Bedingung vom > "unbedenklichen Zeitpunkt" durchgängig erfüllt. Ich frage Dich und alle hier: Wenn das "Sekundenpuls" Beispiel des TO schon eine vollständige Anwendung wär: Wie könnte man diese einfacher und eleganter und robuster implementieren als in Main energie- und komplexitätssparend zu schlafen (sprich auf eine Programmebene ganz zu verzichten) und die übersichtliche Aufgabe in einem einzigen Interrupt zu erledigen? Veit D. schrieb: > Korrekt? Korrekt! Veit D. schrieb: > Nur falls Ralph Zählerwerte größer 1 Byte in die Main zurückgibt Bedarf > es atomarer Behandlung. In Asm weniger, in C viel mehr. Siehe weiter oben.
Εrnst B. schrieb: > Nö. Das ist komplizierter, langsamer, und bietet nochmal Fallstricke, > z.B. bzgl. Compiler-Optimierung. Dachte ich es mir doch ;) > Stattdessen könnte man es auch einfach richtig machen, und sich 100%ig > sicher sein, dass man immer und jederzeit einen gültigen Wert lesen > kann. Ich habe auch schon trickreiche Konstrukte wie z.B. seqlocking verwendet. Aber nicht ohne Not. Wenn das mit normalen atomic-blocks lösbar ist, dann nehme ich die. Ist viel einfacher und wartbarer. Ralf hat bis heute keine Begründung und keinen Beispielcode liefern können, der belegt, dass es besser und schlauer ist grundsätzlich auf atomic-blocks zu verzichten. Das bestätigt mich nur. > Aber da geht das Abenteuer verloren. Muss ja spannend bleiben. Aus dem Alter bin ich raus :)
Ralf schrieb: > Ich frage Dich und alle hier: > Wenn das "Sekundenpuls" Beispiel des TO schon eine vollständige > Anwendung wär: Wie könnte man diese einfacher und eleganter und robuster > implementieren als in Main energie- und komplexitätssparend zu schlafen > (sprich auf eine Programmebene ganz zu verzichten) und die > übersichtliche Aufgabe in einem einzigen Interrupt zu erledigen? Völlig richtig. Leider sind die wenigsten Programme so trivial, dass sie nur Sekunden zählen.
MaWin schrieb: > Und das ist dann einfacher und besser als kurz IRQs abzuschalten? Ja. Zumal ein sequential lock nicht die worst-case-Latenz der Interrupts erhöht. Außerdem hält es den Kontrollfluß sauber, aber das ist wohl auch Geschmackssache. Deswegen ist es meine zweitliebste Lösung - nach atomarem Zugriff (sofern möglich), also auf diesem µC mit 8-Bit-Variablen. Interrupts sperren würde ich allerdings, wenn Interrupt-Settings verändert werden sollen, oder wenn etwas gemacht wird, wo keine Interrupts kommen sollen, z.B. Flashprogrammierung.
Nop schrieb: > Ja. Zumal ein sequential lock nicht die worst-case-Latenz der Interrupts > erhöht. Was, wenn mir die worst-case-Latenz des Interrupts (es geht hier um 2, 3, vielleicht 4 Takte) in der Größenordnung egal ist? Was, wenn mir die erhöhte Latenz durch das seqlock in der Hauptschleife nicht egal ist? > Außerdem hält es den Kontrollfluß sauber, Ein seqlock hält den Kontrollfluss sauber? Hast du ein neues Wort gelernt und versuchst es jetzt auf Krampf zu verwenden?
MaWin schrieb: > Was, wenn mir die worst-case-Latenz des Interrupts (es geht hier um 2, > 3, vielleicht 4 Takte) in der Größenordnung egal ist? Es spielt keine Rolle, ob es Dir egal ist. > Was, wenn mir die erhöhte Latenz durch das seqlock in der Hauptschleife > nicht egal ist? Dann ist der Punkt erreicht, an dem Du über ein RTOS nachdenken solltest. > Hast du ein neues Wort gelernt und versuchst es jetzt auf Krampf zu > verwenden? Du mich auch.
Nop schrieb: > Dann ist der Punkt erreicht, an dem Du über ein RTOS nachdenken > solltest. Ja genau. Um atomic-blocks zu vermeiden ein RTOS verwenden. Hast du wieder getrunken? Kein Bier vor vier.
MaWin schrieb: > Um atomic-blocks zu vermeiden ein RTOS verwenden. Nope. Du hast auch diesen Punkt nicht verstanden. > Kein Bier vor vier. Bezeichnend, was so alles in Deinem Kopf vorgeht. Merkt man Deinen Beiträgen gerade auch an.
Ralf schrieb: > Ich frage Dich und alle hier: > Wenn das "Sekundenpuls" Beispiel des TO schon eine vollständige > Anwendung wär: dann könnte man das ganze Programm weglassen, weil es ja nix "nach Außen" sichtbares mehr macht, außer Strom zu verbrauchen. Ralf schrieb: > und die > übersichtliche Aufgabe in einem einzigen Interrupt zu erledigen da stößt man schnell an Grenzen, oder muss noch mehr komische Handstände machen. z.B. in dem Programm in dem ich den oben gezeigten Sekunden-Zähler verwende: Da wird auch der ADC verwendet, und zwar mit dessen Sleep/Noise Reduction Mode. Schon hast du zwei Ebenen an IRQs, und musst noch mehr mit CLI/SEI rumbasteln, als es das simple ATOMIC_BLOCK beim Auslesen einer Variable macht... Nop schrieb: > Zumal ein sequential lock nicht die worst-case-Latenz der Interrupts > erhöht. Nur weil es denkbar ist, dass es in 1-von-1Mio Anwendungen vielleicht nötig ist, die Interruptlatenz um die paar Instruktionen zu optimieren: Das ist nicht allgemeingültig. In >99% der Anwendungen ist es völlig egal, ob der Sekunden- oder millis-Interrupt mal fünf Taktzyklen später kommt. Er geht deswegen ja nicht verloren. Nop schrieb: > Außerdem hält es den Kontrollfluß sauber, Genau das Gegenteil ist der Fall. Statt einfach den Timer-Wert zu nehmen musst du überall den Programmfluss mit zusätzlichen Abfragen und Schleifen zumüllen.
Nop schrieb: > Nope. Du hast auch diesen Punkt nicht verstanden. Willst du ihn mir erklären? Wir kamen von atomic-blocks. Du schlugst seqlocks als Alternative vor. Und dann schlugst du ein RTOS als Problemlösung für die Probleme des seqlocks vor. Warum also keine atomic-blocks?
Εrnst B. schrieb: > Genau das Gegenteil ist der Fall. Statt einfach den Timer-Wert zu nehmen > musst du überall den Programmfluss mit zusätzlichen Abfragen und > Schleifen zumüllen. Unsinn. Siehe mein Originalposting von vor vier Tagen. Das wird mit nem Getter gekapselt. MaWin O. schrieb: > Und dann schlugst du ein RTOS als Problemlösung für die Probleme des > seqlocks vor. Nope. Du kamst mit "Latenzen in der Hauptschleife", und wenn das ein Problem ist, dann bist Du eben im Bereich, wo ein RTOS Sinn ergibt. > Warum also keine atomic-blocks? Habe ich bereits geschrieben, siehe oben. Lesen. Ich wiederhole mich ungerne.
Nop schrieb: > Unsinn. Siehe mein Originalposting von vor vier Tagen. Das wird mit nem > Getter gekapselt. Was am Programmfluss rein gar nichts ändert. > Nope. Du kamst mit "Latenzen in der Hauptschleife", und wenn das ein > Problem ist, Kannst du überhaupt sinnerfassend lesen? Warum "kam ich" denn damit? Denk mal darüber nach. > Lesen Gute Idee.
Ralf schrieb: > Bei 100Hz zählt das niederwertigste Byte1 bis 256* 10ms = 2,56 Sek > das zweite deckt einen Bereich von 2,56Sek *256 Sek= 10,92min ab, > das dritte 10,92min *256= 46,6 Stu, das vierte Byte bis 497 Tage ab. Das ist genau das, was ich oben beschrieben habe, allerdings beschrankst Du Dich eben auf Shifts von 8-Bit, weil Du nur die ganzen Bytes des uint32_t nimmst. Da Du beim polling die N Bytes (N € [1,4]) nicht atomar abfragst, "verlierst" Du im worst case immer (N-1) niederwertigere Bytes bzw. die dazugehörige Zeitspanne wird das Intervall länger. Das ist Deine beschriebene Ungenauigkeit. Das kann man genauso natürlich auch in C / C++ machen. Dazu benötigst Du kein ASM. Und in C++ kann man das natürlich auch elegant kapseln und elegant erweitern. Wie das geht, schreibe ich natürlich nicht, lieber Ralph ;-)
:
Bearbeitet durch User
Ralf schrieb: > Wilhelm M. schrieb: >> Das ist zwar kein Code, aber irgendwie der Ansatz eines Algorithmus > > Das war zwar wieder nicht die geforderte Begründung, aber irgendwie der > Ansatz einer Rückkehr zur Sache. Du bist nicht in der Lage, eine Begründung von mir für was auch immer zu fordern. Im Gegensatz zu Dir habe ich oben sehr detailliert für den TO und andere erläutert, wo das Problem ist. Und zwar inkl. Code-Beispielen. Und genau das tust Du seit Anbeginn nicht, stattdessen müssen wir raten, was Du meinst. Ich habe oben auch mir als einziger hier die Mühe gemacht, Dein Geschwafel mal in verständliche Worte zu fassen. Das kriegst Du nicht hin bzw. Du willst es nicht, weil Du hier einfach nur trollen willst. Und Du bist nicht in der Lage bzw. Du willst es offenbar nicht, irgendeinen konkreten Code zu zeigen. Nach dem ganzen Gelaber bleibt nur ein einziger Schluss: Du hast solchen Code gar nicht, oder willst ihn gar nicht zeigen, weil Du die Schwachstellen kennst aber nicht zugeben willst.
Ralf schrieb: > Ich hab noch nicht den Eindruck. Ihr denkt per default zu kompliziert. Du kannst Deinen Ansatz sogar noch verbessern, indem Du so ähnlich vorgehst, wie ich oben geschildert habe, allerdings einfach nur einen uint32_t Zähler hast und daraus per Shift den entsprechenden quantisierten Zähler als uint8_t (atomar) für Deine Task ableitest. Und das kann man in C++ wunderbar und allgemeingültig, plattformneutral abbilden.
Veit D. schrieb: > Wenn ich Ralph richtig verstehe, dann ist seine gesamte Zählerverwaltung > in der ISR und nirgends woanders. Das kann gut sein. > Wenn er verschiedene "Zähler" benötigt stehen die alle in der ISR. Auch das kann gut sein. > Am Ende wird Ralph ein 'bool' für jeden Zähler zurückgeben und in der > Main auswerten und ggf. auch in der Main zurücksetzen. Das allerdings muss wirklich nicht sein, das ist wieder dieser fiese C-only-Dummy-Blick, wo eigentlich alles in main() passiert, weil da die Kontrollstrukturen einfach zu lesen sind. Und wo unendlich Zeit damit verballert wird, die Ereignisse zu pollen. Statt zu schlafen... Nimm also bitte zur Kenntnis, dass Anwendungen auch vollständig ereignisgesteuert implementiert werden können, wobei "Ereignisse" immer auf die Auslösung irgendeines Interrupts zurückgehen und die dafür nötige Aktion vollständig im Kontext der jeweiligen ISR abgearbeitet werden. In solchen Anwendungen hat main() nur noch genau eine Aufgabe: den entsprechend der Gesamtsituation der Anwendung aktuell tiefstmöglichen Schlafzustand zu aktivieren. Ja, das sind Sachen, die dem generischen C-ler nicht so richtig schmecken, aber: solche Anwendungen kann man durchaus auch in C programmieren! Man muss halt einfach nur wissen, was man tut.
c-hater schrieb: > Nimm also bitte zur Kenntnis, dass Anwendungen auch vollständig > ereignisgesteuert implementiert werden können, wobei "Ereignisse" immer > auf die Auslösung irgendeines Interrupts zurückgehen und die dafür > nötige Aktion vollständig im Kontext der jeweiligen ISR abgearbeitet > werden. Niemand hat das hier jemals bestritten.
Wilhelm M. schrieb: > Und zwar inkl. Code-Beispielen. Frag mal den TO ob er deinen Code versteht. Es gibt hier leider im Forum genauso C++ Missionare wie es Ralf ein ASM Missionar ist. :( Beides war hier wahrscheinlich nicht gefragt. Das Problem lässt sich in vielen Sprachen lösen. Der TO hat vermutlich(!) eher auf C gesetzt.
900ss D. schrieb: > Wilhelm M. schrieb: >> Und zwar inkl. Code-Beispielen. > > Frag mal den TO ob er deinen Code versteht. Es gibt hier leider im Forum > genauso C++ Missionare wie es Ralf ein ASM Missionar ist. :( > Beides war hier wahrscheinlich > nicht gefragt. > Das Problem lässt sich in vielen Sprachen lösen. Der TO hat > vermutlich(!) eher auf C gesetzt. Es gab auch C Beispiele
900ss D. schrieb: > wie es Ralf ein ASM Missionar ist Das Ansprechen von Problemen unter einer Programmier-Sprache macht noch niemanden zum Missionar. MaWin schrieb: > Ralf hat bis heute keine Begründung und keinen Beispielcode liefern > können, der belegt, dass es besser und schlauer ist grundsätzlich auf > atomic-blocks zu verzichten. Was heißt hier "grundsätzlich"? Es ist eine schnelle, ziemlich unsaubere Lösung. Gleich sämtliche Interrupts sperren zu müssen würde ich schon als Versagen und Niederlage einstufen- die des unerfahrenen Programmierers. Gerade in größeren Programmen mit vielen zeitnah zu beantwortenden Interrupts führt der Weg solcherlei Politik von unnötiger Ressourcenbelastung über zunehmende Intransparenz bis hin zu immer schwerer ortbaren Fehlerbildern. MaWin schrieb: > Völlig richtig. > Leider sind die wenigsten Programme so trivial, dass sie nur Sekunden > zählen. Wunderbar daß Du zustimmst. Es ging zunächst ums Beispiel des TO. Und die grundsätzliche Lösungs-Strategie die ich nun von allen Seiten versucht habe zu beleuchten lässt sich auch darüber hinaus anwenden. Mit der Einstellung daß der Zweck stets alle Mittel heiligt und alle Software derselben Qualität sei darfst Du ja gerne ans Werk gehen. Mein Anspruch ist ein anderer. Εrnst B. schrieb: > Ralf schrieb: >> und die >> übersichtliche Aufgabe in einem einzigen Interrupt zu erledigen > > da stößt man schnell an Grenzen, oder muss noch mehr komische Handstände > machen. > z.B. in dem Programm in dem ich den oben gezeigten Sekunden-Zähler > verwende: Da wird auch der ADC verwendet, und zwar mit dessen > Sleep/Noise Reduction Mode. Schon hast du zwei Ebenen an IRQs, und musst > noch mehr mit CLI/SEI rumbasteln, als es das simple ATOMIC_BLOCK beim > Auslesen einer Variable macht... Mit CLI/SEI bastelt man möglichst überhaupt nicht rum. Die Notwendigkeit mußt Du mir schon genauer begründen. Mehrere Ebenen an IRQs (der AVRxDx hat derer zwei) sind jedenfalls kein Grund dazu. Meine Programme enthalten genau 1x SEI: Nämlich um die Grundinitialisierung ungestört vornehmen zu können und dann alle Interrupts die darauf aufbauen endgültig freizuschalten. Wilhelm M. schrieb: > Das kann man genauso natürlich auch in C / C++ machen. Dazu benötigst Du > kein ASM. Und in C++ kann man das natürlich auch elegant kapseln und > elegant erweitern. Wie das geht, schreibe ich natürlich nicht, lieber > Ralph ;-) Ja. Vermutlich ist es noch komplizierter und ziemlich unansehnlich. Wenn ich dagegen die wenigen Zeilen Asm Code stelle mit denen ich eine große Anzahl Zähler verwalten kann dürfte das ziemlich blamabel für C ausfallen. Immerhin, Punkt für Dich, kannst Du immer noch mit "Portabilität" protzen. Nur muß man die erstens wirklich benötigen und zweitens geht die eben aufs Konto von Leistung und Codetransparenz- und das behaupte ich bis zum Beweis des Gegenteils. Wilhelm M. schrieb: > Im Gegensatz zu Dir habe ich oben sehr detailliert für den TO > und andere erläutert, wo das Problem ist. Deine Begründung warum es nicht besser sein soll die Sekunden des TO gleich in der ISR zu zählen steht bis zur aktuellen Sekunde aus. Du redest um den heißen Brei und versuchst den Anschein zu erwecken, daß diese Lösung nur mit C-Code zu belegen wäre. Ziemlich albern. Wilhelm M. schrieb: > allerdings einfach nur einen > uint32_t Zähler hast und daraus per Shift den entsprechenden > quantisierten Zähler als uint8_t (atomar) für Deine Task ableitest. > verständliche Worte Verständliche Worte sind eher Deinen Schilderungen aus abstrakter Theorie anzuraten. c-hater schrieb: > C-only-Dummy-Blick So drastisch wollte ich es nicht ausdrücken. Immerhin hat C auch seine Verdienste. Wenn es aber dazu führt daß Grundlagen unverstanden bleiben führt das u.a. hier zu vielen überflüssigen Beiträgen.
Naja, ich bin dann mal weg. War ganz lustig, aber der Troll wiederholt sich.
Ralf schrieb: > 900ss D. schrieb: > >> wie es Ralf ein ASM Missionar ist > > Das Ansprechen von Problemen unter einer Programmier-Sprache macht noch > niemanden zum Missionar In der Art, wie du krampfhaft versuchst die vermeintlichen Vorteile einer Assembler-Lösung gegen über C oder eine Hochsprache in den Vordergrund zu stellen hat durchaus etwas Missionarisches. Dann weiter die Geheimniskrämerei, kein konkretes ASM-Beispiel zu liefern an dem man einen Vorteil erkennen könnte. Das verstärkt schon den Eindruck, dass du nur so ungefähr weißt, wovon du redest. Aber was sollst... DU darfst gerne alles in Assembler lösen. Und wenn der Tag kommt, wo du auf eine MCU wechselst, die kein AVR Assembler versteht, da machst halt alles nochmal. Ja, das ist klug, Hut ab.
:
Bearbeitet durch User
MaWin schrieb: > Naja, ich bin dann mal weg. War ganz lustig, aber der Troll > wiederholt > sich. Was solltest Du bei dieser recht eindeutigen Sachlage auch noch vorbringen können? Was bleibt ist Frust auf persönlicher Schiene abzulassen. Was auch für meinen unmittelbaren Vorredner gilt.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.