PIC-Prozessoren - Interrupt


zurück zu PIC-Prozessoren , Elektronik , Homepage


 
Einleitung

Flags und Enable-Bits

Interrupt-Auslösung

Sichern der Programm-Umgebung

Beispiel







zurück


Einleitung

Das Programm eines Prozessors oder Microcontrollers enthält oft in einer ständig durchlaufenen Schleife alle nötigen Routinen.

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
    btfss   PORTB, 0        ; ist RB0 high?
    goto    loop            ; nein: in der Warteschleife bleiben

; 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.:

Welche Interruptquellen ein PIC unterstützt, hängt natürlich von der vorhandenen Hardware ab. Ein 16F84 hat z.B. keine USART und folglich auch keine USART-Interrupts.
 
nach oben

Flags und Enable-Bits

Für jede Interruptquelle gibt es in einem Register ein Interrupt-Bit - das Interrupt-Flag. Tritt ein Ereignis auf (z.B. der Überlauf des Timer0), dann wird das zu diesem Ereignis gehörende Interruptflag auf 1 gesetzt.

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.

Bedingung zur Auslösung eines Interrupts am Beispiel des Timer0

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:

Bessere PICs haben mehr Interruptquellen. Der 16F876 bringt es schon auf 14. Um diese zu verwalten, hat er 4 zusätzliche Steuerregister für die zusätzlichen (sogenannte peripheren) Interrupts: All diese zusätzlichen (peripheren) Interrupts müssen dann aber zusätzlich durch das Bit PEIE (Bit 6 im INTCON-Register) freigeschaltet werden. Da die genaue Bedeutung der einzelnen Steuer-Bits sowie auch die Adressen der Register PIR1, PIR2, PIE1 und PIE2 von PIC-Typ zu PIC-Typ verschieden ist, hilft dann nur ein Blick in die Dokumentation des jeweiligen Chips. Eine Beschreibung in dieser Webpage würde den Rahmen sprengen.
nach oben

Interrupt-Auslösung

Bei einem Interrupt beendet der Microcontoller noch den gerade anstehenden Befehl des Hauptprogramm, dann speichert er die Adresse des nachfolgenden Befehls (um später wieder zurückzufinden) und springt zur Interrupt-Adresse. (Das ist bei PIC16F-Typen die Adresse 0x04.) Je nach Takt wird dafür etwa eine Mikrosekunde benötigt.

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.

Ablauf eines Interrupts
 
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.

nach oben

Sichern der 'Programm-Umgebung'

Die Interrupt-Routine wird also an eine 'zufällige' Stelle im Hauptprogramm "eingefügt", wenn ein Ereignis auftritt.

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:

Während das STATUS-Register und w in allen PICs vorkommt, ist PCLATCH nur bei größeren PICs (16F87x) von Interesse.

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)

nach oben

Beispiel

Im folgenden Beispiel soll immer dann, wenn der Eingang RB0 von 0 auf 1 geht, per Interrupt eine LED am Port RA0 umgeschaltet werden (an-aus-an- ...). Das ließe sich natürlich auch einfacher realisieren, aber es geht um die Demonstration der Interrupt-Programmierung.

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:

Die Interruptroutine hat folgende Aufgaben zu erfüllen: Das Port RB0 kann sowohl bei einer 0-1 wie auch bei einer 1-0-Flanke einen Interrupt auslösen. Das wird mit dem Bit INTEDG im Options-Register eingestellt.

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
; bis 4 MHz: Power on Timer, kein Watchdog, XT-Oscillator

        __CONFIG _PWRTE_ON & _WDT_OFF & _XT_OSC

;**************************************************************
; Variablennamen vergeben

w_copy  Equ    0x20               ; Backup für Akkuregister
s_copy  Equ    0x21               ; Backup für Statusregister

;**************************************************************
; los gehts mit dem Programm

        org    0
        goto   Init               ; Sprung zum Hauptprogramm

;**************************************************************
; die Interruptserviceroutine

        org  4 
intvec
        movwf  w_copy             ; w retten
        swapf  STATUS, w          ; STATUS retten
        movwf  s_copy 

        incf   PORTA, f           ; invertieren von RA0

Int_end 
        bcf    INTCON, INTF       ; RB0-Interrupt-Flag löschen
        swapf  s_copy, w          ; STATUS zurück
        movwf  STATUS 
        swapf  w_copy, f          ; w zurück mit flags
        swapf  w_copy, w
        retfie

;**************************************************************
; das Hauptprogramm

Init
; Port RA0 auf Ausgabe stellen
        clrf   PORTA              ; LED aus
        bsf    STATUS, RP0        ; auf Bank 1 umschalten
        movlw  B'11111110'        ; PortA RA0 output
        movwf  TRISA
        bcf    STATUS, RP0        ; auf Bank 0 zurückschalten

; RB0-Interrupt einstellen
        bsf    STATUS, RP0        ; auf Bank 1 umschalten
        bsf    OPTION_REG, INTEDG ; 0-1-Flanke an RB0
        bcf    STATUS, RP0        ; auf Bank 0 zurückschalten

        bsf    INTCON, INTE       ; RB0-interrupt erlauben
        bsf    INTCON, GIE        ; Interrupt generell erlauben

loop    goto   loop               ; eine Endlosschleife

;**********************************************************

        end

   

nach oben

zurück zu PIC-Prozessoren , Elektronik , Homepage

Autor: sprut
erstellt: 07.05.2002
letzte Änderung: 29.05.2007