Arduino Interrupt – unterbricht

Interrupts sind Ereignisse, die während der Ausführung des Programms auftreten. In diesem Fall unterbricht der Arduino das Hauptprogramm, um sofort auf externe Ereignisse zu reagieren.

Arduino verwendet eine Reihe von Interrupts. Ein Beispiel ist der Interrupt erster Priorität, Reset. USART-, SPI-, TWI/I2C-Kommunikationsports. Timer/Zähler, Watchdog, Signalpegeländerungen an einzelnen Pins des Arduino-Geräts usw. Die folgende Liste fasst die Interrupts von ATmega328-basierten Geräten in der Reihenfolge ihrer Priorität zusammen.

1. Zurücksetzen
2. Externe Interrupt-Anfrage 0 (Pin D2) (INT0_vect)
3. Externe Interrupt-Anfrage 1 (Pin D3) (INT1_vect)
4. Pin-Änderungs-Interrupt-Anfrage 0 (Pins D8 bis D13) (PCINT0_vect)
5. Pin-Änderungs-Interrupt Anforderung 1 (Pins A0 bis A5) (PCINT1_vect)
6. Pin Change Interrupt Request 2 (Pins D0 bis D7) (PCINT2_vect)
7. Watchdog Timeout Interrupt (WDT_vect)
8. Timer/Counter2 Compare Match A (TIMER2_COMPA_vect)
9. Timer/Zähler2 Vergleichsübereinstimmung B (TIMER2_COMPB_vect)
10. Timer/Zähler2 Überlauf (TIMER2_OVF_vect)
11. Timer/Zähler1 Erfassungsereignis (TIMER1_CAPT_vect)
12. Timer/Zähler1 Vergleichsübereinstimmung A (TIMER1_COMPA_vect)
13. Timer/Zähler1 Vergleichsübereinstimmung B (TIMER1_COMPB_vect)
14. Timer/Zähler1-Überlauf (TIMER1_OVF_vect)
15. Timer/Zähler0-Vergleichsübereinstimmung A (TIMER0_COMPA_vect)
16. Timer/Zähler0-Vergleichsübereinstimmung B (TIMER0_COMPB_vect)
17. Timer/Zähler0-Überlauf (TIMER0_OVF_vect)
18. Serielle SPI-Übertragung abgeschlossen (SPI_STC_vect)
19 . USART Rx abgeschlossen (USART_RX_vect)
20. USART, Datenregister leer (USART_UDRE_vect)
21. USART, Tx abgeschlossen (USART_TX_vect)
22. ADC-Konvertierung abgeschlossen (ADC_vect)
23. EEPROM bereit (EE_READY_vect)
24. Analogkomparator (ANALOG_COMP_vect)
25. 2-Draht-serielle Schnittstelle (I2C) (TWI_vect)
26. Programmspeicher bereit speichern (SPM_READY_vect)

Unser Arduino-Programm kann mehrere Interrupt-Ereignisse haben. In diesem Fall kann jeweils nur ein Interrupt ausgeführt werden, die anderen Interrupts werden je nach Priorität nach dem aktuell laufenden Interrupt ausgeführt.

Interrupts sind standardmäßig aktiviert, können jedoch durch Aufruf der Funktion noInterrupts() vorübergehend deaktiviert werden. Diese Funktion kann nützlich sein, wenn wir uns in einem kritischen Abschnitt des Arduino-Codes befinden und nicht möchten, dass externe Ereignisse die Ausführung des Codes beeinträchtigen. Eine Ausnahme hiervon bildet der Reset-Interrupt, der nicht deaktiviert werden kann.

Wir nutzen es nur in begründeten Fällen, denn wenn wir die Unterbrechungen ausschalten, kann es zu Fehlfunktionen bestimmter Funktionen kommen. Wenn die Ausführung des kritischen Codeteils abgeschlossen ist, werden Interrupts durch Aufrufen der Funktion interrupts() aktiviert.

void loop()
{
  noInterrupts();
  // Ein kritischer Codeabschnitt hier
  interrupts();

  // weitere Code...
}

