Da bei der Interrupt-Programmierung einige Besonderheiten zu beachten sind, soll dies in einem eigenen Artikel zusammengefasst werden.
Weil die Interrupt-Programmierung sehr stark von der zugrundeliegenden Hardware abhängt, ist allerdings in keiner C-Spezifikation festgehalten, wie etwa die Definition einer Interrupt Service Routine auszusehen hat oder wie Interrupts aktiviert oder deaktiviert werden.
Dies führt dazu, daß zum Beispiel die Definition einer ISR nicht nur von der Hardware abhängig ist, sondern zusätzlich noch vom verwendeten C/C++-Compiler.
Aufgabenstellungen
Bei der Interruptprogrammierung sieht man sich verschiedenen Aufgaben gegenüber
- Interrupts aktivieren/deaktivieren
-
- Global
- Damit überhaupt Interrupts von der Hardware ausgelöst werden, müssen Interrupts global aktiviert werden. Dies sieht ja nach Hardware und Compiler sehr verschieden aus, und teilweise ist diese Operation nur Benutzern mit Sonderrechten (Supervisor) erlaubt. Dies trifft zu für die so genannten "maskierbaren Interrupts". Maskierbar bedeutet dabei, daß man im Programm einstellen kann, ob man eine gewisse IRQ auslösen lassen will oder nicht. Das Gegenstück dazu sind die nicht-maskierbaren Interrupts, gegen deren Auftreten man sich nicht wehren kann. Ein Beispiel für einen nicht-maskierbaren Interrupt ist der RESET, wie er zum Beispiel nach Einschalten der Spannungsversorgung ausgelöst wird.
- Spezielle IRQs
- Damit eine spezielle IRQ ausgelöst wird, muss diese zudem erlaubt worden sein. Dazu gibt es in der Regel spezielle Register, in denen dazu ein Bit gesetzt/gelöscht werden muss. Damit lassen ich die IRQs einzeln an- bzw. ab-stellen.
- Interrupt-Service-Routine (ISR) implementieren/deklarieren
- Nachdem ein Ereignis eine Interrupt-Anforderung (IRQ) ausgelöst hat, wird ein bestimmtes Codestück (ISR) ausgeführt – natürlich unter der Voraussetzung, daß dieser IRQ wie oben beschrieben zugelassen wurde. Dazu wird nach Auftreten des Ereignisses der normale Programmfluss unterbrochen und der Code der ISR ausgeführt. Nach Beenden der ISR wird der Programmfluss entweder an der unterbrochenen Stelle fortgesetzt oder es werden weitere IRQs ausgelöst, die sich die Hardware gemerkt hat und deren ISR noch nicht ausgeführt wurden. Wie dieser Ablauf genau von statten geht ist stark von der Hardware anhängig, ebenso wie die Art und Weise wie mitgeteilt wird, daß es sich bei einem Codestück – üblicherweise eine C-Funktion – um eine ISR handelt. Dementsprechend verschieden sieht die Umschreibung dafür in unterschiedlichen C-Compilern aus.
- Interrupt-Prioritäten festlegen
- Manche Architekturen erlauben es, einer IRQ eine bestimmte Priorität zuzuordnen. Dadurch wird es möglich eine niederpriore IRQ von einer höherprioren IRQ unterbrechen zu lassen, während eine IRQ nicht von einer nieder- oder gleichprioren IRQ unterbrochen werden kann. Die Zuordnung der Prioritäten geschieht über spezielle, dafür vorgesehene SFRs. Zum Teil (z.B. AVR) bezieht sich die Priorität auch nur darauf welche ISR zuerst ausgeführt wird, wenn mehrere Interrupts anfordert werden. Ein Unterbrechen anderer Interrupts ist dabei nicht vorgesehen und müsste von Hand implementiert werden.
- Daten-Integrität sicherstellen
- Durch Interrupts wird der normale Programmfluss verändert. Dadurch ergeben sich ein paar Schwierigkeiten. Der Zugriff auf einige Variablen oder Hardwareteile kann durch einen Interrupt unterbrochen werden, der gerade an diesen etwas verändert. Im ungünstigsten Fall werden dann alte und neue Daten, z.B. oberes und unteres Byte einer Integervariabel vermischt. Eine Tücke bei derartigen Fehlern ist, dass sie selten reproduzierbar auftreten und damit schwer zu finden sind. Um diese Art der Fehler zu vermeiden sollte man verhindern das an den falschen Stellen eine Interrupt auftreten kann. In der Regel geschieht das durch ein kurzzeitiges globales sperren von Interrupts. Eine Alternative wären Semaphoren, die aber bei µCs nur sehr selten genutzt werden. Siehe: Fallstricke bei der C-Programmierung#Nicht-atomarer Code.
- Eine zweite Schwierigkeit ergibt sich, wenn der Compiler den Code optimiert. Bei normalem Programmfluss kann der Compiler ausnutzen das einige Ausdrücke konstant bleiben, oder Variablen nicht ins SRAM schreiben, sondern nur in den Registern belassen. Durch die Interrupts gehen diese Voraussetzungen verloren und viele Optimierungen können nicht mehr durchgeführt werden, oder liefern nicht mehr lauffähigen Code. Um den Compiler darauf hinzuweisen gibt es das Schlüsselwort volatile. Damit werden Variablen gekennzeichnet die sich anders als durch den normalen Programmfluss verändern können. Das sind typischerweise Variablen die im Hauptprogramm und in einer ISR benutzt werden. Wenn ein Programm nur ohne Optimierung lauffähig ist, ist oft ein fehlendes volatile die Ursache.