Was kann die Ursache für eine außergewöhnlich große Latenz für den UART-Empfangsinterrupt sein?

Ich empfange Daten auf dem UART mit einem 8-Bit-Atmega, normalerweise etwa 5 Bytes verbunden, dann eine lange Pause. Die Gesamtzeit für ein Byte (mit Start- und Stoppbits, ich verwende keine Parität) beträgt 160 us. Der Empfangs-Interrupt wird jedoch 60 bis 100 us nach dem Stoppbit ausgelöst, und fast die Hälfte der Zeit löst er überhaupt nicht aus! (überprüft mit Scope)

Es gab einige ziemlich lange Interrupts, also habe ich ihnen die Schuld gegeben, aber nachdem alle Interrupts außer dem UART deaktiviert wurden, bleibt die Situation dieselbe. Die UART-Interrupts enden die ganze Zeit in unter 10 us (normalerweise 7 us). Die Signalstärke ist OK, es sind 5V, ebenso die Versorgungsspannung.

Nachdem ich festgestellt hatte, dass viele Bytes verloren gegangen waren, dachte ich zunächst, dass die Signalfrequenz die Ursache war, aber ich habe es noch einmal überprüft: Die Baudrate ist perfekt, die Signalqualität sieht gut aus, der Baudratenfehler ist nahe Null. Wenn das das Problem wäre, würde ich einige verlorene Interrupts bekommen (weil einige Bits verloren gingen), aber der Rest sollte zur richtigen Zeit passieren, oder? Bei mir kommt der Interrupt, wenn überhaupt, sehr spät, und selbst wenn er kommt, enthält er manchmal Müll. Das Signal am Pin ist OK, ich kann es am Scope korrekt ablesen und auswerten.

Ich habe nach der typischen Latenz des UART-Interrupts gesucht, aber nichts gefunden. Ich vermute stark, dass eine wilde Schwankung zwischen 60 und 100 us nicht normal sein sollte.

Kannst du deinen Code posten?
@OliGlaser: Ich bezweifle, dass das Posten von 3000 Codezeilen hilfreich sein könnte. Wenn Sie sich für die Einstellungen interessieren, der Takt beträgt 10 MHz, UCSR0A=0x01; UCSR0B=0xDC; UCSR0C=0x06; UBRR0H=0x00; UBRR0L=0x09;und ich lese UDR0den Interrupt ein.
Ja, bitte nicht den Code posten! :-)
Ich habe nicht an den gesamten Code gedacht, sondern nur an den Interrupt und alle anderen relevanten Bits. Das Problem liegt wahrscheinlich am Code und kann in diesem Fall nicht gefunden werden, ohne ihn anzusehen. Das Ganze zu vereinfachen (oder ein Testprogramm zu erstellen) würde helfen, die Ursache zu finden.
Ist das eine Art Bewertungsgremium? Haben Sie einen Logikpegelübersetzer zwischen Ihrem Atmega und Ihrem PC? Auf welche Codezeile setzen Sie Ihren Haltepunkt, optimiert der Compiler ihn geschickt? Könnte auch ein Problem mit der Baudrate sein. Versuchen Sie den umgekehrten Weg und senden Sie Zeichen vom atmega an den PC und sehen Sie, ob sie korrekt empfangen werden.
Können Sie stattdessen Ihren Code einfügen und uns einen Link bereitstellen? ( pastebin.com )
Nicht vertraut mit dem Teil, aber soll der uart Ihnen für jedes Zeichen einen Interrupt geben, oder hat er ein Fifo? Der springende Punkt bei einem Fifo ist es, die Interrupt-Frequenz zu reduzieren, aber um dies zu tun, muss der Uart die Ausführung des Interrupts mit einer Strategie aufschieben. Eine Möglichkeit besteht darin, zu warten, bis eine „Hochwassermarke“ erreicht ist. Eine andere wäre zu warten, bis eine geeignete Lücke zwischen empfangenen Bytes erkannt wird. Riecht irgendwie wie die letztere Situation. Merkwürdig, wie "60 zu 100" einen Mittelwert von sehr nahe an einem halben Byte hat, nicht wahr? Sind die Abstände zwischen Ihren 5er-Gruppen zufällig in etwa so oder kleiner?
Sie müssen Ihren Code auf eine Hauptschleife, die nichts tut, und die ISR reduzieren. Wenn das Problem weiterhin auftritt, können Sie Hilfe erhalten. Andernfalls (und das ist meine Vermutung) haben Sie etwas anderes am Laufen, das Sie noch nicht berücksichtigen.