Externe Interrupts können nützlich sein, z.B. zum Verwalten von Tastendrücken und zum Überwachen von Encoderimpulsen, während andere Aufgaben im Hauptprogramm ausgeführt werden können.

Die Funktion attachmentInterrupt() ermöglicht eine Änderung der dedizierten Pins des Arduino, um ein Interrupt-Ereignis auszulösen. In der folgenden Tabelle sehen wir die Pins, die auf einigen Arduino-Boards für Interrupts verwendet werden können.

Arduino-BoardPins zum interrupt
Uno, Nano, Mini und andere 328-basierte Boards2, 3
Uno WiFi Rev.2, Nano Everyalle digitalen Pins
Mega, Mega2560, MegaADK2, 3, 18, 19, 20, 21 (  Pins 20 und 21  können nicht für Interrupts verwendet werden, während sie für die I2C-Kommunikation verwendet werden)
Micro, Leonardo, 32u4-basierte Boards0, 1, 2, 3, 7
Zeroalle digitalen Pins außer 4
MKR-Familie0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Nano 33 IoT2, 3, 9, 10, 11, 13, A1, A5, A7
Nano 33 BLE, Nano 33 BLE Sensealle Stifte
Duealle digitalen Pins
101alle digitalen Kontakte (Nur die Kontakte 2, 5, 7, 8, 10, 11, 12, 13 funktionieren mit der  CHANGE-  Funktion)

Mit Hilfe von Interrupt-Routinen in Arduino, sogenannten (ISR) Interrupt Service Routines, können wir diese Ereignisse einfach und sofort verarbeiten. Bei der Verwendung von Interrupt-Routinen sind jedoch einige Regeln zu beachten.

Ein ISR kann keine Parameter und keinen Rückgabewert haben. Wenn Daten vom Hauptprogramm an den ISR übertragen werden müssen oder wir einen Rückgabewert vom ISR erwarten, verwenden wir globale Variablen. Wir deklarieren diese globalen Variablen als volatile.

Die Interrupt-Routine muss so schnell wie möglich beendet werden, nur die wichtigsten Dinge werden im Rumpf des ISR erledigt, die zeitaufwändigen Aufgaben werden an das Hauptprogramm übergeben.

Vermeiden wir Zeitvorgaben. Für die Funktion von „delay()“ sind Interrupts erforderlich. Innerhalb eines ISR funktioniert dies nicht. Die Funktion „millis()“ verwendet auch Interrupts zum Zählen, sodass sie im Hauptteil der Interrupt-Routine niemals inkrementiert wird. micros() funktioniert zunächst, aber nach ein paar ms fängt es auch an, sich unregelmäßig zu verhalten. Da „delayMicroseconds()“ keinen Zähler verwendet, kann es problemlos funktionieren.

Die Funktion attachmentInterrupt() wird verwendet, um den externen Interrupt zu initialisieren. Die Funktion hat drei Parameter.

attachInterrupt(interrupt, ISR, mode);

Der erste Parameter ist die Interrupt-Nummer. Dies kann auf verschiedene Arten angegeben werden. Wir können die Interrupt-Nummer direkt eingeben, dies wird jedoch nicht empfohlen, da bei einigen Karten die externen Interrupts möglicherweise einem anderen Pin zugewiesen sind, sodass diese Lösung die Portabilität des Codes verringert. Eine weitere Möglichkeit ist die Angabe der Pin-Nummer, allerdings funktioniert dies nicht auf allen Arduino-Boards und ist daher auch nicht zu empfehlen.

Die richtige Lösung ist die Verwendung der Funktion digitalPinToInterrupt(pin). Als Parameter der digitalPinToInterrupt-Funktion geben wir die Nummer des für den Interrupt verwendeten Pins ein und es wird die Interrupt-Nummer des aktuellen Arduino-Boards zurückgegeben.

int pin = 2;
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

Der zweite Parameter der Funktion attachmentInterrupt() ist der ISR, der Name der Funktion, die den Interrupt verarbeitet.

