Alle Interrupts setzen voraus, dass sie zuerst global erlaubt wurden. Dies erfolgt durch das Setzen des I-Bits im Status-Register SREG des AVR's. Praktisch geschieht dies durch die Funktion sei(). Gesperrt werden Interrupts durch die Funktion cli().
Der ATmega8 verfügt über die im folgenden aufgelisteten Interruptquellen (Auszug aus iom8.h):
#define SIG_INTERRUPT0 _VECTOR(1) | Ereignis an INT0 (siehe unten) |
#define SIG_INTERRUPT1 _VECTOR(2) | Ereignis an INT1 (siehe unten) |
#define SIG_OUTPUT_COMPARE2 _VECTOR(3) | siehe Timer/PWM |
#define SIG_OVERFLOW2 _VECTOR(4) | Überlauf von Timer/Counter2 (siehe Timer) |
#define SIG_INPUT_CAPTURE1 _VECTOR(5) | (habe ich noch nicht verwendet) |
#define SIG_OUTPUT_COMPARE1A _VECTOR(6) | siehe Timer/PWM |
#define SIG_OUTPUT_COMPARE1B _VECTOR(7) | siehe Timer/PWM |
#define SIG_OVERFLOW1 _VECTOR(8) | Überlauf von Timer/Counter1 (siehe Timer) |
#define SIG_OVERFLOW0 _VECTOR(9) | Überlauf von Timer/Counter0 (siehe Timer) |
#define SIG_SPI _VECTOR(10) | siehe SPI |
#define SIG_UART_RECV _VECTOR(11) | USART hat Zeichen empfangen (siehe USART) |
#define SIG_UART_DATA _VECTOR(12) | USART data register empty (siehe USART) |
#define SIG_UART_TRANS _VECTOR(13) | USART hat Transmission abgeschlossen (siehe USART) |
#define SIG_ADC _VECTOR(14) | ADC hat Konvertierung abgeschlossen (siehe ADC) |
#define SIG_EEPROM_READY _VECTOR(15) | (habe ich noch nicht verwendet) |
#define SIG_COMPARATOR _VECTOR(16) | (habe ich noch nicht verwendet) |
#define SIG_2WIRE_SERIAL _VECTOR(17) | (habe ich noch nicht verwendet) |
#define SIG_SPM_READY _VECTOR(18) | (habe ich noch nicht verwendet) |
Man sieht, dass die Interrupts in einer Vektortabelle bestehend aus 18
Vektoren zusammengefasst sind. Der Inhalt der einzelnen Vektoren entspricht
einem Zeiger auf die entsprechende Routine, welche beim Auslösen der Interrupts
ausgeführt werden soll.
Beim Auslösen eines Interrupts unterbricht der Mikrocontroller automatisch das
aktuelle Programm und lädt den Programmzähler und den Prozessorstatus auf den
Stack. Dann startet die vom Programmierer festgelegte ISR
(Interrupt-Service-Routine). Nach Abschluss der Bearbeitung werden die Daten
wieder vom Stack herunter geladen und der normale Programmablauf fortgesetzt.
Hat man für den gewünschten Interrupt über das entsprechende Bit aktiviert
und zusätzlich Interrupts noch global erlaubt, muss noch eine entsprechende
Interrupt-Service-Routine (ISR) geschrieben werden. Ist diese nicht vorhanden,
wird das Verhalten des MC unberechenbar. Normalerweise wird ein Reset
ausgeführt.
Es gibt zwei Möglichkeiten eine solche ISR zu definieren:
SIGNAL ( xyz ) {
//xyz entspricht einem Interruptvektor, z.B.
SIG_INTERRUPT0
Anweisung ( ); //beliebige Anweisung
}
oder alternativ:
INTERRUPT ( xyz ) {
Anweisung ( );
}
Der Unterschied zwischen beiden ISR's besteht im wesentlichen darin, dass bei
betreten SIGNAL Interrupts global deaktiviert werden, bis die ISR fertig
ausgeführt ist. Weitere Interrupts werden somit nicht erkannt, können allerdings
den Programmablauf auch nicht stören.
Bei INTERRUPT bleiben Interrupts weiterhin aktiviert, somit kann während des
Ausführens einer ISR bereits ein weiterer Interrupt erkannt und bearbeitet
werden. Diese Option sollte mit Vorsicht benutzt werden! Generell empfiehlt sich
die Verwendung von SIGNAL.
Zu finden sind beide Funktionen in den Headerdateien
signal.h bzw. interrupt.h im Verzeichnis von
WinAVR.
Ausgenommen der beiden nachfolgenden Interruptquellen habe ich die Interrupts jeweils schon in dem dazu passenden Kapitel erläutert.
Mit den Eingängen INT0 und INT1 verfügt der AVR über die Möglichkeit, auch
externe Interrupts zu zulassen. Hervorzuheben ist hierbei, das es vollkommen
egal ist, ob man die beiden PINS als Ein- oder Ausgänge definiert. Somit ist es
sogar möglich, Software-Interrupts zu erzeugen. Die externen können auf
steigende bzw. fallende Flanken getriggert werden. Es besteht zusätzlich die
Möglichkeit, den Controller mit ihrer Hilfe "aufzuwecken", falls er sich in
einem der Sleep-Modi befindet. Dieses hat jedoch für mein Projekt keinen Belang.
Wie die Eingänge agieren sollen, legen die Bits im Register MCUCR (MCU
Control Register) fest.
INT1:
ISC11 |
ISC10 |
Beschreibung |
0 |
0 |
Low-Pegel erzeugt Interrupt |
0 |
1 |
Jeder Pegelwechsel erzeugt Interrupt |
1 |
0 |
Fallende Flanke generiert Interrupt |
1 |
1 |
Steigende Flanke generiert Interrupt |
INT0:
ISC01 |
ISC00 |
Beschreibung |
0 |
0 |
Low-Pegel erzeugt Interrupt |
0 |
1 |
Jeder Pegelwechsel erzeugt Interrupt |
1 |
0 |
Fallende Flanke generiert Interrupt |
1 |
1 |
Steigende Flanke generiert Interrupt |
Nun müssen nur noch die externen Interrupts
eingeschalten werden. Dies erfolgt durch setzen der Bits INT1 bzw.
INT0 im Register GICR (General Interrupt Control Register).
Wird an INT1 ein Interrupt ausgelöst, so wird zusätzlich im GIFR
(General Interrupt Flag Register) INTF1 (Interrupt Flag) gesetzt. Dieses
wird beim Ausführen der ISR (Interrupt-Service-Routine) automatisch wieder
gelöscht.
Analog würde an INT0 das INTF0-Flag gesetzt und wieder gelöscht werden.
Diese Flags können auch manuell durch schreiben einer logischen 1 in die
entsprechenden Bits gelöscht werden.
Eine Beispielanwendung kann man unter Programme finden.