Antworten (4)

Ich gehe davon aus, dass Sie denselben Debugging-Prozess verwenden, den ich in diesem Fall durchführen würde – eine der ersten Anweisungen in der Interrupt-Routine schaltet eine LED ein und eine der letzten Anweisungen in der Interrupt-Routine schaltet diese LED aus.

Dann haben Sie ein Dual-Trace-Oszilloskop verwendet, bei dem eine Sonde an den entsprechenden Pin geklemmt wurde, um die Bytes zu beobachten, die in den UART gehen, und die andere Sonde an den Pin, der die LED ansteuert.

Ich gehe davon aus, dass Ihre UART-Handler-Interrupt-Routine mit der Return-from-Interrupt-Anweisung endet (anstatt die Return-from-Subroutine-Anweisung zu verwenden, die von normalen Anweisungen verwendet wird).

Es gibt 4 Dinge, die eine lange Latenz zwischen dem Ende des letzten Bytes einer Nachricht und dem Start des UART-Handlers verursachen können:

  • Ein vorheriges Byte in der Nachricht, das den UART-Handler auslöst, und irgendwie dauert es lange, bis Interrupts wieder aktiviert werden. Einige Leute strukturieren ihre Interrupt-Routinen so, dass, nachdem der UART-Handler das Speichern eines Bytes im entsprechenden Puffer beendet hat, er eine Reihe anderer Dinge überprüft, bevor er die Return-from-Interrupt-Anweisung ausführt – es erhöht Jitter und Latenz, aber manchmal tun es diese Leute es trotzdem, weil es den Durchsatz verbessert.

  • Irgendein anderer Interrupt, dessen Ausführung lange dauert, bevor er die Interrupts wieder aktiviert, indem er den Return-from-Interrupt-Befehl ausführt. (Wenn Sie jeden Interrupt dazu bringen können, eine andere LED ein- und auszuschalten, ist es ziemlich einfach, auf dem Oszilloskop zu sehen, ob dies das Problem ist, oder dies auszuschließen).

  • Einige Nicht-Interrupt-Codes schalten Interrupts "vorübergehend" aus. (Dies erhöht Jitter und Latenz, aber die Leute tun es trotzdem, weil es oft der einfachste Weg ist, eine Datenbeschädigung zu verhindern, wenn sowohl einige Interrupt- als auch einige Main-Loop-Hintergrundtasks mit denselben Daten arbeiten). (Wenn Sie jedes Bit des Codes, der dies tut, dazu bringen können, eine andere LED ein- und auszuschalten, ist es ziemlich einfach, auf dem Oszilloskop zu sehen, ob dies das Problem ist, oder dies auszuschließen).

  • Anweisungen, deren Ausführung lange dauert.

Der traditionelle Weg, um genau herauszufinden, was das Problem verursacht, besteht darin, die aktuelle Version Ihres Codes zu speichern (Sie verwenden TortoiseHg oder ein anderes Versionskontrollsystem, richtig?) und dann absichtlich eine temporäre Kopie Ihres Codes zu hacken und zu slashen Code, Stubbing und vollständiges Entfernen von Code ein paar Subroutinen auf einmal, erneutes Testen nach jeder Löschrunde, bis Sie ein winziges -- aber technisch "vollständiges" und lauffähiges -- Programm haben, das das gleiche Problem aufweist.

Viel zu oft zeigen uns Leute Teile eines kompletten Programms – die Teile, die sie für relevant halten – und wir können ihnen nicht helfen, weil einer der ausgelassenen Teile das Problem verursacht.