Der dritte Parameter bestimmt, welche Phase der Signalpegeländerung des für den Interrupt verwendeten Pins einen Interrupt auslöst. In Arduino sind vier konstante Werte vordefiniert.

LOW, löst den Interrupt aus, wenn der Pin einen niedrigen Signalpegel hat.

CHANGE: Wenn sich der Signalpegel des Pins in irgendeiner Weise ändert, wird der Interrupt ausgelöst.

RISING, wenn der Pin von Low auf High wechselt, wird der Interrupt aktiviert.

FALLING, wenn der Pin von High auf Low wechselt.

Bei Arduino Due-, Zero- und MKR1000-Boards funktioniert auch der Wert „HIGH“, der Interrupt wird ausgelöst, wenn der Pin einen hohen Signalpegel hat.

Die Funktion „attachInterrupt()“ muss im Abschnitt „setup()“ aufgerufen werden, und wir benötigen außerdem einen ISR, um den Interrupt zu verarbeiten.

const byte buttonPin = 2;
volatile byte buttonState = LOW;

void setup()
{
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPressed, FALLING);
}

void loop()
{
  // Hauptprogramm....
}

void buttonPressed()
{
  buttonState = !buttonState;
}

Es kann vorkommen, dass wir in unserem Arduino-Programm das Ereignis, das den Interrupt ausgelöst hat, nicht mehr überwachen müssen oder den für den Interrupt verwendeten Pin auf andere Weise verwenden möchten. In einem solchen Fall können wir den angegebenen Interrupt ausschalten, um die Ressourcen des Arduino freizugeben.

Zu diesem Zweck verwenden wir die detachInterrupt()-Funktion von Arduino. Der von der bekannten Funktion digitalPinToInterrupt(pin) als Parameter zurückgegebene Wert bestimmt, welcher Interrupt ausgeschaltet werden soll.

detachInterrupt(digitalPinToInterrupt(pin))

Eine gängige Verwendungsmethode ist das Auslesen von Interrupts, beispielsweise Signalen von Drehgebern. Durch die Verwendung von Interrupts können wir Impulsverluste der Encodersignale im Arduino-Programm vermeiden.

Rotary Encoder Modul kompatibel mit Arduino.

KY-040 Drehwinkelgeber

Rotary Encoder Modul kompatibel mit Arduino.

Anschlüsse:
„GND“ = Masse-Anschluss
„+“ = 5V Stromversorgung
„SW“ = Interrupt für Push-Button
„DT“ = Encoder Pin B
„CLK“ = Encoder Pin A

werbung

Ein Encoder oder Inkrementalgeber ist ein Sensor, der an seinen Ausgängen Impulse ausgibt, wenn sich seine Achse dreht. Die Anzahl der Impulse ist proportional zum Grad der Winkeldrehung und der Auflösung des Encoders.

Auf der Welle des mechanischen Inkrementalgebers wird eine Encoderscheibe platziert, dies ist der mittlere, gemeinsame Ausgang des Drehgebers, diesen Schenkel verbinden wir mit GND. Die Encoderscheibe ist eine Lochscheibe, die Pins „A“ und „B“ des Encoders kommen mit dieser Encoderscheibe in Kontakt.

Schematische Darstellung des Encoders

Wenn die Encoderkontakte „A“ und „B“ während der Drehung die Encoderscheibe berühren, werden zwei Rechtecksignale erzeugt. Diese Signale sind relativ zueinander versetzt, da ein Kontakt das Encoderrad vor dem anderen berührt.

Wenn der Encoder im Uhrzeigersinn gedreht wird, wird Pin „A“ vor Pin „B“ mit dem Zifferblatt verbunden. Wenn der Pin „A“ auf einen niedrigen Signalpegel wechselt, befindet sich der Pin „B“ immer noch auf einem hohen Signalpegel. Wenn also Pin „A“ den Pegel ändert, befindet sich „B“ immer auf dem entgegengesetzten Signalpegel.

