Forum: Mikrocontroller und Digitale Elektronik AVR: In Interrupt auf Interrupt warten


von Olaf (Gast)


Lesenswert?

Hallo liebe AVRler,

habe hier ein vertracktes Problem:

Ich muß aus dem Interrupt der seriellen Schnittstelle heraus viele
Datenpakete über die TWI-Schnittstelle senden.
Da die TWI ebenfalls in ihrem Interrupt bedient wird, gibt es folgendes
Problem:

Aus dem "seriellen Interrupt" heraus schaufele ich solange die zu
sendenden TWI-Pakete in meinen TWI-Sendepuffer, bis dieser gefüllt ist
(kann nicht alle zu sendenden Daten aufnehmen).
Bis der Buffer wieder Platz hat, müßte ich aktiv warten - was
allerdings nicht möglich ist, da der TWI-Interrupt nicht aufgerufen
werden kann (der AVR steckt ja noch im seriellen Interrupt).

Falls jemand eine hübsche Idee hat, wäre es nett, wenn er diese hier
einstellen könnte.

Grüße / Olaf

P.S: Polling aus der Main-Routine geht leider nicht, da auch außerhalb
des "seriellen Interrupt" zu sendende TWI-Daten anfallen und die
Main-Routine bereits LCD bedient etc.

von D. W. (dave) Benutzerseite


Lesenswert?

Was du nicht hören willst: deine Programmstruktur wirklich auf
unabhängiges Senden/Empfangen in der main umschreiben.

Oder einfach Assembler "sei" bzw. in c
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Unterbrechbare_Interuptroutinen
benutzen.

von Olaf (Gast)


Lesenswert?

Danke erstmal für die Antwort.

Das Umschreiben (unabh. Senden/Empfangen in der main) will ich durchaus
hören, ist aber nicht realisierbar ;-)
Problem dabei ist, daß dies die Latenzzeiten zu sehr erhöht (in der
main wird unter anderem schon das LCD und weiteres I/O bedient).

Die Möglichkeit mit dem "sei" könnte funktionieren...

Ich werde es mal probieren - alle entsprechenden Variablen auf
"volatile" und dann sollte der GCC schon was funktionierendes
ausspucken.

von Olaf (Gast)


Lesenswert?

Funktioniert wohl.

Im ganz normalen "seriellen Interrupt" (als nicht-unterbrechbar
deklariert und definiert) einfach ein "sei()" eingefügt, falls der
Buffer voll ist - und anschließendes aktives warten.

Nun hängt sich der uC nicht mehr beim aktiven Warten auf.

C-Schnipsel (TWI-Ringbuffer):

SIGNAL(SIG_UART_RECV) // jaja deprecated :-)
{
  ...
  if (twi_buffer_out_index == tiw_buffer_in_index)
  {
    sei();
    while (twi_buffer_out_index == twi_buffer_in_index);
  }
  ...
}

von Karl heinz B. (kbucheg)


Lesenswert?

Warte erst mal ab was passiert, wenn über die Serielle ein
neues Zeichen eintrudelt während du 'aktiv wartest'.

Dann beginnt der Tanz erst so richtig.

von Marko (Gast)


Lesenswert?

Ja, eine solche Programmstruktur ist definitiv Müll.
Dann mach doch Dein Display auf nen Timerinterrupt.
Die Kiste warten lassen bis zum Sanktnimmerleistag
bzw. bis das LCD ausgeschlafen hat klingt mir nicht
sinnvoll.

von Peter D. (peda)


Lesenswert?

"Problem dabei ist, daß dies die Latenzzeiten zu sehr erhöht (in der
main wird unter anderem schon das LCD und weiteres I/O bedient)."


Und wo ist das Problem dabei ?

Wenn Dir die 40µs Delay des LCD zuviel sind, dann gib halt nur ein
Zeichen pro Maindurchlauf aus.


Die Mainloop immer als abweisende Schleife aufbauen, d.h. immer wenn wo
gewartet wird, einfach ab zur nächsten Task und beim nächsten Durchlauf
weitermachen.
Das kostet nur ein Byte (= max 256 Schritte) pro Task, um sich zu
merken, wo man weitermachen muß.