Der Prozess, ein Programm auf einen kleinen Testfall zu reduzieren, ist eine sehr nützliche Fähigkeit, denn oft entdeckt man beim Durchlaufen dieses Prozesses schnell, was das eigentliche Problem ist.

Sobald Sie ein so kleines -- aber lauffähiges -- Programm haben, posten Sie es bitte hier. Wenn Sie während dieses Vorgangs herausfinden, was das Problem ist, teilen Sie uns dies bitte ebenfalls mit, damit der Rest von uns dieses Problem vermeiden kann.

Im Allgemeinen sind UARTs mit Nicht-Echtzeitcode unzuverlässig. Das heißt, wenn Sie wiedereintretende Subroutinen und unbestimmte Codeausführungslängen haben, können Sie garantieren, dass die Firmware rechtzeitig auf einen nicht maskierbaren Interrupt reagiert? Aber Sie haben alle deaktiviert, also erfüllt die IRQ-Dauer die minimalen Zeitanforderungen für den ungünstigsten Fall?

Wenn dies zu einem schwierigen Problem wird, müssen Sie möglicherweise Pufferdaten mit 16 Ebenen tiefen UARTs und Pufferüberlauf- und -unterlauferkennung verwenden und eine Polling-Strategie für Daten verwenden, um zu erkennen, ob Daten im Puffer empfangen werden. Mein 1. UART-Design im Jahr 1976 hatte dieses Problem zuerst. Dann ging ich zum DMA-Design mit einer abgefragten Antwort, die kleiner als die Pufferlänge war, wobei der FIFO fast voll war, was einen Interrupt zuließ.

Sie sagen, die Baudrate ist korrekt, aber ich rechne mit 62500 bps (160 us / 10 Bit = 16 us pro Bit). Dies scheint für eine serielle Verbindung etwas seltsam zu sein, und eine falsche Baudrate würde ähnliche Probleme wie das, was Sie sehen, verursachen. Das heißt, der UART unterbricht möglicherweise das, was er für das Stoppbit hält, das aber tatsächlich Teil des nächsten Oktetts ist. Natürlich werden die Daten auch beschädigt angezeigt :)

Wenn Sie Daten empfangen, können Sie auch die Fehlerflags vom UART lesen? Das wird Ihnen sagen, warum der UART die Daten nicht mag.

Ich bin mit dem ATMega nicht vertraut, aber einige Controller verwenden flankengetriggerte statt pegelsensitive Interrupts; auf solchen Controllern muss eine Interrupt-Service-Routine prüfen, bevor sie beendet wird, ob alle ihre zugeordneten Ursachen "erfüllt" sind; wenn dies nicht der Fall ist, muss die Routine zurückschleifen und sie handhaben, anstatt sie zu verlassen. Wenn eine Interrupt-Routine endet, ohne gleichzeitig alle ihre Ursachen erfüllt zu haben, kann der Interrupt schließlich gesperrt werden, es sei denn, oder bis ein anderer Code bewirkt, dass alle Ursachen des Interrupts erfüllt werden.

Wenn beispielsweise ein Controller dieselbe Interrupt-Serviceroutine verwendet, um die eingehenden und ausgehenden Daten eines UART zu verarbeiten, und die Serviceroutine damit beginnt, eingehende Daten zu verarbeiten und dann ausgehende Daten zu verarbeiten. Wenn ein Byte eingehender Daten ankommt, kurz bevor die Interrupt-Service-Routine ein Byte ausgehender Daten an den UART weiterleitet, ist es möglich, dass der Interrupt-Controller einen konstanten Pegel auf dem Interrupt-Signal vom UART sieht. Bevor das ausgehende Byte geladen wird, möchte es unterbrechen, weil es ausgehende Daten benötigt. Zum Zeitpunkt des Ladens würde der UART unterbrechen wollen, weil er eingehende Daten hatte. Aus Sicht des Interrupt-Controllers scheint es jedoch, dass die Interrupt-Service-Routine des UART beim Auflösen der Interrupt-Bedingung einfach unwirksam war.