Encoder-Ausgangsquadratursignal für Rechtsdrehung

Wenn der Drehgeber gegen den Uhrzeigersinn gedreht wird, berührt Stift „B“ zuerst das Encoderrad. In diesem Fall ist Kontakt „A“ bei Pegeländerung gleich dem Signalpegel von Kontakt „B“.

Encoder-Ausgangsquadratsignal für Drehung gegen den Uhrzeigersinn

Wenn wir die Abfolge der Signalpegeländerungen der Kontakte A und B beobachten, können wir die Drehrichtung des Encoders bestimmen.

Verbinden wir einen KY-040-Drehgeber mit dem Arduino UNO, basierend auf dem Bild unten. Die Ausgänge „A“ und „B“ („DT“ und „CLK“) des KY-040 Encoders werden mit einem 10k-Widerstand auf der Platine auf die Versorgungsspannung hochgezogen.

Anschließen des KY-040-Encoders an Arduino Uno

Laden wir dann den folgenden Code auf das Arduino UNO hoch. Ich werde die Funktionsweise des Codes nicht separat beschreiben, ich habe in der Skizze viele Kommentare abgegeben.

// Arduino Uno-Pins zum Anschluss des Encoders
const char clkPin = 2;
const char dtPin = 3;
const char switchPin = 4;

// Im ISR verwendete Variablen müssen als volatile deklariert werden.
// Variable, die zum Zählen der Encoderdrehungen verwendet wird.
volatile int counter = 0;

// Variable, die den vorherigen Zustand des CLK-Pins des Encoders speichert
volatile int clkLastState = LOW;

// Eine Variable, die den aktuellen Zustand des CLK-Pins des Encoders speichert
volatile int clkCurrentState;


// Der ISR wird zur Verarbeitung von Encoder-ausgelösten Interrupts verwendet
void encoderHandler()
{  
  // Der aktuelle Zustand des CLK-Pins wird in der Variablen clkCurrentState gespeichert
  clkCurrentState = digitalRead(clkPin);
  
  // Wenn der niedrige Signalpegel des CLK-Pins hoch wird
  if((clkLastState == LOW) && (clkCurrentState == HIGH)) 
  {
    // und der Signalpegel des DT-Pin ist ebenfalls hoch
    if(digitalRead(dtPin) == HIGH)
    {
      // Der Encoder drehte sich gegen den Uhrzeigersinn
      // Wir reduzieren den Wert des Zählers
      counter--;
    }
    // wenn der DT-Pin niedrig ist 
    else
    {
      // Der Encoder drehte sich im Uhrzeigersinn
      // also erhöhen wir den Wert des Zählers
      counter++;
    }
    // Der aktuelle Wert des Zählers wird auf den seriellen Monitor geschrieben. 
    Serial.println(counter);
  }
  // Der aktuelle Zustand des CLK-Pins wird an die Variable clkLastState übergeben
  clkLastState = clkCurrentState;
}

void setup()
{
  Serial.begin(9600);
  
  // Die CLK- und DT-Pins des Encoders sind als Eingänge eingestellt. 
  // Die Encoderplatine verfügt über Pull-Up-Widerstände
  pinMode(clkPin, INPUT);
  pinMode(dtPin, INPUT);
  
  // Der SW-Pin des Encoder-Taster wird ebenfalls als Eingang eingestellt.
  // Hier verwenden wir den internen Pull-Up-Widerstand des Arduino
  pinMode(switchPin, INPUT_PULLUP);
  
  // Wir stellen die Interrupts ein, um die Drehung des Encoders zu überwachen
  attachInterrupt(digitalPinToInterrupt(clkPin), encoderHandler, CHANGE);
  attachInterrupt(digitalPinToInterrupt(dtPin), encoderHandler, CHANGE);
}

void loop()
{
 // Wir untersuchen den Zustand des Tasters 
 if(digitalRead(switchPin) == LOW)
  {
    // Wenn es gedrückt wurde, setzen wir den Zähler zurück
    counter = 0;
  }
}