Ich programmiere gerade serielle Routinen, die interruptgesteuert Daten
vom Arduino zum PC schicken sollen, d.h. im ATMEGA8 gibt es einen
Buffer, der mittels Interrupt USART_UDRE_vect "abgearbeitet" wird. Wenn
der Transmitter meldet, dass das Zeichen gesendet worden ist, wird
mittels Interrupt das nächste Zeichen zum Versand gegeben.
Das funktioniert auch schon ganz gut. Ich habe jetzt im Hauptprogramm
(also nicht in der Interruptroutine) 2 Fälle:
1. genug Platz noch im Buffer, kein Problem, ich kann die Daten in den
Buffer geben zum Versand
2. der Buffer ist voll. In dem Fall muss ich wohl warten, bis die
Interruptroutine meldet, dass sie fertig ist, damit ich das nächste
Zeichen senden kann.
Das ist alles klar. Beim Warten muss ich natürlich immer wieder den
Buffer abfragen, ob schon Platz ist, daher muss ich hier kurzfristig die
Interrupts sperren, schauen, ob Platz ist, und natürlich die Interrupts
wieder freigeben, damit der Interrupt passieren kann.
Und jetzt endlich die Frage: in dieser Schleife hatte ich den Code in
etwa
1
while(buffer_voll)
2
{
3
sei();
4
cli();
5
}
Und jetzt das Problem: Hier kommt offensichtlich der Interrupt nicht
immer durch. Es funktioniert, wenn ich schreibe
1
while(buffer_voll)
2
{
3
sei();
4
_delay_us(1);
5
cli();
6
}
Heisst das, dass nach einem sei() immer ein anderer Befehl gemacht
werden muss, damit die Interrupts verlässlich durchkommen? Oder gibt es
hier einen anderen Effekt? Wie gesagt, es funktioniert manchmal, und
manchmal bleibt er hängen, ich vermute, er läuft in dem Fall endlos in
der Schleife.
ps.: volatile ist in Verwendung, das ist es nicht.
Dein Fall ist extrem ungewöhnlich. Warum sei() und cli() direkt
hintereinander? Damit hat die CPU quasi keine Zeit für einen Interrupt.
Interrupts schaltet man nur ab, wenn es unbedingt notwendig ist. Die
Pufferverwaltung bei der seriellen Übertragung gehört normalerweise
nicht dazu. Das geht problemlos bei eingeschalteten Interrupts.
Deine while-Schleife implementiert ein Busy-Wait und verbraucht viel CPU
Zeit und damit auch Strom. Das delay ist aus diesen Gründen durchaus
sinnvoll. Aber 1µs ist extrem kurz. So schnell wird deine serielle
Übertragung nicht sein.
Ich benutze für sowas normalerweise einen Puffer mit z.B. 16 Zeichen und
einem Lese- und einem Schreib-Zeiger (beide volatile). Damit sind beide
Seiten voneinander unabhängig.
Wenn der Puffer der seriellen Schnittstelle voll ist und nichts anderes
zu tun ist, würde ich den Arduino in den Sleep Mode schicken. UART und
andere Peripherie können weiterlaufen. Aus dem Sleep Mode kann der
Arduino bei einem Interrupt oder nach einem Timeout automatisch
aufwachen.
Erwin U. schrieb:> ... daher muss ich hier kurzfristig die Interrupts sperren, schauen, ob Platz
ist, ...
Warum musst du dazu den Interrupt sperren? Wie stellst du fest, dass
Platz in deinem Puffer ist? Es kann dir doch egal sein, ob du den freien
Pufferplatz in der aktuellen oder in der nächsten Abfrage feststellst.
Was ist dein eigentliches Problem, dass du mit dem Sperren und wieder
Freigeben der Interrupts lösen möchtest?
So etwas bizarres habe ich selten gesehen.
Gerade auf einem Arduino ist doch genug Code vorhanden, der das schon
macht.
Oder Code, den man sich anschauen kann, wie Profis das machen.
Warum meint jeder, dass er es besser kann, als die Generationen vor ihm?
Erwin U. schrieb:> Heisst das, dass nach einem sei() immer ein anderer Befehl gemacht> werden muss, damit die Interrupts verlässlich durchkommen?
Ja, so steht es im Datenblatt.
Diese Arbeitsweise ist äußerst nützlich. Z.B. kommt dann immer noch das
Main zum Zuge, wenn ein Interrupt nicht gelöscht wird. Oder es wird
garantiert, daß ein Sleep vor dem Aufwachinterrupt ausgeführt wird.
Erwin U. schrieb:> Heisst das, dass nach einem sei() immer ein anderer Befehl gemacht> werden muss, damit die Interrupts verlässlich durchkommen?
Ein Sleep-Befehl passt da perfekt hin!
Erwin U. schrieb:> Das ist alles klar. Beim Warten muss ich natürlich immer wieder den> Buffer abfragen, ob schon Platz ist, daher muss ich hier kurzfristig die> Interrupts sperren, schauen, ob Platz ist, und natürlich die Interrupts> wieder freigeben, damit der Interrupt passieren kann.
Seit mindestens 50 Jahren sind Ringpuffer erfunden worden, bei denen man
auch ohne Interruptsperre für die Abfragen auskommt. Stichwort
Producer/Consumer-Schema.
Des Weiteren sind Warteschleifen, die einfach nur Zeit verballern, die
Babysprache der Programmierung. Damit sollte man aber keine Programme
mit Interrupts selbst erstellen. Wenn man keine bessere Anleitung dafür
hat, bietet selbst die vielgeschmähte Arduino-Umgebung zwei
Blinkprogramme zur Anschauung, einmal in Babysprache und einmal in
Erwachsenensprache, allerdings nur für den Umgang mit Timerinterrupts.
Gruß Klaus (der soundsovielte)
Erwin U. schrieb:> Ich programmiere gerade serielle Routinen, die interruptgesteuert Daten> vom Arduino zum PC schicken sollenErwin U. schrieb:> der Buffer ist voll. In dem Fall muss ich wohl warten
Es wäre äußerst ungewöhnlich wenn eine serielle Routine, die asynchron
Daten verschicken soll, dann doch in Sonderfällen wartet.
Erwin U. schrieb:> ATMEGA8
Welcher Arduino ist das denn?
Erwin U. schrieb:> daher muss ich hier kurzfristig die Interrupts sperren, schauen, ob> Platz istMichael H. schrieb:> einem Lese- und einem Schreib-Zeiger (beide volatile)
Das ist beste Lösung, weil dadurch buffer_voll atomar wird.
LG, Sebastian
Zur Klarstellung:
1. die seriellen Routinen sind ein Beispiel für den Unterricht und
sollen verdeutlichen, wie man mit einem Ringbuffer umgeht. Sie enthalten
einen Schreib- und einen Lesezeiger, beide volatile.
2. Alle, die meinen, man müsste nicht warten, stelle ich mal die
Gegenfrage: was machst du, wenn dein Buffer bereits voll ist und du im
Programm weitere Daten an die Schnittstelle loswerden willst? Ich habe
diesen Fall natürlich durch geringe Baudrate etc. provoziert, aber ich
denke, jeder gute Programmierer sollte seinen Code ausprobieren.
3. Der Zugriff auf volatile Variable sollte immer nur mit gesperrtem
Interrupt erfolgen, damit keine Laufzeiteffekte auftauchen. Wenn ich
jetzt, nur in dem seltenen und unerwünschten Fall, dass mein Buffer voll
ist, warten muss, dann ist eine Schleife, die einfach Interrupts
freigibt und danach wieder sperrt, sinnvoll.
- Ich sehe den Kommentar mit dem sleep() als sehr sinnvoll, ich wollte
aber im Unterricht keine zusätzliche Komplexität einführen.
- Ein Kommentar sagt, wenn ich sofort wieder sperre, dann hat die CPU
quasi keine Zeit, einen Interrupt auszulösen. Ist das jetzt die Antwort
auf meine Frage? Immerhin müsste der Interrupt ja gequeued sein, und
sofort ausgelöst werden, oder macht Atmel dazu irgendeine Aussage?
- Auch vielen Dank für den Hinweis auf die Blinkprogramme, ich werde sie
mir mal anschauen, ob sie mir bei der Beantwortung meines Problems
helfen können ;-)
Sebastian W. schrieb:> Es wäre äußerst ungewöhnlich wenn eine serielle Routine, die asynchron> Daten verschicken soll, dann doch in Sonderfällen wartet.
Es bleibt ihr schlichtweg nichts anderes übrig, will sie die Daten nicht
verstümmeln. Daher machen es die Standardroutinen so.
Solange der FIFO ausreicht, ist alles fein. Will das Main aber längere
Daten versenden, als man Puffer reserviert hat, muß es eben warten.
Man könnte höchstens mit malloc eine weiteres Häppchen dem FIFO
hinzufügen, muß dann aber aufpassen, daß irgendwann der RAM auch wieder
freigegeben wird und nicht überläuft.
Das Main zu verlangsamen ist daher allgemein die beste Lösung.
Hallo!
Ich habe gerade gesehen, dass ich die Antwort bereits erhalten habe.
Vielen Dank an Peter D. ich habe das Handbuch durchsucht, bin aber nicht
fündig geworden. Ich nehme an, ich sollte alle 600 Seiten mal lesen und
nicht nur suchen ;-)
Ist etwas versteckt im Datenblatt des ATmega8.
Abschnitt "Reset and Interrupt Handling":
"When the AVR exits from an interrupt, it will always return to the main
program and execute one more instruction before any pending interrupt is
served."
"When using the SEI instruction to enable interrupts, the instruction
following SEI will be executed before any pending interrupts, as shown
in the following example."
Erwin U. schrieb:> die seriellen Routinen sind ein Beispiel für den Unterricht
Gerade bei einer Lösung für den Unterricht (Ich nehme an als
Lehrer/Dozent, nicht als Hausaufgabe) sollte man eine saubere,
durchdachte, schöne Lösung präsentieren, und nicht irgendwas hinfrickeln
und anschließend volatile und cli/sei drübersprinkeln bis es irgendwie
funktioniert.
So als Denkanstoß:
Fang mit deinen Lese- und Schreib-Pointern oder Indices an. Sind die 8
oder 16 Bit?
Wieviele Bit kann der µC atomar lesen, ohne dass eine IRQ-Sperre nötig
ist?
Welcher Prozess schreibt welchen Pointer? Kann es vorkommen, dass sowohl
main als auch die ISR denselben Pointer beschreiben wollen?
Warum meinst du "volatile" zu brauchen? Weil das irgendein Tutorial
irgendwann mal behauptet hat? Wenn deine Schüler nach dem "volatile"
fragen, antwortest du dann:
"Das macht man halt so, weil der 13-Jährige Timmy das in seinem
TikTok-Video so gesagt hat"?
Ist's um Compiler-Optimierungen zu verhindern?
Braucht es das an allen Stellen, oder ist es z.B. in der ISR sinnfrei,
weil während dem ISR-Lauf sowieso niemand anderes an deinen Pointern
dreht?
Brauchst du es in main()? Brauchst du es in main an allen Stellen, oder
nur an ausgewählten? Brauchst du es in main immer noch, wenn du weißt
dass cli()/sei() eine memory barrier beinhalten, die das Neu-Lesen der
Variable auch ohne "volatile" erzwingen?
Die "ATOMIC"-Helper aus der avr-libc sind bekannt?
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
Peter D. schrieb:> Es bleibt ihr schlichtweg nichts anderes übrig, will sie die Daten nicht> verstümmeln.
Ich wollte darauf hinaus, dass es schlechtes Vertragsdesign ist, wenn in
einer Routine, deren Zweck es ist, nicht zu warten, dann doch manchmal
und unerwartet gewartet wird.
LG, Sebastian
Erwin U. schrieb:> Der Zugriff auf volatile Variable sollte immer nur mit gesperrtem> Interrupt erfolgen, damit keine Laufzeiteffekte auftauchen.
Das ist nur nötig, wenn der Prozessor den Lese- und Schreibzugriff nicht
auf die komplette Wortbreite atomar hinbekommt. Seit 1980 übliche
Architekturen schaffen einen 8-Bit Schreib- und Lesezugriff aber
garantiert atomar. Damit kann ich einen Buffer bis 256 Datensätze ohne
jegliche Interruptsperre sicher und kollisionsfrei hinbekommen.
Mal abgesehen von der nicht so ganz durchdachten Nutzung von sei/cli
geht es hier also um die Frage, was tun, wenn Murks passiert. In diesem
Fall, wenn mehr Daten anfallen als man weiter geben kann.
Auch wenn man sich immer sehr ungern von etwas trennt, wird man wohl
früher oder später Daten wegwerfen müssen, weil
- kein Puffer unendlich groß ist
- während dem Warten auf Platz im Puffer die Auswertung stockt, und ihr
dadurch Daten entgehen
Also mehr als zu erkennen, dass Mist passiert, und die Stelle für den
Empfänger zu markieren ist vermutlich nicht drin.
Oder bist Du in der glücklichen Lage, dass keine Daten anfallen, wenn
niemand fragt ? Dann musst Du nur die Rückmeldung "ich habe Platz im
Puffer (gemacht)" vom Interrupt zum Hauptprogramm atomar machen. D.h.
der µC kann es auf einmal erfassen, ohne dass ihn jemand dazwischen
stören kann. Es reicht dabei ja schon ein Flag "habe gerade ein Datum
aus dem Puffer genommen". Wenn Du dagegen mit einem 8-Biter zwei Zeiger
zu je 32 Bit auswerten musst, um zu wissen, ob Platz im Puffer ist, wird
es schwer mit atomar.
Klaus S. schrieb:> Seit mindestens 50 Jahren sind Ringpuffer erfunden worden, bei denen man> auch ohne Interruptsperre für die Abfragen auskommt. Stichwort> Producer/Consumer-Schema.
Ganz sicher nicht. Wenn ein FIFO mit einem Interrupt arbeitet,
müssen zumindest für die sehr kurzen Abfragen des Füllstandes die
Interupts gesperrt werden.
Erwin U. schrieb:> Ich programmiere gerade serielle Routinen, die interruptgesteuert Daten> vom Arduino zum PC schicken sollen, d.h. im ATMEGA8 gibt es einen> Buffer, der mittels Interrupt USART_UDRE_vect "abgearbeitet" wird.
Nichts besonderes.
> Wenn> der Transmitter meldet, dass das Zeichen gesendet worden ist, wird> mittels Interrupt das nächste Zeichen zum Versand gegeben.
Nein. Wenn der Hardware Sendepuffer leer ist (UDRE). Das ist was
anderes.
> Das funktioniert auch schon ganz gut. Ich habe jetzt im Hauptprogramm> (also nicht in der Interruptroutine) 2 Fälle:> 1. genug Platz noch im Buffer, kein Problem, ich kann die Daten in den> Buffer geben zum Versand
Welcher Buffer? Der Hardwarebuffer des UARTs oder dein Software-Buffer?
> 2. der Buffer ist voll. In dem Fall muss ich wohl warten, bis die> Interruptroutine meldet, dass sie fertig ist, damit ich das nächste> Zeichen senden kann.
Nö, du musst warten, bis Platz im Buffer ist. Das passiert im
Huntergrund durch den UDRE-Interrupt, welcher den Inhalt deines
Software-FIFOs in den Hardware-UART schaufelt.
> Das ist alles klar. Beim Warten muss ich natürlich immer wieder den> Buffer abfragen, ob schon Platz ist, daher muss ich hier kurzfristig die> Interrupts sperren, schauen, ob Platz ist, und natürlich die Interrupts> wieder freigeben, damit der Interrupt passieren kann.
Stimmt, dafür gibt es im avr gcc die Funktion ATOMIC_BLOCK(){}
> Und jetzt endlich die Frage: in dieser Schleife hatte ich den Code in> etwawhile (buffer_voll)> {> sei();> cli();> }
Solche Fragmente sich Schrott. Zeig deinen REALEN Quelltext, siehe
Netiquette.
> Und jetzt das Problem: Hier kommt offensichtlich der Interrupt nicht> immer durch. Es funktioniert, wenn ich schreibewhile (buffer_voll)> {> sei();> _delay_us(1);> cli();> }
Ist Unsinn und ein Indiz für einen Programmier- oder Konzeptfehler.
> Heisst das, dass nach einem sei() immer ein anderer Befehl gemacht> werden muss, damit die Interrupts verlässlich durchkommen?
Nein.
> Oder gibt es> hier einen anderen Effekt?
Ja, siehe oben.
> Wie gesagt, es funktioniert manchmal, und> manchmal bleibt er hängen, ich vermute, er läuft in dem Fall endlos in> der Schleife.
Nicht "vermuten", TESTEN!
> ps.: volatile ist in Verwendung, das ist es nicht.
Auch immer mit atomarem Zugriff?
So geht's.
https://www.mikrocontroller.net/articles/FIFO#FIFO_als_Bibliothek
Lothar M. schrieb:> Damit kann ich einen Buffer bis 256 Datensätze ohne> jegliche Interruptsperre sicher und kollisionsfrei hinbekommen.
Ja und? Was hast du dann gewonnen? Die handvoll Takte für die
Interruptsperre für Daten >8 Bit sind in den allermeisten Fällen
vollkommen unkritisch. Das Problem des OPs liegt sowieso woanders.
Sebastian W. schrieb:>> Es bleibt ihr schlichtweg nichts anderes übrig, will sie die Daten nicht>> verstümmeln.>> Ich wollte darauf hinaus, dass es schlechtes Vertragsdesign ist, wenn in> einer Routine, deren Zweck es ist, nicht zu warten, dann doch manchmal> und unerwartet gewartet wird.
Stimmt, aber das ist bei einem FIFO eben manchmal der Fall. Und das muss
auch klar definiert und kommuniziert werden.
. . . die Funktion wartet auf freien Speicher, wenn dieser nicht
vorhanden ist . . .
Falk B. schrieb:> Wenn ein FIFO mit einem Interrupt arbeitet, müssen zumindest für die sehr> kurzen Abfragen des Füllstandes die Interupts gesperrt werden.
Für Fifos habe ich seit Jahrzehnten keinen Interrupt mehr gesperrt. Wenn
wie gesagt die Pointer jeweils nur an 1 Stelle geschrieben und sonst nur
gelesen werden und zudem Zugriffe auf die Pointer atomar sind, dann muss
kein Interrupt gesperrt werden.
Und der Füllstand ist im schlimmsten Fall dann eben 1 Datensatz in die
unkritische Richtung "falsch": der Sendefifo meldet "voll" obwohl 1
Datensatz Platz hätte, oder der Empfangsfifo meldet "leer" obwohl grade
1 Datensatz hereingekommen ist. Aber beim nächsten Durchlauf der
"Hauptschleife" passt dann wieder alles, es gehen sicher keine Daten
verloren.
Falk B. schrieb:> Was hast du dann gewonnen? Die handvoll Takte für die Interruptsperre> für Daten >8 Bit sind in den allermeisten Fällen vollkommen unkritisch.
Ffifos haben prinzipiell nichts mit Interrupts zu tun. Ich muss nicht
mit Interrupts herumhampeln, wenn der Code so ist, dass es per
Definition kein Problem mit Interrupts geben kann.
> in den allermeisten Fällen vollkommen unkritisch.
Genaus das: in ganz seltenen Fällen eben doch. Und dann kann man erst
mal ein paar Tage herumsuchen. Denn je seltener ein Fehler auftritt,
umso länger dauert die Suche.
Lothar M. schrieb:> Falk B. schrieb:>> Wenn ein FIFO mit einem Interrupt arbeitet, müssen zumindest für die sehr>> kurzen Abfragen des Füllstandes die Interupts gesperrt werden.> Für Fifos habe ich seit Jahrzehnten keinen Interrupt mehr gesperrt. Wenn> wie gesagt die Pointer jeweils nur an 1 Stelle geschrieben und sonst nur> gelesen werden und zudem Zugriffe auf die Pointer atomar sind, dann muss> kein Interrupt gesperrt werden.
Das sind ne Menge Nebenbedingungen! Die Interruptsperre hat ja eben das
Ziel, diese sicherzustellen! Wenn das dein (32)Bit Controller auch ohne
Sperre kann, umso besser.
> Falk B. schrieb:>> Was hast du dann gewonnen? Die handvoll Takte für die Interruptsperre>> für Daten >8 Bit sind in den allermeisten Fällen vollkommen unkritisch.> Ffifos haben prinzipiell nichts mit Interrupts zu tun.
Doch! Wenn eine Seite vom Hauptprogramm und die andere von einem
Interrupt zugreift, was oft der Fall ist!
> Ich muss nicht> mit Interrupts herumhampeln, wenn der Code so ist, dass ich per> Definition kein Problem mit Interrupts habe.
Jaja, wir definieren Probleme weg.
Lothar M. schrieb:> Aber beim nächsten Durchlauf der> "Hauptschleife" passt dann wieder alles, es gehen sicher keine Daten> verloren.
Was machst du in so einem Fall:
1
intmain(){
2
...
3
while(1){
4
...
5
if(fifoFill<max){
6
writeToFifo();
7
}else{
8
sleep_enable();
9
}
10
}
11
}
Was wenn der Fifo voll ist, dementsprechend der else-Fall betreten wird,
aber direkt vor dem Betreten des Sleep-Mode der Interrupt kommt und den
Fifo leert? Dann wird der Sleep-Mode trotzdem betreten aber ggf. nie
wieder verlassen, weil der Interrupt nicht nochmal kommt.
Niklas G. schrieb:> Was machst du in so einem Fall:
Zeig mehr vom Code. Wo wird der ominöse "fifoFill" berechnet?
Falk B. schrieb:> Jaja, wir definieren Probleme weg.
Wo kann da ein Problem auftreten, wenn ein Bytezugriff atomar ist:
Lothar M. schrieb:> Zeig mehr vom Code. Wo wird der ominöse "fofiFill" berechnet?
Das ist eine Möglichkeit einen Ringpuffer zu implementieren, um im Fall
readPtr==writePtr zu unterscheiden ob der Puffer voll oder leer ist.
Aber man kann es auch in deinem Code demonstrieren:
1
intmain(){
2
...
3
4
while(1){
5
...
6
// Fifo schreiben
7
if((fifo.w-fifo.r)&BMSK==BMSK){
8
// -- Wenn genau jetzt ein Interrupt den Ringpuffer komplett leert, bleiben wir ggf. ewig im Sleep-Modus --
Erwin U. schrieb:> Der Zugriff auf volatile Variable sollte immer nur mit gesperrtem> Interrupt erfolgen
Das ist nur bei Variablen hilfreich, die so groß sind, dass sie nicht
atomar gelesen/geschrieben werden können. Also bei AVR alle Variablen,
die größer als 8 Bit sind.
Steve van de Grens schrieb:> Das ist nur bei Variablen hilfreich, die aus mehr als einem Byte> bestehen.
Oder wenn sowohl main-Schleife als auch Interrupt auf der Variablen ein
Read-Modify-Write machen.
Falk B. schrieb:> Ja und? Was hast du dann gewonnen? Die handvoll Takte für die> Interruptsperre für Daten >8 Bit sind in den allermeisten Fällen> vollkommen unkritisch.
Ja schon. Aber solange man sei(), cli(), volatile, und ATOMIC_BLOCK noch
nicht durchschaut, ist dieses Wissen hilfreich, denn derart kleine
Puffer kann man (einfach) ganz ohne diese Maßnahmen implementieren.
Niklas schrieb:
> [Beispiel]> Was wenn der Fifo voll ist, dementsprechend der else-Fall betreten wird,
Ganz einfach: der Code ist broken ;-) Dieses Test-und-Sleep muss
sorgfältig programmiert werden, damit keine Race-Conditions entstehen.
Bei mir steckt das in den Macros sleep_unitl/sleep_while und sähe so
aus:
1
volatileuint8_tfifoFill;
2
intmain(){
3
...
4
while(1){
5
...
6
sleep_until(fifoFill<max);
7
writeToFifo();
8
}
9
}
Die passenden (low-level) Makros:
1
// sleeping
2
// tries /exp/ first with ints enabled,
3
// but then with ints disabled before actually sleeping.
4
// Note: you got something wrong if /exp/ does not evaluate a volatile!
Niklas schrieb:
> Hätte ich auch so gemacht, aber es wurde behauptet:>> Lothar M. schrieb:>> Für Fifos habe ich seit Jahrzehnten keinen Interrupt mehr gesperrt
Die Fifo-Routinen selbst brauchen ja auch keine Interruptsperren. Es
ist das Schlafen, was sie benötigt (atomic test-and-sleep).
Solange man in dem Beispiel nicht zu schnell sendet, werden auch hier
nie Interrupts gesperrt - das passiert nur, wenn feststellt wird, dass
der Buffer voll ist (irqs sperren, nochmal testen, wenn immer noch voll
dann sleep, irqs freigeben).
> Über genau das Problem bin ich letztens beim ARM gestolpert:
Jo, das WFI scheint etwas gutmütiger zu sein (schläft nur, wenn kein irq
pending ist).
Foobar schrieb:> Die Fifo-Routinen selbst brauchen ja auch keine Interruptsperren. Es> ist das Schlafen, was sie benötigt (atomic test-and-sleep).
Genau das ist es. Wenn ich einen sleep() verwenden will, muss ich mir
anschauen, welche Wechselwirkungen der hat.
Aber das mit dem Schlafen kam erst im zweiten Post dazu. Der TO will
nichts in den Schlaf versetzen, sondern das Prinzip eines Fifos mit
einem Ringpuffer vermitteln. Und dazu braucht er keinen Interrupt
sperren.
Steve van de Grens schrieb:>> Ja und? Was hast du dann gewonnen? Die handvoll Takte für die>> Interruptsperre für Daten >8 Bit sind in den allermeisten Fällen>> vollkommen unkritisch.>> Ja schon. Aber solange man sei(), cli(), volatile, und ATOMIC_BLOCK noch> nicht durchschaut, ist dieses Wissen hilfreich, denn derart kleine> Puffer kann man (einfach) ganz ohne diese Maßnahmen implementieren.
Das würde ich genau anders herum sehen. Man sollte die Grundlagen von
volatile, atomarem Zugriff und Interrupts erstmal verstehen und dann den
FIFO bauen. Denn sonst könnte der irrtümliche Eindruck entstehen, daß
man da nichts beachten muss. Das funktioniert in einigen Fällen
vielleicht, dann aber oft durch Glück und Zufall. Eben so wie der Lothar
das alles als easy und null Problemo darstellt. Das ist FALSCH! Das ist
es NUR, wenn man GENAU weiß was man tut und wo man bestimmte Dinge wie
cli()/sei() weglassen kann. Allgemeingültig ist das keine Sekunde!
Lothar M. schrieb:> Und dazu braucht er keinen Interrupt> sperren.
Schon wieder so eine vollkommen falsche und gefährliche
Verallgemeinerung! Das gilt nur unter bestimmten Bedingungen!
Und was ist plötzlich an einer sehr kurzen Interruptsperre so kritisch?
Was soll die Hysterie? Eine handvoll sehr kurze ATOMIC_BLOCK() tut
keinem weh, macht die Sache im Zweifel aber sicher.
Falk B. schrieb:> Eine handvoll sehr kurze ATOMIC_BLOCK() tut keinem weh, macht die Sache> im Zweifel aber sicher.
Solche, mehr oder weniger blind in den Code gestreute
Sicherungsmaßnahmen zeugen nur von mangelndem Überblick über die
ablaufenden Vorgänge.
Rainer W. schrieb:>> Eine handvoll sehr kurze ATOMIC_BLOCK() tut keinem weh, macht die Sache>> im Zweifel aber sicher.>> Solche, mehr oder weniger blind in den Code gestreute> Sicherungsmaßnahmen zeugen nur von mangelndem Überblick über die> ablaufenden Vorgänge.
Jaja, noch so ein Schlaumeier. Von blind hingestreut war nie die Rede!
Thema verfehlt, 6!
Ich erzähl euch mal, wie wir früher™ serielle Ausgaben programmiert
haben. Da hatten wir manchmal Interfaces, die gar keinen Interrupt
erzeugen konnten. Wir mussten aber puffern, damit die Ausgabe nicht
stotterte und das Hauptprogramm nicht durch unnötiges Warten gebremst
wurde. Da haben wir einen periodischen Timer-Interrupt hergenommen und
in der ISR den Pufferfüllstand UND den Interface-Status "Frei zur
Ausgabe" abgefragt und dann ggf. ein Zeichen ausgegeben. Natürlich
entstand da eine kleine Latenz, aber dafür war das Ganze interrupt-mäßig
absolut zuverlässig. Wenn der UART einen kleinen Puffer hatte, konnte
man in der ISR auch gleich mehrere Zeichen in einem Rutsch ausgeben und
so die Latenz mildern.
Statt eines Timers konnte man ggf. auch einen anderen periodischen
Interrupt nehmen, der von irgendeinem Signal kam (z. B. Umdrehungszähler
eines Motors).
Falk B. schrieb:> Das ist es NUR, wenn man GENAU weiß was man tut und wo man bestimmte> Dinge wie cli()/sei() weglassen kann.
Ich sehe es genau andersrum: cli() und sei() gehören nur dorthin, wo sie
tatsächlich nötig sind.
Aber seins durm: es darf jeder gerne die Interrupts seines Controllers
sperren und freigeben, wie er will. Er hat ihn bezahlt und kann ihn auf
jede ihm beliebte Art und Weise langsam machen. Aber bitte verschont
mich vor solchen Geräten, wo das dann "einfach so zur Sicherheit"
gemacht wird. Denn wer da keinen Plan hat, ob es nötig ist oder nicht,
der hat auch sonst noch irgendwo Macken im Design.
Erwin U. schrieb:> Ich habe gerade gesehen, dass ich die Antwort bereits erhalten habe.
Das ist zwar die Antwort auf deine Frage, aber eben nicht die Lösung des
ursächlichen Problems. Und gerade, weil du diesen Fifo im Unterricht
zeigen willst, sollte der sauber implementiert sein, sonst lernen die
Schüler noch irgendeine umständliche Implementierung ganz im Sinne von
"Überall, wo du nicht ganz sicher bist, was passiert, sperrst du im
Zweifelsfall besser mal die Interrupts!"
Und wie gesagt: bei geeigneter Implementioerung besteht absolut keine
Notwendigkeit da irgendwo die Interrupts zu sperren.
Erwin U. schrieb:> Wie gesagt, es funktioniert manchmal, und manchmal bleibt er hängen
Genau diese Race-Conditions kommen gerne vom Nicht-Verwenden von
volatile, wo es hingehört. Das Problem liegt hier nicht im geposteten
Code. Sondern das, was du gepostet hast, ist nur ein Work-Around um das
eigentlich Problem.
Zeig mal den gesamten Code dieses Fifos. Sooo arg viele Zeilen sollte
der ja nicht haben...
Statt mit der Keule alle Interrupts zu sperren, könnte man ja auch nur
die Freigabe des problamtischen Interrupts wegnehmen. Erspart einem
Ärger mit dem Kollegen, der einen Regler mit kritischem Timing in einem
anderen Teil der Software realisiert hat.
Die Verallgemeinerung, bei volatile musst Du immer Interrupts sperren
ist auch Humbug. SFRs sind auch als volatile deklariert und da bringt es
mir null zu glauben, dass sich der Wert der Digitaleingänge oder eines
Timers bei gesperrten Interrupts nicht ändert.
Dann hätten wir da auch noch Multicore-Architekturen, wo die Software
eines anderen Kerns die Daten trotz gesperrtem Interrupt aufmischen
kann.
Es hilft wohl echt nur ein paar mal auf die Schnauze gefallen zu sein,
um ein Augenmerk dafür zu entwickeln, wo die Party abgeht, wenn dies mal
genau zwischen dem da und jenem statt findet.
Hallo liebe Leute!
Ich bin überwältigt von der Fülle an Informationen, die ich da erhalten
habe. Daher nur soviel:
1. ich werde nicht mehr reagieren, weil ich die von mir gewünschten
Informationen bereits erhalten habe.
2. Ich bedanke mich für die Hinweise auf die vom Compiler gebotenen
Möglichkeiten, atomare Operationen zu machen.
3. Ebenso dafür, dass man FIFO´s programmieren kann, ohne Sperren.
Mein Lehrziel ist eigentlich, dass meine Schüler verstehen, dass
Interruptroutinen zwar äusserst nützlich, aber auch gefährlich sind,
wenn man nicht aufpasst. Und da werde ich sicher nicht als erstes auf
die Probleme hinweisen, und dann im 2. Schritt alles zu erklären, warum
sie diese Dinge erst jetzt nicht brauchen. Allerdings werde ich auf
Punkt 2. und 3. hinweisen, ebenso, dass sie meine Implementierung ja nie
als Referenz verwenden sollen. Vielleicht sollte auch vom Level der
Kenntnisse her klar sein, dass die Schüler einige Wochenstunden
Programmierung haben, seit ca. 1 Jahr sich mit C befassen, und seit
einigen Monaten mit Mikrocontrollern. Ich nehme an, viele der Leute im
Forum haben 30 Jahre Programmiererfahrung und mehr. Diejenigen bitte
ich, mal zurückzudenken an die Zeit, als sie begonnen haben, mit
Mikrocontrollern zu arbeiten ...
Allen Leuten mit "verschont mich mit solchem Code" oder ähnlichen
Aussagen:
Wie kauft ihr eigentlich ein neues Auto oder andere technische Dinge? In
jedem steckt in der Zwischenzeit ein Mikrocontroller, beim Auto
wahrscheinlich hundert. Macht ihr da Codereview von allen Programmen,
bevor ihr "zuschlagt"? Oder habt ihr Foren, wo darüber diskutiert wird,
wie Scheisse die Automarke xy ist, weil sie immer wieder bei 130 km/h
kleinere Probleme hat, weil einer der Controller da schlecht
programmiert ist? Oder kauft ihr einfach kein Auto? Wobei
Mikrocontroller heute überall drinnen sind. Denkt daran, wenn ihr zur
Maus greift, um mir böse zu antworten, da drinnen steckt auch ein
Controller und ihr wisst nicht, wie der programmiert ist ;-)
Ich habe auch schon Programme gefunden (public domain,
Amateurfunkbereich), da hatten die Programmierer keine Ahnung von
volatile und "Aufpassen bei solchen Zugriffen", da wäre es mir lieber
gewesen, die hätten mal meine Warnungen gehört.
Ich habe auch versucht, den 13-jährigen Timmy zu finden, der blöde
Anweisungen gibt, um meine Schüler vor ihm zu warnen. Leider habe ich
kein TikTok, aber im Youtube habe ich keinen gefunden. Und last not
least habe ich mir inzwischen die Blinkprogramme angeschaut, deren Code
hat mir aber leider überhaupt nicht weitergeholfen ... ;-)
In dem Sinne: danke für eure Hilfe!
Falk B. schrieb:> Und was ist plötzlich an einer sehr kurzen Interruptsperre so kritisch?
Sie ist kein Zeugnis optimaler Code-Durchdachtheit sondern immer nur
gedankliche WildWest Notlösung wenn man sonst nicht mehr weiter weiß.
Und wenn man nicht aufpasst verliert man damit noch mehr Durchblick,
weil es die Abläufe schlicht verkompliziert. Meine Erfahrungen früherer
Jahre- später war da nämlich kein cli mehr nötig ...
Rolf schrieb:> Da haben wir einen periodischen Timer-Interrupt hergenommen
Ein ganz wichtiges Mittel um Aufgaben systematisch voneinander zu
trennen und eine Art zuverlässiger Service-Struktur zu entwickeln wo
eines konfliktfrei aufs andere aufbaut.
Erwin U. schrieb:> dass> Interruptroutinen zwar äusserst nützlich, aber auch gefährlich sind,> wenn man nicht aufpasst.
Es gibt gewisse Regeln zu beachten.
So wie überall.
Falk B. schrieb:> Von blind hingestreut war nie die Rede!
"tut keinem weh" als Argument kannst du getrost mit "da ich nicht genau
drüber nachdenken will, was da passiert, baue ich es zur Sicherheit ein"
übersetzen.
Wenn nicht Blindheit, dann ist es zumindest Faulheit, die den Code
aufbläht.
Ein "es ist nötig, weil ... " wäre IHMO das einzig ernstzunehmende
Argument.
Ein Zusatz wie "... und ich keine andere Lösung gefunden habe" würde
eventuell noch Lernwilligkeit und Ansatzpunkte für weitere Verfeinerung
offenbaren ;-)
Frank O. schrieb:> Statt mit der Keule alle Interrupts zu sperren, könnte man ja auch nur> die Freigabe des problamtischen Interrupts wegnehmen. Erspart einem> Ärger mit dem Kollegen, der einen Regler mit kritischem Timing in einem> anderen Teil der Software realisiert hat.
Nö, damit wird man erst recht auf die Schnauze fallen.
Ein atomarer Zugriff kostet vielleicht 10 CPU Zyklen und tut niemandem
weh.
Nur den betreffenden Interrupt zu sperren, kann aber deutlich länger
dauern, bis alle anderen abgearbeitet sind.
Es kann sogar zur Prioritätsinversion kommen, d.h. jeder noch so
unwichtige Interrupt kriegt plötzlich Vorrang.
Z.B. ein schneller 8Bit-Timer muß innerhalb 128 Zyklen behandelt werden,
um einen Übertrag eindeutig zu erkennen. Sperrt man nur diesen
Interrupt, kann dann ein langsamer UART-Interrupt dazwischen grätschen
und der Timer geht nach dem Mond. Da dieser Konflikt vielleicht nur
selten auftritt, debugt man sich dann dumm und dämlich.
Peter D. schrieb:> Sperrt man nur diesen> Interrupt, kann dann ein langsamer UART-Interrupt dazwischen grätschen
Praktisch wenn man einen Prozessor mit Interrupt-Prioritäten hat, und
man einfach Interrupts bis zu einer bestimmten Priorität sperren kann
(z.B. Cortex-M).
Niklas G. schrieb:> Praktisch wenn man einen Prozessor mit Interrupt-Prioritäten hat
Verschiedene Interrupt-Prioritätslevel sollte man vermeiden wo immer
möglich.
Die reinste Nebel-Maschine gegen klaren Durchblick. Glücklicherweise
haben AVRs nur maximal zwei...
Niklas G. schrieb:> Praktisch wenn man einen Prozessor mit Interrupt-Prioritäten hatErwin U. schrieb:> Ich programmiere gerade serielle Routinen, die interruptgesteuert Daten> vom Arduino zum PC schicken sollen, d.h. im ATMEGA8 ...
ATmega8 != Cortex-M
Rainer W. schrieb:> "tut keinem weh" als Argument kannst du getrost mit "da ich nicht genau> drüber nachdenken will, was da passiert, baue ich es zur Sicherheit ein"> übersetzen.
Da fällt mir ein, was ich vor Jahrzehnten in einem Buch über
Software-Projektleitung gelesen habe. Das ging dem Sinn nach so:
"Programmierer Kevin meint, dass der Befehl "Lade das Spezialregister X
mit dem Wert Y" manchmal nicht funktioniert. Deshalb schreibt er den
Befehl zur Sicherheit immer zweimal hintereinander. Die Frage ist nun:
Wie erkennt man so einen Kevin im Team?"
> die den Code aufbläht
Dass es den Code aufbläht, ist ein schlechtes Argument, obwohl es
stimmt. Ein paar Bytes kosten wenig und der Zeitverlust ist oft
irrelevant. Wenn du eine richtige Sache mit schlechten Argumenten
verteidigst, schadet das, weil das auf deine gesamte restliche
Argumentation abfärbt. "Der erzählt nur Käse, z. B. will er unbedingt 3
Byte sparen und so was." – so denken viele die anderen dann.
Nein, der wichtige Punkt ist, dass man un- oder halbverstandene Sachen
selbst dann nicht einbauen soll, wenn sie anscheinend nach Tests
funktionieren. Die stehen dann nämlich für ewig im Code drin und
verursachen womöglich später nach Änderungen der Bedingungen oder des
Codes oder nach Übernahme von Teilen in völlig andere Projekte
"unerklärliche" Probleme. Es sind Zeitbomben.
Gerhard H. schrieb:> Verschiedene Interrupt-Prioritätslevel sollte man vermeiden wo immer> möglich.
Natürlich. Aber manchmal ist es halt doch hilfreich.
> Die reinste Nebel-Maschine gegen klaren Durchblick
Wenn man die Konzepte von "parallelem" Code wirklich verstanden hat,
bringte einen auch ein Interruptsystem mit mehreren Prioritäten nicht
in's Schleudern.
Das gab's übrigens auch bei den Classic-AVR schon, wenn man das wollte
und brauchte. Musste halt mangels HW-Unterstützung in Software gelöst
werden. Und ja: man muss schon wirklich verstehen, was da passiert, um
es richtig umsetzen zu können...
Erwin U. schrieb:> Mein Lehrziel ist eigentlich, dass meine Schüler verstehen, dass> Interruptroutinen zwar äusserst nützlich, aber auch gefährlich sind,> wenn man nicht aufpasst.
Gefährlich werden die erst, wenn jemand, der davon keine Ahnung hat,
damit eine Passagierflugzeugsteuerung programmiert.
Ansonsten sind die weder gefährlich noch sonst irgend etwas. Die sind
ein elementarer Bestandteil des Konzepts „Mikrocontroller“, und daher
muß man sich damit befassen.
Oliver