(AVR) Interrupts

Aus TippvomTibb
Zur Navigation springen Zur Suche springen

Externe Interrupts können im Prinzip über/von jedem I/O-Pin ausgelöst werden. Wenn nicht ausdrücklich erwähnt beziehe ich mich bei meinen Aussagen auf den ATmega328P. Wenn jemand eine Info bezüglich eines anderen Prozessors benötigt, einfach unten in die Kommentare schreiben.

Zu beachten:

  • Ein Interrupt ist erst nach 4 Taktzyklen durch PCIF erkennbar. Bei 20 MHZ Takfrequenz sind das immerhin 200ns (4x50ns) oder 0,2us. Es wird aber kein Interrupt "verschluckt" wenn der Auslöser länger als einen Taktzyklus dauert. Kürzere Impulse lösen nicht garantiert einen Interrupt aus. Bei 0-Pegel-Interrupts muss zuerst der aktuelle Befehl abgearbeitet werden.
  • Nur INT0 (PD2) und INT1 (PD3) sind "vollwertige" Interrupts
  • Die 3 PCIx sind jeweils für eine Gruppe von I/O-Pins zuständig (PCI0 für PCINT7..0, PCI1 für PCINT14..8 , PCI2 für PCINT23..16).

During interrupts and subroutine calls, the return address program counter (PC) is stored on the stack. The stack is effectively allocated in the general data SRAM, and consequently the stack size is only limited by the total SRAM size and the usage of the SRAM. All user programs must initialize the SP in the reset routine (before subroutines or interrupts are executed). The stack pointer (SP) is read/write accessible in the I/O space. The data SRAM can easily be accessed through the five different addressing modes supported in the AVR architecture. The memory spaces in the AVR architecture are all linear and regular memory maps. A flexible interrupt module has its control registers in the I/O space with an additional global interrupt enable bit in the status register. All interrupts have a separate interrupt vector in the interrupt vector table. The interrupts have priority in accordance with their interrupt vector position. The lower the interrupt vector address, the higher the priority.

Allgemein

Die meisten Microcontroller besitzen einen oder mehrere I/O-Pins an denen abhaengig vom Zustandswechsel durch eine Programmunterroutine (Funktion) reagiert werden kann. Das ist natuerlich viel cleverer als in einer Schleife dauert nach einem Zusandswechsel zu "fragen" (Polling). Bei einem kleinen Microcontrollerprogramm, das nur das tut, mag das noch eine Ueberlegung wert sein. Sobald aber ein Programm ein wenig komplerxer wird ist interrupten angesagt.

Zum konfigurieren des Verhaltens bei einem Interrupt besitzen die Controller ein Reihe von Registern.

Beim ATmega328P als erstes, das SREG (AVR Status Register) und darin das Global Innterrupt Enable (Bit 7)

The global interrupt enable bit must be set for the interrupts to be enabled. The individual interrupt enable control is then

performed in separate control registers. If the global interrupt enable register is cleared, none of the interrupts are enabled independent of the individual interrupt enable settings. The I-bit is cleared by hardware after an interrupt has occurred, and is set by the RETI instruction to enable subsequent interrupts. The I-bit can also be set and cleared by the application with the SEI and CLI instructions, as described in the instruction set reference.

Abhaengig von den Bits BOOTRST, IVSEL, IVCE stehen die Interruptvektoren (siehe weiter unten) an einer anderen Postition im Programmspeicher.

Fuer die Interruptkontrolle sind folgende Register von Bedeutung:

  • EICRA External Interrupt Control Register A (0x69) -> Interrupt Sense Control INT0/INT1 (siehe folgende Tabelle)
  • EIMSK – External Interrupt Mask Register (0x1D/0x3D) -> External Interrupt Request INT0/INT1 Enable
  • EIFR – External Interrupt Flag Register (0x1C/0x3C) -> INTF0/INTF1: External Interrupt Flag 0/1
  • PCICR – Pin Change Interrupt Control Register (0x68) -> PCIE0/1/2: Pin Change Interrupt Enable 0/1/2
  • PCIFR – Pin Change Interrupt Flag Register (0x1B/0x3B) -> PCIF0/1/2: Pin Change Interrupt Flag 0/1/2
  • PCMSK2 – Pin Change Mask Register 2 (0x6D) -> PCINT23-PCINT16
  • PCMSK1 – Pin Change Mask Register 1 (0x6C) -> PCINT14-PCINT8 (kein PCINT15!)
  • PCMSK0 – Pin Change Mask Register 0 (0x6B) -> PCINT7-PCINT0

