Interrupts
Einführung top
Der Programmfluss eines Assemblerprogramms ist sequentiell, d.h. es wird immer eine Anweisung nach der anderen ausgeführt.
Dieser Programmfluss kann nur durch spezielle Befehle verändert werden, den Sprungbefehlen.
Allerdings ermöglichen es uns Interrupts den Programmfluss zu unterbrechen (engl. to interrupt),
ein Unterprogramm auszuführen und danach den Programmfluss wieder aufzunehmen als sei nichts gewesen.
Solche Unterprogramme werden Interrupt-Service-Routinen (ISR) genannt. Eine ISR wird nur ausgeführt, wenn ein entsprechendes
Ereignis aufgetreten ist, dies kann ein Timer/Counter-Überlauf, einer der zwei externen Interrupts (an P3.2 oder P3.3) oder das
empfangen eines ASCII-Zeichen über die Serielle-Schnittstelle sein.
Ist der Mikrocontroller so konfiguriert, dass er auf dieses Ereignis reagieren soll, so wird das Hauptprogramm
kurzzeitig unterbrochen und die Kontrolle geht an ein spezielles Code-Fragment über (die ISR). Nach dem Abarbeiten der ISR wird
das Hauptprogramm fortgesetzt, ohne das dies jemals mitbekommt, dass es unterborchen wurde.
Interrupt-Quellen top
Als Interrupt-Quelle bezeichnet man ein Ereignis, welches einen Interrupt auslösen kann
(also das Programm unterbricht und zur ISR springt).
Damit man die Ereignisse unterscheiden kann, die einen Interrupt auslösen, springt jeder Interrupt zu einer eigenen
Interrupt-Service-Routine (ISR). Jede ISR liegt an einer festen Adresse im ROM. Die folgende Tabelle listet alle
Interrupt-Quellen und die zugehörigen Adressen der Interrupt-Service-Routinen auf:
Interrupt-Quelle | Adresse der ISR |
Externer Interrupt 0 |
003h |
Timer0-Überlauf |
00Bh |
Externer Interrupt 1 |
013h |
Timer1-Überlauf |
01Bh |
Serieller Interrupt |
023h |
Timer2-Überlauf |
02Bh |
Timer2 ist nicht auf jedem 8051 vorhanden! |
Interrupts aktivieren top
Eine Unterbrechung eines Assembler-Programms durch einen Interrupt ist nicht immer wünschenswert, deshalb können Interrupts
an- und abgeschaltet werden. Standartmäßig sind alle Interrupts abgeschalten.
Zum einen gibt es einen generellen Interrupt-Anschalt-Knopf (das Bit IE.7
oder kurz EA
;
IE
=Interrupt Enable und EA
=Enable All interrupts).
Ist dieses auf 1 gesetzt, so sind Interrupts möglich, ist es 0 so sind alle Interrupts gesperrt.
Da es fast kein Programm nötig hat alle Interrupts zu behandeln, kann man jeden einzelnen an- oder abschalten. Hierfür
sind die restlichen Bit im IE
zuständig. Standartmäßig sind diese alle auf 0 (=abgeschalten) gesetzt.
Welches Bit in IE
welchen Interrupt an- bzw. abschaltet zeigt die folgende Tabelle:
Bits in IE (=0A8h) |
Bit Nr. | Name | Interrupt |
IE.7 |
EA |
Enable All Interrupts |
IE.6 |
nicht verwendet |
IE.5 |
ET2 |
Timer2 Interrupt |
IE.4 |
ES |
Serieller Interrupt |
IE.3 |
ET1 |
Timer1 Interrupt |
IE.2 |
EX1 |
Externer Interrupt 1 |
IE.1 |
ET0 |
Timer0 Interrupt |
IE.0 |
EX0 |
Externer Interrupt 0 |
Merke: Um einen Interrupt nutzen zu können (also eine Unterbrechung zuzulassen) muss
EA
und das
betreffende Bit in
IE
für den betreffenden Interrupt auf 1 gesetzt werden!
Abb.: Übersichtsbild über das Interruptsystem.
Sprung zur ISR - oder was passiert bei einem Interrupt? top
Wenn ein Interrupt ausgelöst wurde führt der Mikrocontroller folgende Schritte vollautomatisch aus:
- aktuellen Befehl beenden
- Programm-Counter auf dem Stack ablegen
- Programm-Counter auf den Wert der ISR setzen
- ISR abarbeiten
- Programm-Counter vom Stack holen (
reti
)
- Programm normal fortsetzen
Die Externen Interrupts top
Es existieren zwei externe Interrupts EX0 und EX1. Diese werden, wie der Name schon sagt, durch externe Quellen verursacht, nämlich
durch einen Tastendruck an den Portpins P3.2 (EX0) und P3.3 (EX1).
Im folgenden wird EX0 beschrieben, die Werte und Adressen für EX1 werden in [] angegeben.
Mittels des Bits IT0=TCON.0 [IT1=TCON.2]
kann eingestellt werden, ob der Interrupt bei einer fallenden Flanke (IT0=1
) oder
bei einem gedrückten (P3.2=0 [P3.3]) Schalter (IT0=0
) ausgelöst wird. Der Unterschied ist, dass eine fallende Flanke nur einmal
pro Tastendruck, ein niederer Pegel - also P3.2=0 [P3.3] - bis zum Loslassen des Tasters dauerhaft vorhanden ist, der Interrupt also
wieder und wieder ausgelöst wird.
Wird der Interrupt aktiviert (setb IE.0 [IE.2]
und setb EA
) und ausgelöst, so springt der Mikrocontroller zur
Interrupt-Service-Routine (ISR) des eXternen Interrupts an der Adresse 03h [0Bh].
Ein kleines Beispielprogramm:
ljmp main ; ISR überspringen und im Hauptprogramm (main) beginnen
org 03h ; ISR für EX0 an Adresse 03h im ROM platzieren
inc P0 ; ISR erhöht P0 um 1
reti ; ISR-Ende (Hauptprogramm fortsetzen)
main: setb IT0 ; Externer Interrupt reagiert auf fallende Flanke an P3.2
setb EX0 ; Externen Interrupt aktivieren
setb EA ; Interrupts generell zulassen
; --- ab hier reagiert der µC auf den eXternen Interrupt 0
wait: ljmp wait ; Endlosschleife (Hauptprogramm tut "nichts" mehr)
Die Timer Interrupts top
Der 8051 hat zwei Timer/Counter (T/C0 und T/C1).
Im folgenden wird T/C0 beschrieben, die Werte von T/C1 werden in [] angegeben.
Timer/Counter0 löst einen Interrupt aus, wenn das Zählregister überläuft. Um dies besser zu verstehen werden
wir erst die Funktionsweise des T/C0 betrachten, bevor wir auf die Interrupts eingehen.
Der T/C0 kann als Timer oder Counter verwendet werden. Als Timer zählt er die Maschinenzyklen (MZ), das heißt er
erhöht seinen Wert jedem Mikrosekunde. Als Counter zählt er die fallenden Flanken an dem Port-Pin 3.4 [3.5].
T/C0 zählt in den Registern TH0 und TL0 [T/C1 in TH1 und TL0].
Des weiteren kann man, sowohl für den Counter als auch für den Timer, unterschiedliche Modi festlegen. Alle diese
Einstellungen werden in dem Register TMod
vorgenommen:
Bits in TMod (=089h) nicht bit-adressierbar |
Bit Nr. | Bedeutung |
7 |
Gate1: uninteressant für uns |
6 |
T/C1 (s. Bit 2) |
5 |
Modus von T/C1 gleich wie die Modi von T/C0 (s. Bit0+1) |
4 |
3 |
Gate0: uninteressant für uns. |
2 |
T/C0: Mit diesem Bit wird festgelegt, ob T/C0 als Timer oder als Counter operieren soll. Ist dieses Bit
0 so ist T/C0 ein Timer; ist es 1 so ist T/C0 ein Counter. |
1 |
Wert | Modus |
11 | für uns nicht interessant |
10 | 8-Bit Auto-Reload-Modus: In TH0 wird ein Reload-Wert gespeichert.
Der T/C0 zählt in TL0. Hierbei beginnt er nicht bei 0 sondern bei
dem Wert, welcher in TH0 gespeichert wurde.
Wenn der T/C0 überläuft (wenn in TL0 der Wert 0FFh steht und dieser um 1 erhöht wird)
startet der T/C0 läd der er wieder den Wert von TH0 in TL0 und beginnt somit wieder bei diesem
Wert.
Da hier immer ein festgelegter Startwert geladen wird heißt der Modus Auto-Reload-Modus.
|
01 | 16-Bit Modus: TH0 und TL0 bilden zusammen eine 16-Bit-Variable wobei in TH0 die oberen (höherwertigen)
8 Bits stehen und in TL0 die niederwertigen.
Der T/C0 zählt immer von 0 bis 0FFFFh und beginnt dann wieder bei 0.
|
00 | 13-Bit Modus: Die unteren (niederwertigen) 5 Bit von TL0 und die 8 Bit von TH0 bilden eine 13-Bit-Zahl.
Die 5 Bits von TL0 sind die hinteren Bits dieser Zahl. Der T/C0 zählt somit von 0 bis 2 hoch 13 und
beginnt dann wieder bei 0.
|
|
0 |
Merke: Will man TMod ändern, so muss man den Zähler/Counter zuerst anhalten.
Nachdem für T/C0 der richtigen Modus gesetzt wurde kann er gestartet werden, dies geschieht indem man das Bit TCon.4 [TCon.6]
auf 1 setzt (TCon=88h) und man hält ihn an indem man TCon.4 [TCon.6] auf 0 setzt.
Doch wie bekommt das Programm mit, das der T/C0 überläuft (also vom Maximalwert auf 0 springt)? Genau hier kommt der
Timer-Interrupt ins Spiel. Falls IE.1 [IE.3] und EA auf 1 gesetzt wurden, löst jeder Überlauf einen Interrupt aus
und springt zu der ISR an Adresse 0Bh [01Bh] im Rom. Hier kann im Programm festgelegt werden wie der Überlauf behandelt werden
soll.
Den Timer-Interrupt benötigt man nur, wenn man auf Werte größer als 0FFFFh zählen will.
Ein kleines Beispielprogramm, welches die Anzahl der Überläufe in 040h mitzählt:
include reg_51.pdf
ljmp main ; ISR überspringen
org 0Bh ; ISR für Timer0 Überlauf
call isr_timer
reti
main:
mov 040h, #0 ; 040h initialisieren
mov TMod, #01 ; T/C0 als 16-Bit-Timer
setb IE.1 ; Timer-Interrupt0 zulassen
setb EA ; Interrupts anschalten
setb TCon.4 ; T/C0 starten
w: sjmp w ; sinnlose Endlosschleife
isr_timer: ; wird bei jedem Überlauf aufgerufen
inc 040h
ret
end