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.
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.
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.
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); } ... }
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.
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.
"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
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:
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.
Hallo Karl-Heinz, einziger Nachteil ist, dass du den AVR nicht in den Sleep schalten kannst, da main() immer pollen muss.
Das Wort "warten" in Zusammenhang mit "Interrupt" ? Da ist grundsätzlich was falsch.
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
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...
Nur so als hinweis: das TWI darf (theoretisch) auch 10min lang "warten" bis der Slave das byte verarbeitet hat!
"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
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.
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 ;-)
"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.
@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.
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
> 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.
@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
@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. :-)
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).
@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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.