Wenns ergonomisch sein soll, macht man auch nicht mehr als 2..5 Updates
des LCD pro Sekunde, damit der Mensch es auch ablesen kann und nicht nur
Geflimmer sieht.


Peter

von SuperUser (Gast)


Lesenswert?

Olaf, du hast zu viele Funktionen in deinem UART interrupt. Wenn du die
Bufferverwaltung in eine Zwischenschicht auslagerst, z.B. einen kleine
Funktion MyMethod(context) mit eigenen kleinen Statemachine der im UART
interrupt oder TWI interrupt kontext aufgerufen werden kann, wird es
einfacher. (oder nutzt direkt ein multi-tasking OS)

10:
UART empfängt Byte, schreibt es in den Empfangsspeicher und ruft
MyMethod(UART) auf.

MyMethod() kopiert Byte in TWI FIFO. Wenn TWI-Fifo full, stoppt
MyMethod() den UART (RTS/CST oder flow-control) und return -> UART
interrupt ist beendet.
Wenn TWI-Fifo full ungültig, da TWI gelesen, ruft TWI nun
MyMethod(TWI) auf. MyMethod() copiert jetzt die Daten und startet UART
neu und return -> ende TWI interrupt.

goto 10:

von Karl heinz B. (kbucheg)


Lesenswert?

Ich würde es so machen:

Ich würde die Verarbeitungen in die Hauptschleife in main() legen.
Wichtig: Jede Verarbeitung innerhalb der Hauptschleife muss
kurz sein!

So in etwa (in C-Pseudocode)

int main()
{
  ...
  while( 1 ) {

    if( ZeichenVomUART ) {
      Bereite Zeichen auf
      Fülle TWI-Buffer
      ZeichenVomUART = false;
    }

    if( Zeichen im TWI_Buffer && TWI_Schnittstelle_Bereit ) {
      Sende 1 Zeichen über TWI
      TWI_Schnittstelle_Bereit = false;
    }

    if( Zeichen im LCD_Ausgabepuffer && LCD_Bereit ) {
      Gib 1 Zeichen am LCD aus
    }
  }
}

Die ISR für den UART macht dann nur:
Das Zeichen in einen Buffer stellen und das Flag ZeichenVomUART
auf true setzen.

Der TWI-Rückmeldeinterrupt, der aufgerufen wird, wenn die TWI
die Übertragung beendet hat, macht:
TWI_Schnittstelle_Bereit wieder auf true setzen

LCD_Bereit könnte zb. direkt das Busy Flag des LCD abfragen.

Auf die Art sind alle ISR möglichst kurz gehalten. Nirgends
wird extrem viel Zeit mit warten verbrutzelt. Erst wenn die
vorhergehende Übertragung abgeschlossen ist, bzw. das LCD bereit
für das nächste Zeichen ist wird die nächste Übertragung
angestossen bzw. das nächste Zeichen zum Display übertragen.
Auf die Art arbeitet der µC in der main() Schleife die verschiedenen
Aufgaben fast gleichzeitig ab. Es kommt zwar zu
Latenzzeiten, die verteilen sich aber auf alle Aufgaben gleich-
mässig und sind, nachdem die einzelnen Teilaufgaben nicht
besonders aufwändig sind, so kurz wie es nur irgendwie geht.

von SuperUser (Gast)


Lesenswert?

Hallo Karl-Heinz,

einziger Nachteil ist, dass du den AVR nicht in den Sleep schalten
kannst, da main() immer pollen muss.

von Klaus (Gast)


Lesenswert?

Das Wort "warten" in Zusammenhang mit "Interrupt" ?
Da ist grundsätzlich was falsch.

von Olaf (Gast)


Lesenswert?

Danke für die rege Beteiligung - finde ich echt toll.

-----------------------------
@KarlHeinz Buchegger:

"Dann beginnt der Tanz erst so richtig."

Auf der seriellen trudeln die Bytes gemächlich ein (MidiBaudrate
31kBit). Werden die TWI-Daten LATENZFREI verschickt, paßt es ziemlich
genau zwischen zwei "serielle Bytes".

-----------------------------
@Marko:

"Ja, eine solche Programmstruktur ist definitiv Müll.
Dann mach doch Dein Display auf nen Timerinterrupt."

