Hallo,
ich möchte verhindern, dass eine ISR Daten verändert, die ich gerade
außerhalb der ISR benutze.
Bei AVR kenne ich dazu das Makro ATOMIC_BLOCK. Wie geht man bei STM32 in
solchen Fällen überlicherweise vor?
Fehlerhaftes Beispiel:
1
volatileuint64_tcounter;
2
3
voidmein_isr_handler()
4
{
5
counter++;
6
}
7
8
intmain()
9
{
10
while(1)
11
{
12
printf("Counter: %ul",counter);
13
}
14
}
Dieser Code würde falsche Zahlen ausgeben, wenn die ISR dazwischen
kommt.
g457 verwies im Beitrag #5601753 auf:
> Die korrekte Vorgehensweise ist:> 1. Zustand der Interrupts in einer Variablen sichern> 2. Interrupt deaktivieren (einen oder alle…)> 3. Atomarer Block…> 4. Ursprünglichen Zustand der Interrupts wieder herstellen!> Im Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors> empfehlen sie die Verwendung von __get_PRIMASK() und __set_PRIMASK().
Das klingt vielversprechend, schaue ich mir mal an.
Stefanus F. schrieb:> Dieser Code würde falsche Zahlen ausgeben, wenn die ISR dazwischen> kommt.
Machs Dir nicht zu kompliziert:
volatile uint64_t counter;
uint64_t readCounter() {
uint64_t cval1;
uint64_t cval2;
do {
cval1 = counter;
cval2 = counter;
} while (cval1 != cval2);
return cval1;
}
Meistens will man nur sicherstellen dass der gelesene Wert einigermaßen
valid ist.
Jim M. schrieb:> Meistens will man nur sicherstellen dass der gelesene Wert einigermaßen> valid ist.
Was wenn bei jedem Zugriff ein Interrupt dazwischen kommt? Da ist
Interrupts abschalten doch die deutlich bessere Lösung.
Dr. Sommer schrieb:> Was wenn bei jedem Zugriff ein Interrupt dazwischen kommt? Da ist> Interrupts abschalten doch die deutlich bessere Lösung.
Dann hast Du GANZ andere Probleme, denn dann läuft main() nur noch im
Schneckentempo.
Meistens ist das der Punkt wo man Interrupts verliert, z.B. Zeichen aus
dem UART nicht mehr ankommen.
Zum Vergleich: Interrupt Entry und Exit allein sind bei Cortex-M3
jeweils 12 Takte - das reicht hier für die komplette Funktion falls kein
Interrupt dazwischen funkt.
Und wenn beim Cortex-M die Interrupt Last zu hoch wird, dann wird main()
anders als bei AVR gar nicht mehr ausgeführt. Das merken aber schon
Anfänger wenn man das Rücksetzen eines Flags im Handler vergisst.
Jim M. schrieb:> Machs Dir nicht zu kompliziert (und Lösungsvorschlag> durch Wiederholtes Lesen).
Diese Variante nutze beim Lesen der RTC. Meine Frage zielte allerdings
auf eine allgemeingültige Lösung basierend auf Locking die auch mit
erheblich größeren Datenstrukturen und I/O Schnittstellen funktioniert.
Über deren Nachteile denke ich natürlich auch nach.
Stefanus F. schrieb:> Meine Frage zielte allerdings> auf eine allgemeingültige Lösung basierend auf Locking die auch mit> erheblich größeren Datenstrukturen und I/O Schnittstellen funktioniert.
Und da erwartest Du ernsthaft eine allgemeingültige Lösung?
Ich nehme da gerne Ringpuffer, auch weil ich im Interrupt meistens nicht
blockieren kann oder möchte.
Allgemeingültig ist schon deswegen schwierig weil sich die Frage stellt
wer wann wie blockieren darf oder nicht. Siehe Informatik:
Leser-Schreiber-Problem.
Jim M. schrieb:> Und da erwartest Du ernsthaft eine allgemeingültige Lösung?
Ich dachte, ich hätte die Aufgabe klar genug formuliert:
> ich möchte verhindern, dass eine ISR Daten verändert,> die ich gerade außerhalb der ISR benutze.> Bei AVR kenne ich dazu das Makro ATOMIC_BLOCK. Wie geht man> bei STM32 in solchen Fällen überlicherweise vor?
Der Gast g457 hat meine Frage zufriedenstellend beantwortet. Das in
bestimmten (vermutlich sogar in den meisten) Fällen weniger heftige
Sperren genügen, akzeptiere ich.
Ich löse das meist so, dass ich eine globale Variable benutze. In dieser
setze ich in der ISR ein Bit (Flag) wenn neue Daten vorliegen, das ich
in der main() abfrage, die entsprechende Funktion ausführe und danach
das Flag zurücksetze. Die ISR selbst prüft dieses Flag und schreibt die
Daten, falls das Flag nicht frei ist, in einen Puffer. Meist ist der
Puffer nicht notwendig, speziell bei ISRs mit zeitlich langem Intervall.
Damit komme ich in fast allen Fällen ohne Interruptsperre aus.
That's my 2 cents.
Jan K. schrieb:> Zumindest cortex m3 und aufwärts haben wunderbare Instruktionen, um> variablen lockless atomar zu schreiben und zu lesen. Die heißen ldrex> und strex, siehe z.b da:> http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s03s03.html>> Gibt's natürlich auch als C Variante mit Compiler intrinsics.
Jein. Der Exclusive Access Monitor (EAM) ist zur Synchronisation
zwischen Applikation(en) und ISRs nicht geeignet. Der Begriff "atomar"
ist auch etwas ungünstig in diesem Zusammenhang gewählt. Der EAM erlaubt
es im Prinzip, kooperierenden Handlungssträngen die Möglichkeit zu
geben, zu "pollen," ob ein Anderer Handlungsstrang nebenläufig Zugriff
auf die Variable bansprucht. Das ist natürlich fatal, wenn ein ISR in
einer Schleife sitzt und darauf wartet, dass eine Task fertig ist. Geht
sozusagen gar nicht.
Der asymmetrische Fall (ISRs vs. Tasks), auf den Stefan aus ist, lässt
sich eigentlich wie in jeder Prozessorarchitektur nur mit Ausmaskieren
von Interrupts in den Griff kriegen. Das lässt sich allerdings beim
Cortex recht fein granulieren (wenn man z.B, weiss, welcher ISR
konkurriert, lässt sich genau dieser Interrupt ausmaskieren, und Andere
Interrupts höherer und niedriger Priorität können fröhlich
weiterlaufen).
Nebenbei bemerkt: cpsie() und cpsid() arbeiten unabhängig vom PRIMASK
register, d.h. durch cpsie() und cpsid() lässt sich ein vorher durch
PRIMASK gesetzter Interruptlevel kurzzeitig "global" hochsetzen. Das ist
Anders als bei vielen Anderen Prozessorarchitekturen.
Bin verwirrt, warum sollte das nicht gehen? Wir reden von Single core
Prozessoren. Dh, entweder die ISR oder der App "thread" läuft. Haut der
Interrupt zwischen dem read modify write der App rein, schlägt der
exklusive Zugriff fehl. Spätestens beim 2. Zugriff (in der do while
schleife, beim Schreiben der variablen in der ist) sollte es aber
funktionieren.
Nach Ende der isr schlägt der Check in der App auch fehl und die Daten
werden neu aus dem RAM geladen. Kann sein, dass man unter bestimmten
Bedingungen noch eine memory barrier braucht.
Sehe momentan noch nicht den Grund, warum das so nicht geht :)
Bezüglich atomar hast du recht, es ist immer noch ein read modify write,
nur dass eben auf exklusiven Zugriff geachtet wird und man die schleife
drumherum bauen muss.
Da der ISR per Definition eine höhere Prio hat als der thread, wird er
die loop niemals verlassen (wann sollte der thread drankommen, um seinen
lock wieder freizugeben)? Oder willst Du den ISR erst verlassen und dann
den thread seine Freigabe machen lassen? Wer würde in dem Fall den ISR
nochmals feuern?
Grundsätzlich ist loopen mit nicht deterministischer Länge im ISR ein
Nono, selbst wenn o.g. Szenario verhindert werden könnte. Da der
Prozessor seinen Zustand wechselt, sobald ein ISR anfängt zu Laufen, ist
die einzig gute Lösung, den Zustandswechsel während der kritischen Phase
so kurz wie nötig zu unterdrücken (ausmaskieren).
Jan K. schrieb:> Zumindest cortex m3 und aufwärts haben wunderbare Instruktionen,> um> variablen lockless atomar zu schreiben und zu lesen. Die heißen ldrex> und strex, siehe z.b da:> http://infocenter.arm.com/help/index.jsp?topic=/co...>> Gibt's natürlich auch als C Variante mit Compiler intrinsics.
Die richtige Lösung wurde schon von Jan K. genannt.
Alles andere ist Murks.
Hi,
Das passiert so nicht. Der exklusive Monitor schätzt afaik nur möglichst
pessimistisch, ob im selben Moment ein Speicherzugriff auf die selbe
Adresse stattfindet.
Klar hat die ISR Priorität. Aber beim 2. Versuch in der isr kann kein
paralleler Zugriff mehr stattfinden, da wir ja nur in der isr sind.
Ich habe das so hier implementiert, und zähle zum Spaß mit, wie häufig
der das lesen / schreiben wiederholt werden musste. Glaube war so
1/10000, muss aber nochmal nachgucken. Deadlocks gab es keine.
Versuche auch nochmal zu nachzulesen, wann genau der Monitor gecleart
wird.
PS, es geht hier nur um das Inkrementieren einer Zählvariablen, vllt hat
das Missverständnisse verursacht?
Schöne Grüße!
Ruediger A. schrieb:> Da der ISR per Definition eine höhere Prio hat als der thread, wird er> die loop niemals verlassen (wann sollte der thread drankommen, um seinen> lock wieder freizugeben)?
Die ISR muss natürlich am Anfang einmal die CLREX-Instruktion ausführen,
um alle Locks zu löschen, genau wie der Dispatcher bei einem "echten"
Multithreading-System. Das signalisiert dem "Thread" (main-Loop), dass
zwischen dessen LDREX-STREX-Paar ein Zugriff stattgefunden hat, sodass
die main es nochmal probieren muss.
Ruediger A. schrieb:> Grundsätzlich ist loopen mit nicht deterministischer Länge im ISR ein> Nono, selbst wenn o.g. Szenario verhindert werden könnte
Du brauchst ja auch nicht Loopen, es sei denn du verwendest
verschachtelte Interrupts und die ISR könnte selbst wieder unterbrochen
werden. Ansonsten reicht CLREX-LDR-STR, es kann ja nichts dazwischen
kommen.
Jan K. schrieb:> Gibt's natürlich auch als C Variante mit Compiler intrinsics.
Die braucht man aber gar nicht - zumindest beim GCC kann man auch
std::atomic bzw. _Atomic nutzen, das nutzt dann automatisch die -EX
Instruktionen.
Ja, stimmt, ich hatte hier nicht die genaue Semantik berücksichtigt. Da
das Ganze nicht einfach zu durchschauen ist, hier die Funktionsweise in
einer Nussschale:
Der EAM koppelt Lese-und Schreibzugriffe auf eine Speicheradresse. Ein
strex() auf eine Adresse xyz wird fehlschlagen*, wenn nicht vorher auf
die Adresse mindestens(!) ein ldrex() passiert ist. Mit dem strex() wird
die Markierung aufgehoben, so dass ein zweites strex() nichts bewirkt.
Der Nettoeffekt ist, dass jede durch Scheduler und/oder Interruptfolge
generierte Sequenz
1 <Strang 1 liest xyz>
2 <Strang 2 liest xyz>
3 <Strang 2 schreibt xyz>
4 <Strang 1 schreibt xyz>
keinen Schaden anrichtet, weil step 4 ins Leere läuft, als keine
inkonsistenten Daten erzeugt.
Im ISR Fall sind wir tatsächlich auf der sicheren Seite, weil der ISR
durch seine höhere Priorität mindestens das erste, ggf. das zweite
ldrex() durchführt und damit immer der erste Handlungsstrang ist, dessen
Schreiben erfolgreich ist. Damit ist der unterbrochene Handlungsstrang
derjenige, der seinen Schreibvorgang nochmal auslösen muss.
Im ursprünglichen Beispiel gibt es übrigens hier kein Problem, weil der
eine Handlungsstrang (main loop) nur liest und nicht schreibt. Es könnte
dann ein Problem geben, wenn der Wert von counter an zwei Stellen
gebraucht wird und nicht klar ist, dass der faktische Wert sich in der
Zwischenzeit geändert haben könnte (s.u. bei *).
N.B. Es ist wichtig hier zu sehen, dass es verschiedene
Abstraktionsebenen gibt. Für die EAM Befehle gibt es hicht nur Lib
spezifische Macros als Abstraktionen, sondern auch C Wrappers
(__sync_sub_and_fetch etc), die sich aber dadurch unterscheiden, dass
sie tatsächlich loopen, bis das strex() erfolgreich gewesen ist. Das
kann im Fall von verschachtelten Interrupts tatsächlich zu subtilen
Timingphänomenen führen.
* fehlschlagen heisst, das der Schreibzugriff nicht ausgeführt und der
fehlgeschlagene Status dem Aufrufer mitgeteilt wird. Der Aufrufer kann
das Ergebnis ignorieren, hat damit dann aber ggf. selber eine inkorrekte
Annahme über den Wert von xyz - falls der neu berechnete Wert also
weiter verwendet wird, ist das Ergebnis abhängig davon, ob xyz
tatsächlich wieder gelesen oder
Ruediger A. schrieb:> Im ursprünglichen Beispiel gibt es übrigens hier kein Problem, weil der> eine Handlungsstrang (main loop) nur liest
Es ist aber ein 64bit-Wert, und die können nicht atomar gelesen werden.
Außerdem:
"LDREXD and STREXD are not supported in ARMv7-M." (ARM-v7M ArchRef, S.
65). Daher ist eine Interruptsperre hier wirklich erforderlich, wenn man
sich nicht auf "wird schon klappen" verlassen möchte:
Jim M. schrieb:> Dann hast Du GANZ andere Probleme, denn dann läuft main() nur noch im> Schneckentempo.Stefanus F. schrieb:> Diese Variante nutze beim Lesen der RTC.
Da ist das auch ok, weil hier garantiert ist dass die RTC die Werte
regelmäßig und langsam verändert.
Stefanus F. schrieb:> ich möchte verhindern, dass eine ISR Daten verändert, die ich gerade> außerhalb der ISR benutze.>> Bei AVR kenne ich dazu das Makro ATOMIC_BLOCK. Wie geht man bei STM32 in> solchen Fällen überlicherweise vor?
Das ist eigentlich unabhängig von der Architektur.
Du hast zwei Instanzen, die sich um eine Ressource katzbalgen. Entweder
du schaffst dir eine Möglichkeit, daß beide sich irgendwie verständigen
oder es geht irgendwann mal schief.
Was soll denn deine Interruptroutine tun, wenn sie mitkriegt, daß sie
die Ressource nicht aktualisieren darf? Solange warten, bis die andere
Seite da raus ist, GEHT NICHT. Den Interrupt abwürgen, solange der
Andere drin herumgrabscht, würde eine vielleicht wichtige Reaktion des
Systems zur rechten Zeit(!!!) verhindern.
Vorschlag 1: Du packst in der ISR die Daten in einen Ringpuffer und
übermittelst dem Rest der Welt lediglich eine Botschaft, die besagt, man
möge sich den Datenblock gefälligst aus selbigem wieder herausklauben.
Falls der Ringpuffer jemals überlaufen sollte, ist dein µC sowieso
überlastet.
Vorschlag 2: Du richtest dir eine direkte Synchronisation zwischen ISR
und Rest ein, z.B. so, daß jede Instanz einen popligen Zähler hat, den
nur sie selbst inkrementiert. Die ISR speichert den Wert nur dann in den
allgemeinen Puffer, wenn beide Zähler gleich sind - und der "Rest" liest
nur dann den Wert aus, wenn beide Zähler ungleich sind. Das ist
sozusagen der Ringpuffer mit nur einem Platz darinnen.
W.S.
Stefanus F. schrieb:> Eigentlich wollte ich dieses mal nur Interrupts temporär sperren.
Sowas hatten wir damals beim Z80 gemacht.
Aber heutzutage sollte man so etwas erst garnicht mehr in Erwägung
ziehen. Ja, bei den derzeitigen Cortexen stecken derartige Dinge noch
immer in den CMSIS-Bibliotheken, aber schon vorher bei den ARM7TDMI sah
es ganz anders aus, da hatte man diverse Stacks und wenn die gewöhnliche
App-Schicht der Firmware im Usermodus läuft, gehen solche Spielereien
garnicht. Ich halte das temporäre Abwürgen von Interrupts auch ganz
generell für schlechten Stil. Mit einem vernünftigen Systementwurf
braucht man sowas auch nicht wirklich.
W.S.
W.S. schrieb:> Ich halte das temporäre Abwürgen von Interrupts auch ganz> generell für schlechten Stil. Mit einem vernünftigen Systementwurf> braucht man sowas auch nicht wirklich.
Lass mal die Kirche im Dorf. Es hängt ganz davon ab wie lange die
Sequenz ist unter der die Interrupts gesperrt sind.
Sagen wir mal das sind nur 4 Takte oder so. Alleine um in den IRQ zu
kommen, braucht der Cortex-M3 aber schon 12 Takte. Wenn es bei dem
Verhältnis jetzt wirklich auf diese 4 Takte ankommt daß Du Deine
geforderte Antwortzeit einhalten kannst, fährst Du die Hardware so nah
am Limit, daß ich sagen würde das Konzept oder der verwendete Controller
passt nicht.
W.S. schrieb:> die besagt, man möge sich den Datenblock gefälligst aus selbigem wieder> herausklauben.
Und wie holt man Daten aus einem Ringpuffer, ohne die Interrupts zu
sperren? Die diversen Metadaten (Lesezeiger, Füllstand, ...) atomar zu
ändern ist sehr kompliziert. Atomare nicht-blockierende Queues sind ein
Forschungsgebiet...
W.S. schrieb:> Aber heutzutage sollte man so etwas erst garnicht mehr in Erwägung> ziehen.
Aus ästhetischen Gründen, weil man das halt nicht mehr macht oder wie?
;-)
Wenn nicht dagegen spricht, die Interrupts zu sperren, spricht nichts
dagegen.
Warum umständlich irgendwelche Ringpuffer für ein (wahrscheinlich)
triviales Problem bauen?
Der TE muss entscheiden, ob es für ihn ein Problem ist oder nicht, die
Ints zu sperren.
Ein gutes Pferd springt nur so hoch es muss ;-)
Dr. Sommer schrieb:> W.S. schrieb:>> die besagt, man möge sich den Datenblock gefälligst aus selbigem wieder>> herausklauben.>> Und wie holt man Daten aus einem Ringpuffer, ohne die Interrupts zu> sperren? Die diversen Metadaten (Lesezeiger, Füllstand, ...) atomar zu> ändern ist sehr kompliziert. Atomare nicht-blockierende Queues sind ein> Forschungsgebiet...
Da muss ich mal für W.S. in die Bresche springen... da in dieser
speziellen Situation die Rollen klar verteilt sind - ISR schreibt, App
liest - gibt es einen möglichen Konflikt nur in dem Fall, dass der
Ringpuffer überläuft. Ansonsten ist es der lesenden App egal, ob der ISR
in der Zwischenzeit ein weiteres Zeichen einpflegt oder nicht; je nach
Timing wird das /werden die neue/n Zeichen noch in diesem Zyklus
verarbeitet oder im nächsten. Die Vor- und Nachlaufzeiger sind ja bis
auf das "Berührungsszenario" voneinander unabhängig, so dass der
Vorlaufzeiger vom ISR und der Nachlaufzeiger von der App verwaltet wird
(dabei hilft, dass beide immer in die selbe Richtung laufen).
Zumindestens auf der Rx Seite habe ich schon viele Anwendungen gesehen,
die problemlos und stabil so laufen (insbesonders bei HD Protokollen).
Die Pauschalaussage von W.S. kann auch ich aber nicht so stehen lassen;
es gibt noch genügend reale Fälle, in denen Interruptsperren
unvermeidbar sind (z.B. beim RTOS, wo es Codesabschnitte gibt, die
einfach nicht unterbrochen werden dürfen, zumindestens nicht von ISRs,
die ebenfalls RTOS Services benutzen). Eine "globale" Sperre ALLER ISRs
ist aber in der Regel mit Kanonen auf Spatzen geschossen; die meisten
Prozessorarchitekturen erlauben recht fein granulierbare Teilsperren.
Ruediger A. schrieb:> Ansonsten ist es der lesenden App egal, ob der ISR> in der Zwischenzeit ein weiteres Zeichen einpflegt oder nicht;
Kannst du dafür mal ein Beispiel zeigen? Ein typischer Ringpuffer hat
ja:
- Lesezeiger
- Schreibzeiger
- Füllstand (für wenn Lesezeiger=Schreibzeiger, um zwischen ganz voll
und ganz leer zu unterscheiden)
- Reduzierte Größe - Wird genutzt falls ein Datenblock nicht mehr ans
Ende des Puffers passt, man stattdessen wieder am Anfang anfängt, und
daher den Puffer temporär als geschrumpft angibt
Die alle konsistent zu lesen und zu verändern, sodass bei
Unterbrechungen nichts passieren kann, finde ich nicht so einfach.
Besonders interessant ist es natürlich, wenn von mehreren ISRs, die sich
gegenseitig unterbrechen können, geschrieben werden kann.
Typischer Ringbuffer für Application und Interrupts? Gern. Hab ich
zufällig am Samstag geschrieben:
https://pastebin.com/La3t0Phx
Das funktioniert lock-less, wenn der Interrupt lediglich RBGet und die
Application RBPut aufruft (bzw. natürlich auch, wenn die Rollen
vertauscht sind).
Ruediger A. schrieb:> Eine "globale" Sperre ALLER ISRs> ist aber in der Regel mit Kanonen auf Spatzen geschossen; die meisten> Prozessorarchitekturen erlauben recht fein granulierbare Teilsperren.
Ist es nicht, denn damit ist die maximale Zeit der Interruptsperre klar
definiert. Sie ist immer auf den reinen Read-Modify-Write Zugriff
begrenzt.
Sperre ich dagegen nur den Interrupt, mit dem es Konflikte gibt, können
sich alle anderen Interrupts niederer Priorität dazwischen drängeln und
der kritische Zeitraum kann sich um ein Vielfaches verlängern. Dadurch
entstehende Probleme sind außerdem extrem schwer zu debuggen.
Die globale Sperre hat daher deutlich weniger Seiteneffekte und
Fallstricke als jede andere Lösung.
Mir fällt jetzt auch keine Konstellation ein, wo ein Programm Fehler
macht, wenn mal ein Interrupt 4 CPU-Zyklen später ausgeführt wird.
Nils P. schrieb:> Typischer Ringbuffer für Application und Interrupts? Gern. Hab ich> zufällig am Samstag geschrieben:
Vielen Dank!
1
staticinlineboolRBIsFull(constringBuffer*r)
2
{
3
size_tnext=(r->head+1==r->len)?0:r->head+1;
4
returnnext==r->tail;
5
}
Das bedeutet der RB ist voll wenn noch 1 Byte frei ist?
Deine RBPut und RBGet Funktionen können jeweils nur 1 Byte bearbeiten.
Die für jedes Byte einzeln aufzurufen wäre ziemlich langsam. Was machst
du in dieser Situation:
len=10
head=7
tail=5
Es sind also 8 Plätze frei. Man möchte einen Datenblock von 5 Bytes
schreiben, kann aber nicht weil am Ende nur 3 Bytes frei sind. Den Block
in der Mitte zu teilen wäre ineffizient, weil man dann Zugriffe
byteweise machen muss.
Dr. Sommer schrieb:> Das bedeutet der RB ist voll wenn noch 1 Byte frei ist?
Ja, das ist Absicht so. Du hast ansonsten immer das Problem, das Du
Buffer voll und Buffer leer nicht direkt unterscheiden kannst. Natürlich
kannst Du das mit zusätzlichen Variablen erledigen, aber die kosten auch
mindestens ein Byte und machen es zudem komplizierter. Es ist also netto
unter dem Strich egal.
> Deine RBPut und RBGet Funktionen können jeweils nur 1 Byte bearbeiten.> Die für jedes Byte einzeln aufzurufen wäre ziemlich langsam.
In dieser Simpel Variante ist das in der Tat so, aber niemand kann mich
davon abhalten statt Bytes z.B gleich ganze USB oder DMA Frames zu
verwalten.
Geschrieben habe ich das ganze für simples RS232, da fällt die byteweise
Verarbeitung nicht weiter ins Gewicht.
Nils P. schrieb:> In dieser Simpel Variante ist das in der Tat so, aber niemand kann mich> davon abhalten statt Bytes z.B gleich ganze USB oder DMA Frames zu> verwalten.
Kannst du auch dafür ein Beispiel zeigen, natürlich für variable
Frame-Größe und ohne Interrupt-Sperre? Für einzelne Bytes ist das ja
uninteressant.
Dr. Sommer schrieb:> Ruediger A. schrieb:>> Ansonsten ist es der lesenden App egal, ob der ISR>> in der Zwischenzeit ein weiteres Zeichen einpflegt oder nicht;>> Kannst du dafür mal ein Beispiel zeigen? Ein typischer Ringpuffer hat> ja:> - Lesezeiger> - Schreibzeiger> - Füllstand (für wenn Lesezeiger=Schreibzeiger, um zwischen ganz voll> und ganz leer zu unterscheiden)> - Reduzierte Größe - Wird genutzt falls ein Datenblock nicht mehr ans> Ende des Puffers passt, man stattdessen wieder am Anfang anfängt, und> daher den Puffer temporär als geschrumpft angibt>> Die alle konsistent zu lesen und zu verändern, sodass bei> Unterbrechungen nichts passieren kann, finde ich nicht so einfach.> Besonders interessant ist es natürlich, wenn von mehreren ISRs, die sich> gegenseitig unterbrechen können, geschrieben werden kann.
Grundsätzlich stimme ich dir zu. Einen allgemein gültigen Ringbuffer zu
schreiben ist keine leichte Aufgabe. Der springende Punkt ist in meinen
Augen jedoch, dass man das auf einem STM32 ohnehin nicht braucht.
Ein Großteil aller Use-Cases für embedded Gschichtln enthält ja ziemlich
strenge Constraints:
- push_back, pop_front, sprich ein Ende lesend, ein Ende schreibend
- maximal in 1 ISR genutzt oder zumindest kein preemptive möglich
- single threaded
Geht man jetzt noch davon aus, dass man nicht an die Performance Grenze
des Chips geht und die Interrupts schnell genug bedienen kann, dann
braucht man sich um "locking" eigentlich gar nicht mehr kümmern...
Dr. Sommer schrieb:> Ruediger A. schrieb:>> Ansonsten ist es der lesenden App egal, ob der ISR>> in der Zwischenzeit ein weiteres Zeichen einpflegt oder nicht;>> Kannst du dafür mal ein Beispiel zeigen? Ein typischer Ringpuffer hat> ja:> - Lesezeiger> - Schreibzeiger
ja, ich benutze dafür i.d.Regel die Begriffe Nachlaufzeiger und
Voralufzeiger, aber das ist ja egal.
> - Füllstand (für wenn Lesezeiger=Schreibzeiger, um zwischen ganz voll> und ganz leer zu unterscheiden)> - Reduzierte Größe - Wird genutzt falls ein Datenblock nicht mehr ans> Ende des Puffers passt, man stattdessen wieder am Anfang anfängt, und> daher den Puffer temporär als geschrumpft angibt>
Meinem Verständnis nach brauchst Du die beiden nicht, solange der
Vorlaufzeiger (Schreibzeiger, bei Rx im ISR) immer sicher stellt, dass
er den Nachlaufzeiger nicht überholt. Ich visualisiere mir das immer
gerne so, dass der Wertebereich der beiden Zeiger 2* die Puffergrösse
einnehmen kann, der Zugriff auf den Puffer dann aber Modulo der
Puffergrösse erfolgt. Es ist also im Prinzp kein Unterschied zwischen
einem Wraparound oder nicht warparound, solange der Vorlaufzeiger immer
vor dem Nachlaufzeiger steht. Bei Blocktransfers (z.B. FIFO USARTs) ist
das natürlich ein Problem, wie schon in der Zwischenzeit eingeworfen
wurde, aber ein Überlauf ist ein Überlauf und somit immer ein Problem,
für das es i.d. Regel keine saubere Lösung gibt, das muss auf höheren
Protokollschichten durch retransmissions etc. abgefangen werden. Man
könnte also in dem Fall "einfach" auf das Maximum des freien Puffers vor
dem Nachlaufzeiger und dem empfangenen Datenblock auffüllen. Verlorene
Daten sind verloren, das ist halt so...
> Die alle konsistent zu lesen und zu verändern, sodass bei> Unterbrechungen nichts passieren kann, finde ich nicht so einfach.
Naja, was in unserem Job ist einfach, wenn man nach der bestmöglichen
Lösung sucht? ;-)
> Besonders interessant ist es natürlich, wenn von mehreren ISRs, die sich> gegenseitig unterbrechen können, geschrieben werden kann.
Uhm, Du meinst mehrere nicht synchronisierte
Kommunikationsschnittstellen, die denselben linearen Buffer beschreiben?
Woher soll die Wegverarbeitung dann die Quellen voneinander
unterscheiden können bzw. gibt es dafür einen use case?
Das wäre in der Tat spannend, beträfe aber eigentlich nur das korrekte
Führen des Schreibzeigers. Dem Lesezeiger wäer das meiner ersten groben
Einschätzung nach egal, oder?
Ruediger A. schrieb:> ja, ich benutze dafür i.d.Regel die Begriffe Nachlaufzeiger und> Voralufzeiger, aber das ist ja egal.
Klingt nach Klempnern :D
Ruediger A. schrieb:> aber ein Überlauf ist ein Überlauf
Die gezeigte Situation ist kein Überlauf - es ist genug Platz da - man
kann ihn nur nicht nutzen:
Dr. Sommer schrieb:> len=10> head=7> tail=5Ruediger A. schrieb:> Woher soll die Wegverarbeitung dann die Quellen voneinander> unterscheiden können bzw. gibt es dafür einen use case?
z.B. aus einem Header im Paket in der Queue. Das könnten mehrere
Netzwerk-Schnittstellen sein, die Pakete in den IP-Stack werfen.
Ereignisse aus verschiedenen Schnittstellen, die aber zusammen
verarbeitet werden müssen. Nachrichten im Aktor-Modell.
Ruediger A. schrieb:> Das wäre in der Tat spannend, beträfe aber eigentlich nur das korrekte> Führen des Schreibzeigers.
Vermutlich... Das Problem ist sogar so kompliziert dass bestehende
Lösungen einfach mehrere Queues nutzen:
http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++
The less threads fight over the same data, the better. So, instead of
using a single data structure that linearizes all operations, a set of
sub-queues is used instead -- one for each producer thread.
Peter D. schrieb:> Ruediger A. schrieb:>> Eine "globale" Sperre ALLER ISRs>> ist aber in der Regel mit Kanonen auf Spatzen geschossen; die meisten>> Prozessorarchitekturen erlauben recht fein granulierbare Teilsperren.>> Ist es nicht, denn damit ist die maximale Zeit der Interruptsperre klar> definiert. Sie ist immer auf den reinen Read-Modify-Write Zugriff> begrenzt.>> Sperre ich dagegen nur den Interrupt, mit dem es Konflikte gibt, können> sich alle anderen Interrupts niederer Priorität dazwischen drängeln und> der kritische Zeitraum kann sich um ein Vielfaches verlängern. Dadurch> entstehende Probleme sind außerdem extrem schwer zu debuggen.>
Es ging mir dabei nicht unbedingt um das chirurgische Sperren nur der
von dem Synchronisation betroffenen ISRs (obwohl sich dafür auch use
cases finden lassen), sondern auch um das Sperren Aller ISRs BIS ZU dem
betroffenen level (Bsp. configMAX_SYSCALL_INTERRUPT_PRIORITY in
FreeRTOS). Natürlich werden auch dann höher priorisierte hereinkommende
Interrupts den Umlauf verzögern, aber das ist ja gewollt (deswegen haben
die ISRs ja die höhere Priorität). Das globale Sperren ALLER Interrupts
(cpsid beim Cortex) würde ich wenn möglich nur auf deterministisch
extrem kurze Zeiten beschränken, in denen tatsächlich ein
"Systemstillstand" unvermeidbar ist.
> Die globale Sperre hat daher deutlich weniger Seiteneffekte und> Fallstricke als jede andere Lösung.> Mir fällt jetzt auch keine Konstellation ein, wo ein Programm Fehler> macht, wenn mal ein Interrupt 4 CPU-Zyklen später ausgeführt wird.
Nils P. schrieb:> Typischer Ringbuffer für Application und Interrupts? Gern. Hab ich> zufällig am Samstag geschrieben:>> https://pastebin.com/La3t0Phx>> Das funktioniert lock-less, wenn der Interrupt lediglich RBGet und die> Application RBPut aufruft (bzw. natürlich auch, wenn die Rollen> vertauscht sind).
Die kritischen Teile sind
size_t next = (r->head+1 == r->len) ? 0: r->head+1;
10
return next == r->tail;
11
}
Z.B. Ein paralleles Schreiben von r->head in RBPut() sprengt den
Vergleich in RBIsEmpty() wenn Schreiben und Lesen von r->head nicht
atomar ist.
Das muss die Plattform bzw. der Kontext in dem die Funktionen aufgerufen
werden, garantieren. Wer den Code verwendet muss schon genau hinsehen
und wissen was seine Plattform macht, sonst gibt es Tränen.
Mampf F. schrieb:> Aus ästhetischen Gründen, weil man das halt nicht mehr macht oder wie?
Nein. Nicht aus ästhetischen Gründen.
Den Fall, daß der Usercode auch nur unter Userbedingungen ausgeführt
wird und derartige Sperrungen schlichtweg wegen fehlendem Privileg-Level
nicht drin sind, schließe ich hier mal aus. Das ist bei kleinen µC eher
nicht Mode.
Aber:
Wo soll denn die Sperrung und die korrekte Entsperrung stattfinden? In
der Grundschleife in main oder in irgendeinem aufgerufenen Treiber oder
einer Funktion? Ja? Und wenn irgend eine andere Funktion ebenfalls (aber
aus anderen Gründen) die Int's sperrt und wieder freigibt, dann kommt es
zusätzlich noch zu einem Konflikt zwischen mehreren Funktionen.
Ich habe selber sowas schon erlebt, deswegen weiß ich, daß man dann das
Sperren und Entsperren kellern muß, also einen Zähler beim Sperren
raufzählen und beim Entsperren wieder herunterzählen und nur dann, wenn
er Null wird, tatsächlich wieder entsperren.
Das ist ein elendiglicher Streß, der im Prinzip völlig unnötig ist, wenn
man sich der Methoden bedient, die ich geschildert habe.
Dr. Sommer schrieb:> Und wie holt man Daten aus einem Ringpuffer, ohne die Interrupts zu> sperren? Die diversen Metadaten (Lesezeiger, Füllstand, ...) atomar zu> ändern ist sehr kompliziert.
Dann laß dir dein Lehrgeld zurückzahlen. Jeder Ringpuffer ist im Grunde
EASY. Es gibt einen Schreibzeiger und einen Lesezeiger, die atomar les-
und schreibbar sind - egal wie umfänglich die gepufferten Elemente sind
und jede Instanz verwaltet nur den eigenen Zeiger. Den anderen darf sie
lesen, aber nicht schreiben. Das ist ALLES.
Ruediger A. schrieb:> Die Pauschalaussage von W.S. kann auch ich aber nicht so stehen lassen;> es gibt noch genügend reale Fälle, in denen Interruptsperren> unvermeidbar sind (z.B. beim RTOS, wo es Codesabschnitte gibt, die> einfach nicht unterbrochen werden dürfen
Gerade bei RTOS auf ARM bzw. Cortex sieht das jedoch anders aus, da
macht man so etwas per SVC. Ach ja, GCC-Leute kennen das ja nicht. Also
der Supervisor-Call ist sowas wie ein Software-Interrupt mit eingebauter
Möglichkeit, Zusatzinformationen zu transferieren.
Der so aufgerufene Supervisor läuft dann üblichermaßen auf einer höheren
Privileg-Stufe als alles, was ansonsten als unterbrechbar gilt.
Hochrangige Interrupts, die in jedem Falle durchkommen müssen, sind
davon ausgenommen, sofern man sie auf einer entspechend höheren
Vorrangstufe einrichtet.
Bei früheren ARM's haben die verschiedenen Level auch getrennte Stacks,
so daß der Stack des Usercodes (beginnend ab main()) ein anderer Stack
ist als der für den Supervisor. Wer damals einmal in die Lernbetty
geschaut hat, hätte diese Dinge dort sehen und ggf. begreifen können.
W.S.
Dr. Sommer schrieb:> static inline bool RBIsFull(const ringBuffer * r)> {> size_t next = (r->head+1 == r->len) ? 0: r->head+1;> return next == r->tail;> }Das bedeutet der RB ist voll wenn noch 1 Byte frei ist?
Erstens trifft das auf Elemente beliebiger Art zu und zweitens ist das
Testen auf voll oder leer recht simpel:
mytype Ringpuffer[soundsoviel];
int rd, wr;
bool IsPufferLeer(void) {return rd==wr;}
bool IsPufferVoll(void) {return rd==next(wr);}
wobei next eben der Index bzw. Zeiger ist, der sich ergeben würde, wenn
man noch ein Element in den Puffer tun würde.
Eigentlich ganz einfach: wr darf rd nicht überholen. Und wenn wr direkt
hinter rd zeigt, dann ist der Puffer eben voll. Dabei ist es EGAL, ob
die gepufferten Daten nun aus Bytes oder aus fetten struct's bestehen.
W.S.
W.S. schrieb:> Dann laß dir dein Lehrgeld zurückzahlen. Jeder Ringpuffer ist im Grunde> EASY.
Dann erleuchte mich und zeig mal einen lock-freien Ringpuffer her,
welcher ganze und unterschiedlich große Pakete verwalten kann. Einzelne
Bytes sind uninteressant.
W.S. schrieb:> Das ist ALLES.
Wie bereits erwähnt ist eine solche Umsetzung ineffizient.
W.S. schrieb:> da macht man so etwas per SVC. Ach ja, GCC-Leute kennen das ja nicht
Gerade heute habe ich einen syscall-Mechanismus per SVC mit dem GCC
implementiert. Nur weil du das nicht hinbekommst heißt das nicht dass
das nicht geht. Wie das geht findet man übrigens auch ganz schlicht per
Google.
W.S. schrieb:> Bei früheren ARM's haben die verschiedenen Level auch getrennte Stacks,
Aktuelle Cortex-M (deaktivierbar) und -A (immer) auch; der SP ist bei
letzteren gebankt zwischen den verschiedenen Modi (ABT, USR/SYS, IRQ,
FIQ, UND, SVC).
W.S. schrieb:> Erstens trifft das auf Elemente beliebiger Art zu und zweitens ist das> Testen auf voll oder leer recht simpel:
Ja, ich hab's jetzt auch gesehen. Ich hab es halt immer anders rum
gemacht. Hat auch den netten Nebeneffekt, dass man immer direkt den
freien Platz parat hat.
Dr. Sommer schrieb:> Dann erleuchte mich und zeig mal einen lock-freien Ringpuffer her,> welcher ganze und unterschiedlich große Pakete verwalten kann. Einzelne> Bytes sind uninteressant.
Das wollte der OT aber gar nicht. Du reitest hier Requirements rum, die
gar nicht gefragt waren und gehst vom Hundersten ins Tausendste.
Das nützt niemandem etwas, insbesondere dem OT nicht, der eine einfache
Frage beantwortet haben wollte. Und für den hab ich eine Antwort
geliefert mit der er weiterabeiten kann.
Du kommst von der Seite, grätscht hier rein aber bringst dich nicht
Produktiv ein.
In diesem Sinne: Zeig deinen Ringbuffer, der deinen Ansprüchen genügt.
Nils P. schrieb:> In diesem Sinne: Zeig deinen Ringbuffer, der deinen Ansprüchen genügt
Die Aussage war, dass ein praxistauglicher Lock-Freier Ringpuffer
ziemlich kompliziert ist (ich habe auch keinen) und daher simples
Sperren der Interrupts sinnvoller ist - genau das, was der OP haben
wollte.
W.S. schrieb:> Cortex sieht das jedoch anders aus, da> macht man so etwas per SVC. Ach ja, GCC-Leute kennen das ja nicht.
Und wie funktioniert das deiner Meinung nach unter ARM-Linux? Wird da
die C-Library extra mit ARMCC kompiliert?
Daher hier extra nochmal für dich: So geht SVC mit dem GCC und mit Clang
auf Cortex-A:
Der Wert "time" wird per r0 übergeben. Es wird "SVC #1" ausgeführt. Über
die Clobber-Liste wird angegeben, dass alle Caller-Save-Register (r1-r3,
r12) überschrieben werden - genau wie bei einem Funktionsaufruf. Da der
Cortex-A im Gegensatz zum -M diese Register nicht automatisch beim
Exception-Eintritt sichert, müsste die Exception dies selbst tun, oder
eben wie hier dem Aufrufer überlassen. Zudem wird angegeben, dass "r0"
selbst auch überschrieben werden kann. Somit kann man im
Exception-Handler, der erstmal in Assembler sein muss, eine gewöhnliche
C-Funktion aufrufen. Letztlich verhält sich der Aufruf so wie ein
normaler Funktionsaufruf, aber eben mit "svc" statt "bl". Man kann so
keine Parameter über den Stack übergeben - das wäre aber ohnehin nicht
sehr sinnvoll, weil der SVC-Exception-Handler ohnehin einen eigenen
Stack hat und man nicht so direkt auf den User/System-Stack zugreifen
kann. Da muss man also mit Pointern arbeiten. Die Rückgabe erfolgt über
r0, was hier als In-Out-Register angegeben wird.
Alternativ macht man es einfach so, dass man in C(++) nur einen
Funktionsprototypen erstellt:
1
#ifdef __cplusplus
2
extern"C"
3
#endif
4
uint32_tsleep(uint32_ttime);
Und dann eine Assembler-Datei hinzulinkt mit
1
.type sleep, %function
2
sleep:
3
svc #1
So hat man im Exception-Handler automatisch das selbe ABI wie bei
Funktionen (Übergabe in r0-r3, Rückgabe in r0, r12 beliebig nutzbar),
aber die Funktion kann nicht geinlined werden, d.h. man hat immer einen
Sprung.
W.S. schrieb:> Ich habe selber sowas schon erlebt, deswegen weiß ich, daß man dann das> Sperren und Entsperren kellern muß, also einen Zähler beim Sperren> raufzählen und beim Entsperren wieder herunterzählen und nur dann, wenn> er Null wird, tatsächlich wieder entsperren.
Ich habe sowas noch nie erlebt.
Die Sperre erfolgt natürlich nur für den konkreten atomaren Zugriff.
Wer Interrupts nested über mehrere Funktionsebenen hinweg sperrt, der
hat das Prinzip nicht verstanden: Die Sperre darf nur so kurz wie nötig
sein!
Das gleiche gilt für volatile. Das verteilt man ja auch nicht mit der
Schöpfkelle, sondern benutzt es immer nur dann, wenn nötig.
W.S. schrieb:> Dann laß dir dein Lehrgeld zurückzahlen. Jeder Ringpuffer ist im Grunde> EASY. Es gibt einen Schreibzeiger und einen Lesezeiger, die atomar les-> und schreibbar sind
Nun und genau das muß man mit Interruptsperre absichern. Zeiger sind nur
dann von Haus aus atomar zugreifbar, wenn ihre Größe der Bitbreite der
CPU entspricht. Beim hier oft verwendeten AVR ist das nicht der Fall.
Man kann aber beim AVR die Sperre umgehen, wenn man nicht Pointer
sondern Indexe speichert und die FIFO <256Byte groß ist. Dann reicht ein
uint8_t zum speichern, d.h. der Index ist atomar zugreifbar.
Ich benutze für FIFOs grundsätzlich Indexe, weil das fehlersicherer ist.
Einen Index teste ich auf Überlauf, d.h. er kann niemals in den Wald
zeigen, wie ein Pointer es kann und mir dann andere Daten zerstören.
Das nennt sich defensive Programmierung.
Peter D. schrieb:> Nun und genau das muß man mit Interruptsperre absichern. Zeiger sind nur> dann von Haus aus atomar zugreifbar, wenn ihre Größe der Bitbreite der> CPU entspricht. Beim hier oft verwendeten AVR ist das nicht der Fall.
Wie immer denkst du mal wieder grundfalsch.
Was meinst du denn, wie groß ein Ringpuffer zur sicheren Datenübergabe
zwischen ISR und Grundprogramm sein muß?
Normalerweise reicht dafür ein Ringpuffer von 4..8 Plätzen aus, weil
dann, wenn dieser tatsächlich überlaufen sollte, der Prozessor ohnehin
hoffnungslos überlastet ist.
Und dafür reicht als Zeiger bzw. Index im Puffer jeweils ein Byte
problemlos aus. Und sowas ist auch in jedem AVR atomar. Selbst auf einem
steinalten 4 Bit Controller von NEC würde das Nibble ausgereicht haben.
Hast du das jetzt?
Warum polterst du so oft los, ohne die Sache hinreichend überdacht zu
haben?
W.S.
W.S. schrieb:> Normalerweise reicht dafür ein Ringpuffer von 4..8 Plätzen aus, weil> dann, wenn dieser tatsächlich überlaufen sollte, der Prozessor ohnehin> hoffnungslos überlastet ist.
Und wie legt man da z.B. Ethernet-Pakete drin ab?
Im Ringpuffer-Beispiel sollte man die Indices über _Atomic
implementieren. So wird man das Problem mit den Zugriffen portabel los
(ok, AVR-GCC kann das bestimmt nicht), und kann auch dieses hässliche
1
asmvolatile("":::"memory");
weglassen indem man die korrekte memory order angibt.
Dr. Sommer schrieb:> Deine RBPut und RBGet Funktionen können jeweils nur 1 Byte bearbeiten.> Die für jedes Byte einzeln aufzurufen wäre ziemlich langsam. Was machst> du in dieser Situation:
Ringpuffer füllen sind maximal 2x memcpy, deren Grenzen sich trivial
berechnen lassen.
Und damit das 1-Byte-Memcpy die Performance beeinflussen kann, müsste
die Interrupt Quelle oder Senke schon relativ schnell arbeiten.
Dr. Sommer schrieb:> Und wie legt man da z.B. Ethernet-Pakete drin ab?
Gar nicht. Für sowas nimmt man Scatter-Gather DMA, denn ansonsten wären
die notwendigen memcpy() zu teuer für einen kleinen µC.
Man erinnere sich: Die alten Realtek 8139 PCI erreichten nur ab PII/400
die vollen 100MBit/s, weil da wegen Alignment Problemen zuätzliches
memcpy() nötig war. Mit Intel Karten erreichte man 100MBit/s schon ab
ca. 100MHz CPU Takt.
Dr. Sommer schrieb:> Im Ringpuffer-Beispiel sollte man die Indices über _Atomic> implementieren
Der Witz ist dass du den Ringpuffer mit <=256 Einträgen baust, damit
sind die Indizes nur noch Bytes und so automagisch Atomar.
Größere Ringpuffer braucht man nur relativ selten - insbesondere da die
8-Bit AVR mit RAM knausern. Auf 32-Bit ARM sind 32-Bit Zugriffe Atomar.
Jim M. schrieb:> Ringpuffer füllen sind maximal 2x memcpy, deren Grenzen sich trivial> berechnen lassen.
Ich würde Zero-Copy bevorzugen.
Jim M. schrieb:> Und damit das 1-Byte-Memcpy die Performance beeinflussen kann, müsste> die Interrupt Quelle oder Senke schon relativ schnell arbeiten.
Soll vorkommen.
Jim M. schrieb:> Gar nicht. Für sowas nimmt man Scatter-Gather DMA, denn ansonsten wären> die notwendigen memcpy() zu teuer für einen kleinen µC.
Und was macht man auf Mikrocontrollern die nur DMA ohne Scatter-Gather
haben?
Jim M. schrieb:> Größere Ringpuffer braucht man nur relativ selten - insbesondere da die> 8-Bit AVR mit RAM knausern. Auf 32-Bit ARM sind 32-Bit Zugriffe Atomar.
Das _Atomic ersetzt aber auch auf 32-Bittern das
Assembler-Speicher-Optimierungs-Barrier.
W.S. schrieb:> Peter D. schrieb:>> Nun und genau das muß man mit Interruptsperre absichern. Zeiger sind nur>> dann von Haus aus atomar zugreifbar, wenn ihre Größe der Bitbreite der>> CPU entspricht. Beim hier oft verwendeten AVR ist das nicht der Fall.>> Wie immer denkst du mal wieder grundfalsch.>
Meine Güte, deine Selbstherrlichkeit stinkt zum Himmel!
> Was meinst du denn, wie groß ein Ringpuffer zur sicheren Datenübergabe> zwischen ISR und Grundprogramm sein muß?>> Normalerweise reicht dafür ein Ringpuffer von 4..8 Plätzen aus, weil> dann, wenn dieser tatsächlich überlaufen sollte, der Prozessor ohnehin> hoffnungslos überlastet ist.>
Inkompetenter Blödsinn. Je nachdem was "das Grundprogramm" bzw. die an
der Abarbeitung betiligten tasks so alles zu tun haben ist die
Umlaufzeit der WEGarbeitung komplett indeterministisch. Eine
Pauschalaussage ist hier absolut fehl am Platz. Es reicht schon eine
serielle Kommunikation mit 76800 Baud mit kurzen bursts von (in deinem
Beispiel 9 bytes), dann ist die Mathematik dahinter einfach, wie lange
(kurz) die Verarbeitungstask maximal Latenz haben darf um die Daten
wegzuschaufeln. Von komplexeren Systemen scheinst Du Dich immer fern
gehaletn zu haben!
> Und dafür reicht als Zeiger bzw. Index im Puffer jeweils ein Byte> problemlos aus. Und sowas ist auch in jedem AVR atomar. Selbst auf einem> steinalten 4 Bit Controller von NEC würde das Nibble ausgereicht haben.>> Hast du das jetzt?> Warum polterst du so oft los, ohne die Sache hinreichend überdacht zu> haben?>
Das Kompliment darf man gerne zurück geben.
> W.S.
The other Peter schrieb:> Inkompetenter Blödsinn. Je nachdem was "das Grundprogramm" bzw. die an> der Abarbeitung betiligten tasks so alles zu tun haben ist die> Umlaufzeit der WEGarbeitung komplett indeterministisch.
Dann ist das Programm aber Müll, weil es abstürzen kann. Du brauchst
eine obere Schranke für die Abarbeitung, schon alleine wenn man einen
Watchdog korrekt benutzen möchte.
Mit der oberen Schranke kann man dann auch den Ringpuffer
dimensionieren.
BTW: Bei 76800 Baud und 8Byte Ringpuffer muss ich nur etwas schneller
als 1x pro ms (0.9ms) den Ringpuffer in der Hauptschleife leeren. Das
passen bei einem hoch getaktetem STM32 recht viele Zyklen dazwischen...
Jim M. schrieb:> The other Peter schrieb:>> Inkompetenter Blödsinn. Je nachdem was "das Grundprogramm" bzw. die an>> der Abarbeitung betiligten tasks so alles zu tun haben ist die>> Umlaufzeit der WEGarbeitung komplett indeterministisch.>> Dann ist das Programm aber Müll, weil es abstürzen kann. Du brauchst> eine obere Schranke für die Abarbeitung, schon alleine wenn man einen> Watchdog korrekt benutzen möchte.> Mit der oberen Schranke kann man dann auch den Ringpuffer> dimensionieren.
Sorry, auch das ist Unfug. Jedes fehlerhafte Programm kann abstürzen,
aber ein gut geschriebenes Programm i.Z. mit einem fehlertoleranten
Protokoll über die serielle Schnittstelle hat mit Überläufen keinerlei
Probleme, jedenfalls kann es auch nicht mehr oder weniger abstürzen als
Andere Programme.
Und was das mit einem WatchDog zu tun haben soll, hätte ich auch gerne
erklärt bekommen.
>> BTW: Bei 76800 Baud und 8Byte Ringpuffer muss ich nur etwas schneller> als 1x pro ms (0.9ms) den Ringpuffer in der Hauptschleife leeren. Das> passen bei einem hoch getaktetem STM32 recht viele Zyklen dazwischen...
Nun nimm aber mal eine Software, die mehrere Schnittstellen parallel
bedient und dann vielleicht noch ein Hostinterface über Ethernet bedient
und nebenbei auch etwas mit den Daten tun soll, die da immer fleissig
von A nach B geschoben werden. Da kommt man bald an den Punkt, wo auf
einer Schnittstelle mal das eine oder Andere Zeichen verloren gehen kann
- ohne dass der Prozessor "hoffnungslos überlastet" wäre (wie es der
Herr Oberlehrer ausdrückt).