Interrupts (Assembler)

Einführung

Ein Interrupt wird entweder durch ein „externes Ereignis“ oder ein „internes Ereignis“ ausgelöst und unterbricht den Ablauf des Programms, um ein Unterprogramm auszuführen. Sobald dieses Unterprogramm abgearbeitet ist, wird die Rücksprungadresse vom Stackpointer geholt und das Hauptprogramm weiter ausgeführt.

Dieses Unterprogramm nennt sich Interrupt Service Routine (ISR).

Ein „externes Ereignis“ ist beispielsweise die Auslösung einer Lichtschranke und ein „internes Ereignis“ wird durch sogenannte Timer erzeugt.

Dieser Beitrag beschränkt sich jedoch lediglich auf die „externen Ereignisse“!

Unser Mikrocontroller besitzt 2 Interrupts, die durch „externe Ereignisse“ ausgelöst werden können:

  1. Interrupt
    • Bezeichnung: INT0
    • Port: 3.2
    • Einsprungadresse: 0003h
  2. Interrupt
    • Bezeichnung: INT1
    • Port: 3.3
    • Einsprungadresse: 0013h

Was passiert, wenn ein Interrupt ausgelöst wird?

Sobald ein Interrupt ausgelöst wird, wird zuerst einmal der aktuelle Befehl fertig abgearbeitet. Danach wird der Stackpointer erhöht und die Rücksprungadresse auf dem Stack abgelegt. Somit weiß das Programm später, wo es nach beenden der ISR fortfahren muss. Als nächstes wird der Programm-Counter (PC) auf die Einsprungadresse des ISR gesetzt. Der ISR wird abgearbeitet, die Rücksprungadresse vom Stack geholt, der Stackpointer verringert und die Rücksprungadresse geladen.

Somit befindet sich der Stack bei Aufruf und Verlassen der ISR immer in gleichem Zustand!

 

Anwendung

Stackpointer setzen

Da das Programm beim Auslösen eines Interrupts in ein Unterprogramm springt, muss eine Rücksprungadresse im Speicher (SFR) abgelegt werden. Damit diese nicht dort abgelegt wird, wo sich möglicherweise wichtige Daten befinden (werden), weist man dem Stackpointer (SP) eine bestimmte (freie) Adresse im Speicher zu.

In diesem Fall weise ich die Adresse 0x80 zu:

mov SP,#0x80

Interrupt-Modus wählen

Anschließend hat man die Wahl zwischen 2 verschiedenen Interrupt-Modi. Dies geschieht durch setzen bzw. entfernen der Bits IT0 für INT0 bzw. IT1 für INT1.

  1. mit Low-Signal gesteuert
clr IT0
  1. mit Flanke gesteuert
setb IT0

Interrupt freigeben

Um einen Interrupt verwenden zu können, muss dieser zuerst freigeschaltet werden. Dies geschieht durch setzen der Bits EX0 für INT0 bzw. EX1 für INT1.

setb EX0

Danach muss noch ein Bit gesetzt werden, um alle Interrupts „generell“ zu aktivieren:

setb EA

ISR erstellen

Damit der Interrupt am Ende auch Sinn macht, benötigt man eine Interrupt Service Routine (ISR).

Dazu setzt man cseg auf die jeweilige Einsprungsadresse, erstellt ein Label und beendet die ISR mit dem Schlüsselwort reti, welches dem Assembler mitteilt, dass die ISR beendet ist.

cseg at 0x0003      //cseg auf die Einsprungsadresse setzen
isr:                //Label setzen
                    //Irgendwelche Befehle
   reti             //ISR beendet

Nun kann der Interrupt INT0 verwendet werden!

 

Zusammenfassung

  1. Stackpointer setzen
  2. Interrupt-Modus wählen
  3. Ausgewählten Interrupt freigeben
  4. Alle Interrupts „generell“ freigeben

 

Quelltext-Beispiel

Folgendes (kommentiertes) Quelltext-Beispiel löst Interrupt 0 aus und setzt ein Bit bei P2.0, sobald ein Low-Signal an Port 3.2 anliegt:

$NOMOD51                 //Vordefinitionen vergessen
#include <at89c5131.h>   //Neue Definitionen einbinden
 
cseg at 0x0000
jmp main                 //zu Sprungmarke main springen
cseg at 0x0003           //vordefinierte Einsprungadresse (siehe Einfuehrung)
isr:                     //Sprungmarke isr
   setb P2.0             //Bit bei P2.0 setzen
   reti                  //ISR beendet - zuruekspringen

main:                    //Sprungmarke main
   mov SP,#0x80          //Stackpointer auf 0x80 setzen
   mov P2,#0x00          //P2 auf 0x00 setzen
   clr IT0               //INT0 soll auf Low-Signale reagieren
   setb EX0              //INT0 freischalten
   setb EA               //Alle Interrupts generell freischalten
 
loop:                    //Endlosschleife
   nop
   jmp loop

end