Path: Home => AVR-Überblick => Interrupt-Programmierung => Vektortabelle
Interrupt

Die Interrupt-Vektoren-Tabelle


Hier kommt alles über die Reset- und Interrupt-Vektoren-Tabelle, und was man bei ihr richtig und falsch machen kann.

Wat is ene Vektortabelle?

Vergessen Sie für eine Weile mal die Worte Vektor und Tabelle, sie haben hier erst mal nix zu sagen und dienen nur der Verwirrung des unkundigen Publikums. Wir kommen auf die zwei Worte aber noch ausführlich zurück.

Stellen Sie sich einfach vor, dass jedes Gerät im AVR für jede Aufgabe, die einer Unterbrechung des Prozessors würdig ist, eine feste Adresse hat, zu der sie den Prozessor zwingt hinzuspringen, wenn das entsprechende Ereignis eintritt (also z.B. der Pegel an einem INT0-Eingang wechselt oder wenn der Timer gerade eben übergelaufen ist). Der Sprung an genau diese eine Adresse ist in jedem AVR für jedes Gerät und jedes Ereignis festgenagelt. Welche Adresse mit welchem Gerät und Ereignis verknüpft ist, steht im Handbuch für den jeweiligen AVR-Typ. Oder ist in Tabellen hier aufgelistet.

Alle Interrupt-Sprungziele sind am Anfang des Programmspeichers, beginned bei der Adresse 0000 (mit dem Reset), einfach aufgereiht. Es sieht fast aus wie eine Tabelle, es ist aber gar keine wirkliche. Eine Tabelle wäre, wenn an dieser Adresse tatsächlich das eigentliche Sprungziel selbst stünde, z.B. eine Adresse, zu der jetzt weiter verzweigt werden soll. Der Prozessor täte diese dort abholen und den aus der Tabelle ausgelesenen Wert in seinen Programmzähler laden. Wir hätten dann eine echte Liste mit Adressen vor uns, eine echte Tabelle.

Beim AVR gibt es aber gar keine solche Tabelle mit Adresswerten. Stattdessen stehen in unserer sogenannten Tabelle Sprungbefehle wie RJMP herum. Der AVR ist also noch viel einfacher gestrickt: wenn ein INT0-Interrupt auftritt, lädt er einfach die Adresse 0001 in seinen Programmspeicher und führt den dort stehenden Befehl aus. Und das MUSS dann eben ein Sprungbefehl an die echte Adresse sein, an der auf den Interrupt reagiert wird, die Interrupt-Service-Routine (ISR).

Also nix Tabelle, sondern Auflistung der Sprungbefehle zu den Serviceroutinen. Jetzt haben wir noch die Sache mit dem Vektor zu klären. Auch dieses Wort ist Käse und hat mit den AVRs rein gar nix zu tun. Als man noch Riesen-Mikrocomputer baute mit etlichen 40-poligen ICs, die als Timer, UARTs und I/O-Ports nach außen hin die Schnittstellenarbeit verrichteten, fand man es eine gute Idee, wenn diese selbst darüber bestimmen könnten, wo ihre Service-Routine im Programmspeicher zu finden ist. Sie gaben dazu bei einem Interrupt einen Wert an den Prozessor, der dann diesen zu einer Tabellen-Anfangsadresse addierte und die Adresse der Service-Routine von dieser Adresse holte. Das war sehr flexibel, weil man sowohl die Tabelle als auch die vom Schnittstellenbaustein zurückgegebenen Werte jederzeit im Programm manipulieren konnte und so flugs die ganze Tabelle oder die Interrupt-Service-Routine wechseln konnte. Der zu der Tabellenadresse zu zählende Wert wurde als Vektor oder Displacement (Verschiebung) bezeichnet.

Und jetzt wissen wir, wie es zu dem Wort Vektortabelle kam, und dass es mit den AVR rein gar nix zu tun hat, weil wir es weder mit einer Tabelle noch mit Vektoren zu tun haben. Unsere Sprungziele sind im AVR fest verdrahtet, es wird auch nix zu einer Anfangsadresse einer Tabelle addiert und schon gar nicht sind irgendwelche Service-Routinen austauschbar.

Warum verwenden wir diese Worte überhaupt? Gute Frage. Weil es alle tun, weil es sich doll anhört und weil es Anfänger eine Weile davon abhält zu kapieren worum es wirlich geht.