Das geht nicht, da das Display ein grafisches ist - da muß der ganze
Bildschirm geupdatet werden - in nem Timerinterrupt habe ich dann
dasselbe Problem in grün.

-----------------------------
@Peter Dannegger:

Siehe Antwort an Marko - das Display-Update braucht nicht 40us sondern
geschlagene 10ms im worst-case.

-----------------------------
@SuperUser:

Daraus werde ich nicht so recht schlau. Die serielle stoppen, wenn der
TWI-Buffer voll ist ? Dann verpasse ich ja eventuell Daten, die von der
seriellen kommen... (oder habe ich beim ATMega16 einen großen
Empfangspuffer für die serielle übersehen ?)

-----------------------------
@KarlHeinz Buchegger:

Danke für die ganze Arbeit die Du Dir gemacht hast !

Die Idee an sich finde ich gut, und habe ich auch meist beherzigt
(kurze ISRs).
Problematisch war hier nur, daß das Update des grafischen Displays so
lange benötigt (bis zu 10ms), daß ich ernsthafte Probleme mit der
Latenz bekomme.

Wird gerade das grafische Display komplett neu gefüllt, wäre die main
zu lange blockiert, um noch ein Display-Byte zu handeln.

Mal schauen - sollte es mit dem "sei()" noch Probleme geben, werde
ich wohl das Programm neu strukturieren.

Nochmals Dank an alle.

/ Olaf

von SuperUser (Gast)


Lesenswert?

Da du bei Midi natürlich den UART nicht stoppen kannst, da kein flow
control, lasse den Punkt mit UART stoppen/weiterlaufen lassen einfach
aus...

Ansonsten kann man die Übertragung auf dem UART durchaus anhalten, wenn
man die Daten nicht schnellgenug wegbekommt...

von Läubi (Gast)


Lesenswert?

Nur so als hinweis: das TWI darf (theoretisch) auch 10min lang
"warten" bis der Slave das byte verarbeitet hat!

von Peter D. (peda)


Lesenswert?

"Problematisch war hier nur, daß das Update des grafischen Displays so
lange benötigt (bis zu 10ms)"


Nochmal, dann lade es eben in Häppchen, z.B. 10 Häppchen a 1ms oder 100
a 100µs bzw. lade nur die Teile, die sich auch geändert haben.


"P.S: Polling aus der Main-Routine geht leider nicht, da auch
außerhalb des "seriellen Interrupt" zu sendende TWI-Daten anfallen"

Sowas muß einfach schief gehen !
Ein Blockdevice darf ausschließlich nur aus einer Quelle gefüttert
werden, sonst gibts Bytesalat.
Deshalb ist es erst recht richtig, wenn nur das Main den Puffer füllt
und zwar eine Quelle nach der anderen.


Peter

von Dirk (Gast)


Lesenswert?

Das beste wäre in diesem fall wohl ein RTOS. (Main, SIO und TWI Task)
Mit MSG handling.

Die Zweite Wahl wäre ein Zustandsautomat der aus dem Timer-Interupt das
TWI bedient(Check TWI free -> Get FiFO -> Start next). Plus einen Sende
Automat im TWI Interupt (Get next Byte -> send Byte). Dazu brauchst Du,
wie in einem RTOS, ein MSG (FIFO) System für die zu sendenen Daten.

Meistens baue ich mir die 2. Lösung, bei so einem Problem.

von Olaf (Gast)


Lesenswert?

Da das hier immer weiter geht möchte ich mich mit einem abschließenden
Wort zurückziehen, da ja die ursprüngliche Lösung ausgezeichnet
funktioniert.

@Peter Danegger:

Mich wundert folgender Kommentar:

"Ein Blockdevice darf ausschließlich nur aus einer Quelle gefüttert
werden, sonst gibts Bytesalat."

Da TWI-Daten sowohl per Midi(seriell) als auch per UserInterface
anfallen, gibt es zwangsweise mehrere Quellen (und mein
Anwendungsszenario dürfte nicht das einzige sein, bei dem es so ist).
Wahrscheinlich meinst Du also eine Art Kapselung bzw. Schichtenmodell
die man Einsetzen sollte (was ja generell durchaus sinnvoll ist)
Da ich jedoch paketorientiert arbeite, und die Pakete immer als
Einheiten gehandhabt werden, gibt es da keine Probleme.

