Path:
Home =>
AVR-Überblick =>
Interrupt-Programmierung => Vektortabelle
(This page in English:
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:
- Die Anzahl der Instruktionen (RJMP, RETI) hat exakt der Anzahl der im
jeweiligen AVR-Typ möglichen Interrupts zu entsprechen.
- Alle benutzten Interrupts verzweigen mit RJMP zu einer spezifischen
Interrupt-Service-Routine.
- Alle nicht benutzten Interrupt-Sprungadressen werden mit der Instruktion
RETI abgeschlossen.
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:
- In einer Sprungtabelle haben .org-Direktiven nix verloren.
- Jeder (noch) nicht verwendete Eintrag in der Sprungtabelle wird mit RETI
abgeschlossen.
- Die Länge der Sprungtabelle entspricht exakt der für den Prozessor
definierten Anzahl Interruptsprünge.
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