Im Gegensatz zu den PC-Interrupts, die nur auf einen Wechsel des Zustandes reagieren, koennen INT0/1 auch auch auf fallende und steigende Flanken und Zustaende reagieren.


INT1 INT0

Dies sind die "vollwertigen" Interrupts.

  • Können auf fallende und steigende Flanke und 0-Pegel triggern (-> EICRA)
  • Bei der 0-Pegel wird der Trigger solange ausgelöst bis wieder 1-Pegel anliegt.

An- und Ausschalten kann man die beiden Interrupts im EIMSK – External Interrupt Mask Register. Mask sollte vielleicht besser Enable heißen.

Das EICRA – External Interrupt Control Register A (0x69) dient ausschließlich (Interrupt Sense Control Bit0-3) der Triggereinstellung von INT0 und INT1

Interrupt 0/1 Sense Control
ISCx1 ISCx0 Verhalten
0 0 Ein LOW-Pegel loest einen Interrupt aus.
0 1 Ein Logik-Wechsel loest einen Interrupt aus.
1 0 Eine fallende Flanke loest einen Interrupt aus.
1 1 Ein steigende Flanke loest einen Interrupt aus.

oder kurz

ISCx1 ISCx0 INTx reagiert auf ...
0 0 0-Pegel
0 1 Pegelwechsel
1 0 fallende Flanke
1 1 steigende Flanke

Anwendungsbeispiel

Immer mal wieder habe ich gelesen, dass ein Taster am Interrupt eine schlechte Idee sei. Wegen Tastenprellen und so. Manche behaupten sogar es ginge gar nicht. Hier der Gegenbeweis. Funktioniert bei mir völlig zufriedenstellend. Hier benutze ich die ISR-Variante. Später werde ich weiter unten auch mal eine Gegenüberstellung mit der attachInterrupt-Variante ergänzen.

 1 #include <Arduino.h>
 2 #include <avr/interrupt.h> // fuer ISR Makro
 3 
 4 
 5 const byte ledPin = 5;
 6 volatile byte ledState = LOW;
 7 byte lastledState;
 8 
 9 const byte interruptPin = 3;
10 
11 ISR (INT1_vect){
12   EIFR|=(1<<INTF1); // Interrupt Flag zu Sicherheit schon mal loeschen
13   EIMSK&=~(1<<INT1); // Interrupt abschalten
14   ledState = !ledState;
15 }
16 
17 
18 void setup(){
19   Serial.begin(115200); // vielleicht brauche ich es 
20 
21   pinMode(ledPin, OUTPUT);  // LED-Anschluss auf Ausgang
22   digitalWrite(ledPin, LOW); // LED ausschalten
23 
24   pinMode(interruptPin, INPUT); // dies ist nicht zwingend notwendig, der Interrupt funktioniert auch im Output-Mode
25 
26   EICRA |= (1<<ISC11); // auf fallende Flanke reagieren (Der Taster tastet nach GND über einen Pullup-Widerstand.)
27   EIMSK |= (1<<INT1); // INT1-Interrupt einschalten
28 }
29 
30 void loop(){ 
31   if (lastledState!=ledState){
32     digitalWrite(ledPin, ledState);
33     delay(200);  // entprellen
34     EIFR|=(1<<INTF1); // Interruptflag (nochmal) loeschen
35     EIMSK|=(1<<INT1);  // Interrupt wieder einschalten
36     lastledState=ledState; // Veraenderung merken
37   }
38 }

Es sei hier bemerkt, dass innerhalb einer Interruptroutine (ISR) z.B. kein delay() und millis() funktioniert. Sobald man etwas nutzt was auch Interrupts benutzt ist der Ärger mit einprogrammiert. Am besten programmiert man nach dem Motto:

Nur das Notwendigste erledigen und direkt wieder raus, d.h. am besten nur Variablen setzen und keine Unterroutinen. 

Wenn ich zeitkritische Routinen geschrieben habe, bin ich auch schon mal auf Inline-Assembler ausgewichen, habe Taktzyklen der Opcodes gezählt und die Gesamtlaufzeit durch I/O-Pin-Trigger auf einem Oszi nachgemessen:-)

PCINT0-23

TODO

Request for Comments


Kommentar hinzufügen
TippvomTibb freut sich über alle Kommentare. Sofern du nicht anonym bleiben möchtest, registriere dich bitte oder melde dich an.