@Dirk

RTOS fällt flach (möchte hier aber keine Grundsatzdiskussion starten).

Mit State-Machines arbeite ich schon an zahlreichen Stellen.
Problem beim Timer-Interrupt: Da kurze Latenzzeiten bei meinem Einsatz
(Audio) enorm wichtig sind, würde folgendes Problem eintreten:

Entweder ich setze die Timer-Frequenz hoch (damit die Latenz gering
bleibt), bekomme dann aber eine hohe Systemlast wegen der
Interrupt-Push-Pop-Orgien.

Setze ich die Frequenz runter, um die Systemlast zu verringern geht die
Latenz hoch.

Als Latenz würde ich die halbe TWI-Byte-Rate akzeptieren können, die
bei 400Khz also 50kByte/s eine Interrupt-Rate von 100.000/s fordern
würde - finde ich nicht so prickelnd.

Da es - so wie es ist - wunderbar funktioniert (zuverlässig, hoher
Durchsatz, maximale Leistungsausnutzung), werde ich einen Teufel tun,
etwas zu ändern ;-)

von Peter D. (peda)


Lesenswert?

"Da ich jedoch paketorientiert arbeite, und die Pakete immer als
Einheiten gehandhabt werden, gibt es da keine Probleme."


Tust Du eben nicht, wenn Interrupt und Main was in den Puffer stellen
können.

Das Main ist gerade dabei ein Paket in den Puffer zu stellen.
Mittenrein kommt nun der UART-Interrupt und schon hast Du den
Bytesalet. Also z.B. 2 Byte vom Main, ein Paket von der UART und dann
den Rest des Paketes vom Main.

Eine Notabhilfe wäre, daß wenn das Main ein Paket sendet, der
UART-Interrupt solange abgeschaltet wird.


Mag sein, daß der Fall selten eintritt oder der Empfänger zerstückelte
Pakete ignoriert und neu anfordert, das ist aber noch lange kein Grund
so derart unsauber zu programmieren.


Peter


P.S.:
Man muß immer daran denken, daß ein Interrupt immer und überall
dazwischen hauen kann und bevorzugt an solchen Stellen, wo er den
meisten Schaden anrichtet.

von Dirk (Gast)


Lesenswert?

@Olaf
Das mit dem RTOS war ja nur ein vorschlag und wie ich selber sagte
nehmen ich es meist nicht mal selber.

Aber höre auf Peter. Er hat vollkommen recht.

Und es gibt halt nur wenige wege um den Datensalat zu verhindern.
Und sie wurden Dir hier alle schon gezeigt.

von Olaf (Gast)


Lesenswert?

Woher kommt eigentlich die Behauptung, daß ich mit Datensalat zu kämpfen
hätte ?

Wie ich schon schrieb "...Pakete werden als Einheiten behandelt..." -
der wechselseitige (genauer: überschneidende) Zugriff auf eine Entität
(Paket) ist bei mir hinreichend ausgeschlossen - da kann der Interrupt
nicht dazwischenfunken.

Gerade der Kommentar von Peter Dannegger

"das ist aber noch lange kein Grund so derart unsauber zu
programmieren"

ist mir ein völliges Rätsel, da er keine einzige meiner Quelltextzeilen
gelesen hat (zumindest habe ich nichts derartiges gepostet).

Unsauber ist mein Quelltext mit Sicherheit nicht, wenn ich von zwei
Quellen auf eine Schnittstelle zugreife, jedoch sicherstelle, daß kein
überschneidender Zugriff stattfindet.

Grüße / Olaf

von Karl H. (kbuchegg)


Lesenswert?

> Woher kommt eigentlich die Behauptung, daß ich mit Datensalat zu
> kämpfen hätte ?

Ach, der kommt schon noch. Vielleicht nicht heute oder morgen
oder diesen Monat. Aber irgendwann kommt er. Alte Regel: Was
schief gehen kann, wird auch irgendwann schief gehen.

von Peter D. (peda)


Lesenswert?

@Olaf

