Interrupt-Management - Große AVR-Projekte

Dies ist für die Erfahreneren da draußen.

Ich bin derzeit an einem großen AVR-Projekt beteiligt (mit ATMega328) und es wird in Bezug auf Interrupts verwirrend.

Das Projekt beinhaltet Interrupts aus den folgenden Quellen

  • USART
  • TWI
  • SPI
  • Externer Interrupt (INT0 und INT1)

Bei so vielen Interrupt-Quellen und der Tatsache, dass globale Interrupts deaktiviert werden, wenn ein ISR ausgeführt wird, habe ich Angst, Interrupts zu verpassen, die ausgelöst werden könnten, wenn mein Code einen anderen Interrupt bedient.

Gibt es also eine strukturiertere Methode zur Verwaltung von Interrupts in einem großen Projekt?

Antworten (2)

Die erste Regel bei der Verwendung von Interrupts: Halte sie sehr kurz.

Wenn ein Interrupt auftritt, wird unabhängig davon, ob er aktiviert ist oder nicht und ob er gerade einen Interrupt bedient oder nicht, ein Interrupt Fired-Flag gesetzt. Jedem Interrupt ist eines dieser Flags zugeordnet (z. B. SPSR.SPIF). Das wird immer gesetzt, sobald der Interrupt feuert.

Das Interrupt-System durchläuft dann zyklisch die Interrupts und sucht nach gesetzten Interrupt-Flags, und wenn der Interrupt freigegeben ist, springt es dann zur ISR.

Dies geschieht unabhängig davon, wie lange der Interrupt zurückliegt.

Die Frage ist also nicht "werde ich einen Interrupt verpassen, weil ich in einer ISR bin", sondern "werde ich aufeinanderfolgende Interrupts verpassen, weil ich in einer ISR bin".

Zuerst müssen Sie also die maximale Rate berechnen, mit der ein Interrupt von einer Quelle auftreten kann.

Dann überprüfen Sie Ihre ISR-Routinen und stellen sicher, dass sie diese minimale Interrupt-Periode nie überschreiten. Erwägen Sie, Code aus einer ISR zu verschieben und stattdessen Flags zu setzen, damit Ihre Hauptschleife stattdessen Operationen ausführt.

In einer komplexeren Umgebung (5 Interrupts sind NICHT viel!) können Sie, wenn die Ressourcen dies zulassen, die Erstellung einer Interrupt-Ereigniswarteschlange in Betracht ziehen, in der ein Ereignis zu einer Liste eingehender Ereignisse hinzugefügt wird, wenn der Interrupt auftritt, und Ihre Hauptschleife ist dann frei um diese Ereignisse der Reihe nach zu verarbeiten. Möglicherweise müssen Sie Warteschlangen mit unterschiedlichen Prioritäten für unterschiedliche Interrupts verwenden.

Das ist auf einer kleinen MCU wie der '328P nicht wirklich machbar, da ihr die Ressourcen fehlen, um diese Art von Warteschlangen effektiv zu verwalten.

Angenommen, ich bediene einen Interrupt von Quelle A, während dessen das Interrupt-Flag aufgrund eines Interrupts von Quelle B gesetzt wird. In diesem Fall wird die ISR für B ausgeführt, sobald die ISR für A endet. Die einzige wirkliche Gefahr besteht also darin, aufeinander folgende Interrupts von A selbst zu verpassen.
@Ankit, ja. Wenn ein zweiter Interrupt von B auftritt, bevor Sie das Flag des ersten Ereignisses gelöscht haben, haben Sie ihn verpasst. Auch Majenko the queue ist ein wunderbarer Vorschlag! Ich hatte einmal ein Projekt für den Unterricht, in dem wir eine so genannte Prioritätswarteschlange implementierten , in der Ereignisse in O(lgn)-Zeit hinzugefügt und entfernt wurden, also war es in Bezug auf die Verarbeitungsleistung wirklich keine große Anforderung.
@sherrellbc Ich mache mir mehr Sorgen um die RAM-Nutzung für Ereigniswarteschlangen. Dynamischer Speicher ist auf einem 2K-Chip nicht gut, und das Überlaufen einer Warteschlange mit statischer Größe ist keine glückliche Sache.

