(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 werden lösen nicht garantiert einen Interrupt aus. Bei 0-Pegel-Interrupts muss zuerst der aktuelle Befehl abgearbeitet werden.
  • Nur INT0 und INT1 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).

INT1 INT0

  • 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

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 eines Interrupt z.B. kein delay() und millis() funktioniert. Sobald man etwas benutzt 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.