"Woher kommt eigentlich die Behauptung, daß ich mit Datensalat zu
kämpfen hätte ?"

Ist einfach ne Schlußfolgerung aus dem, was Du bis dahin gesagt
hattest.


"...jedoch sicherstelle, daß kein überschneidender Zugriff
stattfindet."

Nun das ist das erste mal, daß Du das sagst.

In den Kopf schauen kann Dir keiner, sondern nur aus dem schlußfolgern,
was Du uns sagst. Und ein Interrupt haut numal ohne besondere Maßnahmen
dazwischen.


Wichtig wäre dann natürlich noch, wie Du das sicherstellst.


Peter

von Olaf (Gast)


Lesenswert?

@Peter Danegger:

Auch wenn es off-topic ist (da ich damit keinerlei Probleme hatte) -
überschneidende Zugriffe sind bei mir ausgeschlossen, da bei jedem
nichtsequentiell-kritischen Zugriff/(besser: Vorgang, da Pakete) die
Interrupts kurzzeitig mittels cli() gesperrt werden.

Ferner werden nur dann die anderen Interrupts (gemäß Lösung)
freigeschaltet, wenn der freischaltetende Interrupt sich nicht
innerhalb des Zugriffs auf den Buffer bzw. dessen Pakete befindet.

...und die Variablen, die es nötig haben, sind "volatile" :-)

----------------------------

Da mittlerweile diverse Postings zusammengekommen sind, möchte ich hier
die Gelegenheit ergreifen, eine kleine Zusammenfassung zu geben:

Das ursprüngliche Problem war schlicht und ergreifend die Notwendigkeit
innerhalb eines Interrupts "A" darauf zu warten, daß Interrupt "B"
eine Aufgabe beendet hat.

(Nebenbei: Interrupt "B" beinhaltet hier eine TWI-StateMachine -
braucht also mehrere Aufrufe, um die "Aufgabe" zu beenden)

Da in meinem Anwendungsszenario dabei die Latenzzeit minimal sein
sollte (die "main()" macht nebenbei schon jede Menge anderen Kram),
ergab sich diese Lösung (im avr-gcc) als die passende:

Wenn "A" feststellt, daß "B" noch nicht seine Aufgabe erledigt hat,
schaltet "A" per "sei()" die Interrupts frei (ermöglicht also, daß
Interrupt "B" aufgerufen werden kann), und wartet dann aktiv
(while-loop) bis "B" sein Aufgabe beendet hat.

Natürlich ist durch das aktive Warten die Ausführung des main-loops
blockiert, und natürlich ist aktives warten generell keine gute Idee,
aber wenn man weiß, daß man nicht lange warten wird (hier: mein
geschlossenes System mit max 20us Wartezeit bei einem TWI-Byte
@400KBit) und kurze Reaktionszeiten braucht, kann man es durchaus mal
machen.

Ach ja... die Credits für die Lösung gehen natürlich an David W. :-)

von Olaf (Gast)


Lesenswert?

Korrektur:

Die Wartezeit beträgt natürlich ca. n*20us (bei n Bytes pro Paket).
Bei einem Verbindungsaufbau kommen dann noch mal >20us hinzu).

...und um es rund zu machen: Theoretisch kann die Startsequenz durchaus
Minuten dauern - ein langsamer Verbindungsaufbau kann aktives Warten
dann schnell disqualifizieren (kommt bei mir aber nicht vor).

von Michael (Gast)


Lesenswert?

@Olaf:
Bei Deinem Betreff gehen bei jedem 'vernünftigen' Programmierer die
Ampeln auf rot. Keiner käme auf die Idee, im Interrupt zu warten.
Besser ist es immer, die Ereignisse/Daten in Puffern zu speichern und
mit Flags zu signalisieren. Diese werden dann abgearbeitet, wenn sie
vollständig vorliegen. Zustandsautomat (state machine) ist Dir ja ein
Begriff.

von inoffizieller WM-Rahul (Gast)


Lesenswert?

Mich würde die Aufgabenstellung ansich mal interessieren.
Schreib doch bitte mal eine Anforderungsliste (Bauteile, Zeiten etc.).
Dann kann man auch abschätzen, wie man sowas realisiert.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.