Ich habe Angst, Interrupts zu verpassen, die ausgelöst werden könnten, wenn mein Code einen anderen Interrupt bedient.

Majenko erklärte: "Das Interrupt-System durchläuft dann die Interrupts und sucht nach gesetzten Interrupt-Flags, und wenn der Interrupt aktiviert ist, springt es zur ISR." Das ist nicht ganz richtig, denn:

Die Interrupts haben entsprechend ihrer Interrupt-Vektorposition Priorität. Je niedriger die Interrupt-Vektoradresse, desto höher die Priorität.

Dies bedeutet, dass eine Unterbrechungsquelle "aushungern" kann. Nachdem eine ISR abgeschlossen ist, bevorzugt der Controller Interrupts mit hoher Priorität. Abhängig von der Interrupt-Frequenz kann es vorkommen, dass immer ein High-Level-Interrupt-Flag gesetzt ist, das im Wesentlichen ein niedrigeres Prio-Flag dauerhaft blockiert.

Es ist ein guter Rat, ISRs kurz zu halten und ihre Ausführungszeit zu kennen.

5 Interrupts sind wirklich nicht viel, erfordern aber dennoch Vorsicht auf einem einfachen System mit fester Priorität.

AVR XMEGA zum Beispiel bringt es auf eine andere Ebene. Ich habe ein XMEGA-Projekt, das 22 verschiedene Interrupt-Quellen verarbeiten muss (4xUART tx/rx = 8 + 4 Timer + 8 ADC komplett + 2 andere). Der XMEGA verfügt jedoch über einen "programmierbaren Multilevel-Interrupt-Controller". Dadurch können Sie jedem Interrupt manuell eine von drei Interrupt-Ebenen zuweisen (innerhalb dieser zählt weiterhin die Priorität der Interrupt-Vektornummer). Außerdem können Sie ein Round-Robin-Prioritätsschema aktivieren, das das zuvor erwähnte "Hunger"-Problem angeht.

Um das mögliche Hungerproblem für Low-Level-Interrupts mit statischer Priorität zu vermeiden, bei denen einige Interrupts möglicherweise nie bedient werden, bietet der PMIC eine Round-Robin-Planung

Also zu deiner ursprünglichen Frage:

Gibt es also eine strukturiertere Methode zur Verwaltung von Interrupts in einem großen Projekt?

Es hängt von den Fähigkeiten Ihres Systems ab. Stellen Sie bei einem Controller wie dem ATMega328 mit einfacher statischer prioritätsgesteuerter Interrupt-Verwaltung sicher, dass kein Interrupt verhungert, und halten Sie an "short" fest und "kennen Sie Ihre Ausführungszeit". Bei einem Controller wie dem XMEGA ist mehr Planung erforderlich, weil Sie einfach mehr Kontrolle haben. Sie müssen die Prioritäten sorgfältig verteilen und die Verwendung von Funktionen wie "Round Robin" berücksichtigen.

Wie würde ich vorgehen, um die Ausführungszeit eines bestimmten ISR zu berechnen?
@Ankit: Drei Optionen fallen mir ein: 1. Sehen Sie sich den Assembler-Code an und zählen Sie die Anweisungen. 2. Schätzen Sie die Anzahl der Anweisungen, indem Sie sich den C-Code ansehen ISR und verwenden Sie ein Scope, um die Ausführungszeit zu untersuchen. / Ich mag Option 3, weil sie in den meisten Szenarien einfach und präzise genug ist (sie lässt die Vor- und Nach-ISR-Verarbeitung weg). Wenn jede einzelne Anweisung zählt, wäre Option 1 wahrscheinlich die beste.
Danke! Option 3 war verdammt schlau! Ich werde das mal angehen und berichten