Aussehen bei kleinen AVR

Eine Reset- und Interrupt-Vektor-Tabelle sieht bei einem kleineren AVR folgendermaßen aus:
   rjmp Main ; Reset, Sprung zur Initiierung
   rjmp Int0Sr ; Externer Interrupt an INT0, Sprung zur Service-Routine
   reti ; Irgendein anderer Interrupt, nicht benutzt
   rjmp IntTc0OvflwSr ; Überlauf Timer 0, Behandlungsroutine
   reti ; Irgendwelche anderen Interrupts, nicht benutzt
   reti ; Und noch mehr Interrupts, auch nicht benutzt
Merke: Alles andere hat in dieser Sprungliste rein gar nix zu suchen. Das hat damit zu tun, dass die Sprungadressen dann genau stimmen, kein Vertun beim Springen erfolgt und die korrekte Anzahl und Abfolge mit dem Handbuch verglichen werden kann. Die Instruktion RETI sorgt dafür, dass der Stapel in Ordnung gebracht wird und Interrupts auf jeden Fall wieder zugelassen werden.

Schlaumeier glauben, sie könnten auf die lästigen RETI-Instruktionen verzichten. Z.B. indem sie das oben stehende wie folgt formulieren:
   rjmp Main ; Reset, Sprung zur Initiierung
.org $0001
   rjmp Int0Sr ; Externer Interrupt an INT0, Sprung zur Service-Routine
.org $0003
   rjmp IntTc0OvflwSr ; Überlauf Timer 0, Behandlungsroutine
Das geht, alle Sprungbefehle stehen an der korrekten Position. Es funktioniert auch, solange nicht absichtlich oder aus Versehen ein weiterer Interrupt zugelassen wird. Der ungeplante Interrupt findet an der mit .org übersprungenen Stelle den auszuführenden Opcode $FFFF vor, da alle unprogrammierten Speicherstellen des Programmspeichers beim Löschen mit diesem befüllt werden. Dieser Opcode ist nirgendwo definiert, er macht auch nix, er bewirkt nichts und die Bearbeitung wird einfach an der nächsten Stelle im Speicher fortgesetzt. Wo der versehentliche Interrupt landet, ist dann recht zufällig und jedenfalls ungeplant. Folgt auf die Einsprungstelle irgendwo noch ein weiterer Sprung zu einer anderen Serviceroutine, dann wird eben die fälschlich ausgeführt. Folgt keine mehr, dann läuft das Programm in die nächstfolgende Unterroutine. Das wäre fatal, weil die garantiert nicht mit RETI endet und daher die Interrupts nie wieder zugelassen werden. Oder, wenn keine Unterroutinen zwischen der Sprungtabelle und dem Hauptprogramm stehen, der weitere Ablauf läuft in das Hauptprogramm, und alles wird wieder von vorne initiiert.

Ein weiteres Scheinargument, damit liefe die Software auf jedem anderen AVR-Typ auch korrekt, ist ebenfalls Käse. Ein Blick auf die Tabellen mit den Interrupt-Sprungzielen zeigt, dass sich ATMEL mitnichten immer an irgendwelche Reihenfolgen gehalten hat und dass es munter durcheinander geht. Die Scheinkompatibilitäet kann also zur Nachlässigkeit verführen, eine fatale Fehlerquelle.

Daher sollte gelten:

Aufbau bei großen AVR

Große AVR haben einen Adressraum, der mit relativen Sprungbefehlen nicht mehr ganz zugänglich ist. Bei diesen hat die Sprungliste Einträge mit jeweils zwei Worten Länge. Etwa so:
   jmp Main ; Reset, Sprung zur Initiierung
   jmp Int0Sr ; Externer Interrupt an INT0, Sprung zur Service-Routine
   reti ; Irgendein anderer Interrupt, nicht benutzt
   nop
   jmp IntTc0OvflwSr ; Überlauf Timer 0, Behandlungsroutine
   reti ; Irgendwelche anderen Interrupts, nicht benutzt
   nop
   reti ; Und noch mehr Interrupts, auch nicht benutzt
   nop
Bei Ein-Wort-Einträgen (z.B. RETI) sind die NOP-Instruktionen eingefügt, um ein weiteres Wort zu ergänzen, damit die Adressierung der nachfolgenden Sprungziel-Einträge wieder stimmt.

To the top of that page

2009 by http://www.avr-asm-tutorial.net