Einleitung | ||
Flags und Enable-Bits | ||
Interrupt-Auslösung | ||
Sichern der Programm-Umgebung | ||
Beispiel | ||
So ein Programm hat es aber schwer, auf
plötzlich auftretende Ereignisse zu reagieren. Soll der
Microcontroller
z.B. auf das Drücken einer an einem Pin angeschlossenen Taste
reagieren,
so muss er diese Taste in kurzen Abständen immer wieder
abfragen,
um keinen kurzen Tastendruck zu verpassen. Diese Methode der
Ereignis-Überwachung
nennt sich Polling. Polling ist ausreichend, wenn nur ein oder zwei
externe
Signale überwacht werden müssen, und der Microcontroller
sowieso
nichts anderes zu tun hat.
; Beispiel für
Polling ; Pin RB0 wird überwacht loop
; und weiter im Programm ; ja: Programm fortsetzen |
Kompliziert wird das Polling aber, wenn der Microcontroller eigentlich ständig mit komplexen Aufgaben ausgelastet ist, und während der Ausführung dieser Aufgaben auch noch zyklisch die Ereignis-Eingänge überwachen soll. In diesem Fall sollte anstelle des Polling der Interrupt verwendet werden.
Beim Interrupt überwacht die Hardware des Microcontrollers mögliche auftretende Ereignisse. Tritt ein Ereignis ein, unterbricht der Microcontroller seine normale Arbeit, und springt in eine spezielle Programmroutine, zur Behandlung dieses Ereignisses - die Interrupt-Routine. Mögliche externe Ereignisse für PIC-Prozessoren sind z.B.:
Es lösen immer nur die Ereignisse einen Interrupt aus, für die die Interrupterzeugung auch eingeschaltet wurde. Zum Einschalten der benötigten Interrupts dienen in einigen Steuerregistern Enable-Bits für jede Interruptquelle. Diese sind standardmäßig auf 0 (also aus) gesetzt. Wird ein solches Enable-Bit durch das PIC-Hauptprogramm auf 1 gesetzt, dann ist das zugehörige Interrupt-Flag in der Lage, einen Interrupt auszulösen.
Beispiel:
Jedes mal, wenn der Timer0
überläuft
setzt er das Bit T0IF (Bit 2 im Register INTCON) auf 1.
Das
hat aber keine weiteren Auswirkungen, wenn nicht auch das
zugehörige
Enable Bit T0IE (Bit 5 in INTCON) gesetzt wurde.
Außer diesen Enable-Bits, mit denen jede Interruptquelle ein und aus geschaltet werden kann, gibt es auch noch einen Interrupt-Hauptschalter, das Bit GIE (Bit 7 in INTCON ) - general interrupt enable. Auch dieses Bit muss also vom Hauptprogramm zunächst auf 1 gestellt werden.
Läuft nun der Timer0 über, wird im Microcontroller ein Interrupt ausgelöst.
Mit den wenigen Bits des INTCON-Registers lassen sich gerade einmal 3 Interrupts verwalten. Das reicht nur für die allereinfachsten PICs. Diese grundlegenden Interrupts sind:
An dieser Adresse (04h=0x0004) muss also die Interrupt-Behandlungsroutine beginnen.
Gleichzeitig wird GIE (der Interrupt-Hauptschalter) auf 0 gesetzt, um einen weiteren Interrupt während des Interrupts zu verhindern.
Es folgt nun die
Interrupt-Behandlungsroutine,
in der der dringend abzuarbeitende Programmcode steht.
Innerhalb dieser Routine muss per
Software das
auslösende Interrupt-Flag wieder auf 0 zurückgesetzt werden!
Wird dies vergessen, löst das noch immer aktive Flag nach dem
Interrupt-Ende
sofort einen neuen Interrupt aus. Das Resultat wäre ein PIC, der
in
einer Endlosschleife von Interrupts fest hängt.
Diese Routine muss mit dem Befehl RETFIE enden. Dies ist ein erweiterter RETURN-Befehl. Der Microcontroller springt wieder in das Hauptprogramm (er hat sich ja die Adresse des nächsten Befehls des Hauptprogramms gemerkt) und schaltet das GIE-Bit wieder ein, wodurch neue Interrupts wieder erlaubt sind.
Wir haben oben gesehen, das es eine
ganze Reihe von Ereignissen gibt, die einen Interrupt auslösen
können. In allen Fällen springt aber der PIC in die selbe
Interruptroutine. Wenn gleichzeitig mehreren Interruptquellen durch
Setzen der Interrupt-Enable-Bits erlaubt wurde einen Interrupt
auszulösen, dann muss in der Interruptroutine zunächst
geprüft werden, was denn den Interrupt im Moment gerade
ausgelöst hat. Dafür fragt man die Interrupt-Flags ab. Dann
springt man in den Teil der Interruptroutine, der für diese Art
Interrupt zuständig ist.
Priorisierte Interrupts bei
PIC18F-Typen
Bei einfachen PICs (PIC16F...)
sind alle Interrupts gleich wichtig. Wird ein Interrupt ausgelöst,
dann wird die Interruptroutine abgearbeitet. Tritt währenddessen
ein weiterer Interrupt auf, dann wird der ignoriert, bis die
Interruptroutine abgearbeitet wurde. Die Interruptroutine wird durch
einen weiteren Interrupt also nicht unterbrochen.
Nun kann es aber sein, dass es einzelne Ereignisse gibt, die so wichtig
sind, dass der PIC auf sie auf jeden Fall sofort reagieren muss.
Dafür gibt es bei den größeren PICs priorisierte
Interrupts.
Das bedeutet nur, dass es bei diesen PICs zwei Sorten von
Interrupts gibt, bei denen der PIC auch zu verschiedenen
Interruptstartadressen springt:
(Einen one-fits-all-Interrupt mit
Startadresse 0x0004 wie bei den PIC16F-Typen gibt es hier also nicht.)
Die Interruptroutine des low-priority-Interrupts kann durch einen
high-priority-Interrupt unterbrochen werden. Dann wird zuerst die
high-priority-Interrupt-Routine abgearbeitet, und danach zur
low-priority-Interrupt-Routine zurückgekehrt. Ist die auch
abgearbeitet, dann geht es endlich ins Hauptprogramm zurück.
Durch das Setzen von Bits in einem
speziellen Steuerregister kann für jede mögliche
Interruptquelle festgelegt werden, ob sie einen high-priority-Interrupt
oder einen low-priority-Interrupt auslösen soll.
Das Hauptprogramm verlässt
aber sich
darauf, dass nur es selbst das STATUS-Register und das
Register
w
(Akku) verändert. Das geht auch gut, bis man Interrupts nutzt.
Viele
normale Befehle einer Interrupt-Routine werden z.B. das Zero-Flag oder
das Carry-Flag im STATUS-Register verändern. Tritt im
folgenden
Programmausschnitt
decf
counter, f btfsc STATUS, Z goto Null |
zwischen der 1. und der 2. Zeile ein Interrupt auf, der das Zero-Flag verändert, dann versagt an dieser Stelle das Programm. Auch das w-Register wird kaum unverändert eine Interruptroutine überstehen.
Um solchen Problemen vorzubeugen, muss die Programm-Umgebung des Hauptprogramms 'gerettet' werden wenn ein Interrupt ausgelöst wird, und der gerettete Zustand muss wieder hergestellt werden, wenn der Interrupt endet. Zu den zu rettenden Werten zählt außer dem (automatisch geretteten) Programcounter:
Für einen 16F84 sollte die
Interruptbehandlungsroutine stets mit dem folgenden Code beginnen:
movwf
w_temp ;status retten
swapf
STATUS,w
movwf
status_temp
und wie folgt enden:
swapf
status_temp,w
movwf
STATUS
swapf
w_temp,f
swapf
w_temp,w
retfie
Für einen 16F62x oder
einen
12F6xx
sollte die Interruptbehandlungsroutine stets mit dem folgenden Code
beginnen:
movwf
w_temp
swapf
STATUS,w
bcf
STATUS, RP0 ; status_temp in Bank 0
movwf
status_temp
und wie folgt enden:
swapf
status_temp,w
movwf
STATUS
swapf
w_temp,f
swapf
w_temp,w
retfie
Für einen 16F87x sollte
die
Interruptbehandlungsroutine stets mit dem folgenden Code beginnen:
MOVWF
W_TEMP
SWAPF
STATUS,W
CLRF
STATUS
MOVWF
STATUS_TEMP
MOVF
PCLATH, W
MOVWF
PCLATH_TEMP
CLRF
PCLATH ; Bank 0
und wie folgt enden:
MOVF
PCLATH_TEMP, W
MOVWF
PCLATH
SWAPF
STATUS_TEMP,W
MOVWF
STATUS
SWAPF
W_TEMP,F
SWAPF
W_TEMP,W
retfie
Die Verwendung von SWAPF-Befehlen wirkt
zwar umständlich, aber einfache MOVF-Befehle verändern das
Zero-Flag
und können deshalb nicht verwendet werden.
Das Register
'status-temp'
sollte in der Bank 0 definiert sein.
Da man nicht
weiß,
welche Registerbank gerade ausgewählt ist, während der
Interrupt
ausgelöst wird, muss das Register 'w_temp' ein Register
sein, das in jeder Bank existiert. Beim 16F84 und beim 12F6xx ist das
immer
garantiert, aber z.B. beim 16F62x dürfen für 'w_temp'
Register
mit einer Adresse unter 0x70 nur verwendet werden, wenn im Programm
niemals
in die Bänke 2 oder 3 geschaltet wird.
Beispiel:
Wird 'w_temp'
für
die Adresse 0x20 definiert (w_temp
equ 0x20),
und befindet sich der Prozessor im Augenblick des Interrupts in der
Bank
3, dann versucht die erste Zeile der Interruptserviceroutine, den
Inhalt
von w in 0x20 abzuspeichern (movwf
w_temp).
Da der Prozessor in der Bank 3 ist, wird anstelle der Adresse 0x20 aber
die Adresse 0x1A0 verwendet. Der 16F62x besitzt auf dieser Adresse aber
keine Register. Der Wert von w verschwindet im Nirwana, und der Absturz
ist vorprogrammiert.
Damit sind die wichtigsten Register
geschützt.
Verändert man in der Interruptroutine weitere Register, die das
Hauptprogramm
unverändert benötigt (TRISA, TRISB, FSB ...) so muss man
sie innerhalb der Interruptroutine ebenfalls retten und am Ende
wiederherstellen.
Die oben aufgeführten
Codeschnipsel gelten nur für die jeweils angegebenen PIC-Typen.
Bei anderen (insbesondere größeren) PICs mag der Code zum
Retten und Zurückschreiben der "Programm Umgebung" etwas
abweichen. Genaue Informationen darüber findet man im Datenblatt
des jeweiligen PIC unter: "context saving during interrupt".
(Special Features Of The CPU - Interrupts - Context Saving During
Interrupts)
Das Programm muss aus Hauptprogramm und Interruptroutine bestehen. Da die Interruptroutine auf der Adresse 04h beginnt, ist davor nicht genug Platz für das Hauptprogramm. Deshalb wird auf der Adresse 00h ein Sprung zum Hauptprogramm erfolgen, welches hinter der Interruptroutine stehen muss.
Das Hauptprogramm hat folgende Aufgaben zu erfüllen:
Das erlauben von RB0-Interrupts erfolgt mit dem INTE-Bit (Interrupt-Enable) im INTCON-Register.
Das vom RB0 gesetzte Interruptflag, das
in der Interruproutine wieder gelöscht werden muss ist INTF
in INTCON-Register.
list
p=16f84 ;************************************************************** ;* ;* Pinbelegung ;* ---------------------------------- ;* PORTA: 0 LED ;* 1 ;* 2 ;* 3 ;* 4 ;* PORTB: 0 Eingang ;* 1 ;* 2 ;* 3 ;* 4 ;* 5 ;* 6 ;* 7 ;* ;************************************************************** ; ; sprut (zero) Bredendiek 05/2002 ; ; Demo für Interrupt ; 0-1-Flanke an RB0 ändert LED an RA0 ; ; Taktquelle: 4 MHz ; ;************************************************************** ; Includedatei für den 16F84 einbinden #include <P16f84.INC> ; Configuration
festlegen __CONFIG _PWRTE_ON & _WDT_OFF & _XT_OSC ;**************************************************************
w_copy
Equ
0x20
;
Backup
für
Akkuregister ;**************************************************************
org
0 ;**************************************************************
org
4 incf PORTA, f ; invertieren von RA0 Int_end
;**************************************************************
Init
; RB0-Interrupt
einstellen
bsf
INTCON,
INTE
;
RB0-interrupt erlauben loop goto loop ; eine Endlosschleife ;********************************************************** end |
Autor: sprut
erstellt: 07.05.2002
letzte Änderung: 29.05.2007