Aus RN-Wissen.de
Wechseln zu: Navigation, Suche


Programm-Code, der beim Auftreten einer freigeschalteten Interrupt-Anforderung (IRQ) ausgeführt wird. Dazu wird der normale Programmfluss unterbrochen, die Interrupt Service Routine (ISR) ausgeführt, und danach das Programm an der unterbrochenen Stelle fortgeführt.

Je nach Hardware bzw. deren Konfiguration wird die ISR direkt betreten oder indirekt über eine Interrupt-Vektor-Tabelle, welche nur die Startadressen der unterschiedlichen ISRs enthält bzw. Sprunganweisungen zum Start der jeweiligen ISR.

Empfehlungen für Code in der ISR

In der ISR kann im Prinzip ein beliebiger Code stehen. Das kann im Extremfall so weit gehen, das fast der ganze Code in ISRs steht, und das Hauptprogramm nur die Initialisierung am Anfang macht und später nur noch den µC zwischen den Interrupts in einen Stromsparmode versetzt.

Die beiden wesentlichen Forderungen an die ISR sind, dass genügend Rechenzeit für das Hauptprogramm übrig bleibt, und das weiter schnell genug auf ggf. weitere Interrupts reagiert werden kann. Was das genau heißt hängt stark vom Einzelfall ab. Auch wenn es nicht immer nötig ist, gibt es die Faustregel dass eine ISR relativ schnell beendet werden sollte. Entsprechend sollte man langsame Teile wie längeres explizites warten oder das Warten auf externe Ereignisse vermeiden. Dazu gehört meist auch die Ausgabe auf ein LCD Display. Auch Rechnungen mit Fließkommazahlen brauchen bei kleinen µCs oft relativ lange, sind aber nicht absolut tabu. Einfache Änderungen an IO-Registern sind dagegen relativ schnell, auch wenn man dafür relativ viele Zeilen im Source-code schreibt. Gerade für Anfänger ist es nicht einfach abzuschätzen wie viel Rechenzeit die ISR tatsächlich benötigt - eine Möglichkeit die Zeit zu bestimmen ist die Benutzung des Simulators.

Sofern es nicht schon von der Hardware oder dem Compiler erledigt wird, ist eine der ersten Aufgaben in der ISR das sichern von Registern, damit das Hauptprogramm nicht gestört wird. Falls eine ISR durch mehrere Interruptquellen aufgerufen werden kann, muss in der Regel erst einmal die genaue Interruptquelle bestimmt werden, um die dazu passende Funktion auszuführen. Das Flag das den Interrupt-request anzeigt muss dann in der Regel gelöscht werden, damit die ISR nicht noch einmal aufgerufen wird. Bei je nach Interruptquelle getrennten ISRs (z.B. beim AVR)kann dieser Schritt ggf. entfallen, weil die Hardware das Flag bereits mit dem Start der ISR löschen kann.

Um Platz auf dem Stack zu sparen, sollte man in C Funktionsaufrufe in der ISR vermeiden, also besser den Code direkt einfügen. Einige Unterprogramme sind ggf. nicht "reentrant", dürfen also nicht 2 mal gleichzeitig aufgerufen werden - eventuell aus dem Hauptprogramm und dann noch mal aus der ISR. Dies passiert z.B. wenn globale Variablen für Zwischenwerte benutzt werden.

Der oft genannte Hinweis in der ISR nur ein Flag zu setzen, und dann die ganze Rechnung im Hauptprogramm zu machen hilft oft auch nicht weiter. Zum einen setzt die Hardware schon von sich aus meistens ein Flag in einem IO-Register, wenn ein Interrupt ausgelöst werden könnte. Nur um ein Flag zu setzen braucht man also den Interrupt meist gar nicht, ein Flag hat die Hardware schon gesetzt. Es geht dann also besser ganz ohne Interrupt. Wenn eine andere ISR zeitkritisch ist, kann man ggf. in der ISR Interrupts freigeben und so geschachtelte Interrupts erlauben. Ein wirkliches Problem entsteht dabei erst, wenn der selbe Interrupt noch einmal kommt. Die Abfrage eines Flags im Hauptprogramm löst dieses Problem aber auch nicht, sondern reduziert höchstens die Folgen (logischer Fehler statt eines möglichen Stack-Überlaufs).

Aufgaben wie die Ausgabe mehrerer Bytes per UART oder an ein ähnliches langsames Gerät wie ein LCD löst man besser, indem man die Ausgabe puffert. Die eigentliche Ausgabe erledigt eine andere ISR, die dann jeweils 1 Byte ohne Wartezeiten ausgibt. Viele kurze Interrupts sind meist das kleinere Problem als eine langsame ISR. Die Koordination mit ggf. vorhanden Ausgaben des Hauptprogramms ist dabei noch eine andere Baustelle.

Wenn das Hauptprogramm und die ISR auf die gleiche Variable oder ein IO Register zugreifen, muss das Hauptprogramm aufpassen, das nicht zu einem ungeeigneten Zeitpunkt die ISR dazwischen kommt. Die Zugriffe sollten "atomar" gestaltet sein, z.B. durch ein CLI() und SEI(), wenn der Zugriff nicht von sich aus schon atomar ist, z.B. durch einen einzigen ASM Befehl. Mit CLI()-SEI() vor Interrupts geschützte Abschnitte sollten nicht länger als unbedingt nötig sein. Je nach µC ist der schreibende Zugriff auf einzelne Bits von sich aus atomar möglich: erst wird das ganze Byte gelesen, dann das gewünschte Bit manipuliert und dann das ganze Byte zurück geschrieben. Ob der Compiler dabei wirklich den möglichen atomaren Zugriff nutzt, sollte man ggf. kontrollieren.

Damit es mit der Optimierung keine Probleme gibt, müssen in C die gemeinsam genutzten Variablen, die also in einer ISR und dem normalen Programm genutzt werden, als volatile gekennzeichnet werden. Bei einer volatilen Variable lohnt es sich ggf. in der ISR eine lokale nicht volatile Kopie anzulegen.

Siehe auch