Verwendung von Interrupts mit Arduino

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

12. August 2015 von Nash Reilly

Optimieren Sie Ihren Arduino-Code mit Interrupts – die einfache Möglichkeit auf Echtzeit-Events zu reagieren!

Wir Unterbrechen Diese Sendung...

Wie sich herausstellt, gibt es einen großartigen (und wenig genutzten) Mechanismus, der in allen Arduinos eingebaut ist und ideal zum Überwachen von Echtzeit-Events ist. Dieser Mechanismus wird Interrupt genannt. Die Aufgabe eines Interrupts ist sicherzustellen, dass der Prozessor schnell auf wichtige Ereignisse reagiert. Wenn ein bestimmtes Signal erkannt wird, dann unterbricht (wie der Name andeutet) ein Interrupt was auch immer der Prozessor tut und führt Code aus, der entworfen wurde, auf jedweden extern dem Arduino zugeführten Impuls zu reagieren. Sobald der Code ausgeführt wurde, geht der Prozessor auf den ursprünglichen Punkt zurück und fährt dort fort, als wäre nichts geschehen!

Was wirklich toll daran ist: Es strukturiert Ihr System, schnell und effizient auf wichtige Ereignisse zu reagieren, die nicht leicht in Software vorauszuberechnen sind. Das Beste daran: Es macht den Prozessor für andere Dinge frei, während auf das tatsächliche Ereignis gewartet wird.

Button Interrupts

Lassen Sie uns mit einem einfachen Beispiel anfangen – die Verwendung eines Interrupts zum Überwachen eines Schalterdrucks. Zu Anfang machen wir eine Skizze, die sie wahrscheinlich schon einmal gesehen haben – die „Button“ Beispielskizze, die bei allen Arduinos dabei ist. (Sie finden dies im „Beispiel“ Skizzenbuch. Schauen Sie unter "File > Examples > Digital > Button".)

const int buttonPin = 2;     // the number of the pushbutton pin
const int ledPin =  13;      // the number of the LED pin

// variables will change:
int buttonState = 0;         // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);
}

void loop() {
  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed.
  // if it is, the buttonState is HIGH:
  if (buttonState == HIGH) {
    // turn LED on:
    digitalWrite(ledPin, HIGH);
  }
  else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
  }
}

Was Sie hier sehen, ist nichts Schockierendes oder Außergewöhnliches – alles was das Programm wieder und wieder tut, ist der Lauf durch eine `loop()` und Auslesen der Werte von `buttonPin`. Nehmen Sie für einen Moment an, dass Sie etwas anderes in der `loop()` tun wollten – etwas anderes als nur einen Pin auszulesen. Hier kommt der Interrupt ins Spiel. Anstatt den Pin die ganze Zeit zu überwachen, können wir die Arbeit den Pin zu überwachen an einen Interrupt übergeben und die `loop()` frei machen, damit es während dieser Zeit tut, wofür wir es brauchen! Der neue Code würde in etwa wie folgt aussehen:

const int buttonPin = 2;     // the number of the pushbutton pin
const int ledPin =  13;      // the number of the LED pin

// variables will change:
volatile int buttonState = 0;         // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);
  // Attach an interrupt to the ISR vector
  attachInterrupt(0, pin_ISR, CHANGE);
}

void loop() {
  // Nothing here!
}

void pin_ISR() {
  buttonState = digitalRead(buttonPin);
  digitalWrite(ledPin, buttonState);
}

Schleifen und Interrupt Modi

Sie werden hier einige Änderungen feststellen. Die erste und offensichtlichste ist, dass `loop()` keine Befehle enthält! Wir können dies deshalb tun, da alle Arbeit die vorher durch eine `if/else` Anweisung durchgeführt wurde nun durch die neue Funktion `pin_ISR()` gehandhabt wird. Diese Art von Funktion wird „Interrupt Dienstroutine“ genannt – die Aufgabe ist ein schneller Durchlauf und die Handhabung des Interrupt und dass der Prozessor wieder zum Hauptprogramm zurück kann (z.B. dem Inhalt von `loop()`). Es gibt einige wichtige Dinge zu beachten, wenn eine Interrupt Dienstroutine geschrieben wird, welche sich im obigen Code wiederspiegelt:

ISRs sollten kurz und gut sein. Sie wollen die Hauptschleife nicht zu lange aussetzen! Es gibt keine Eingabevariablen oder ausgegebenen Werte. Alle Änderungen müssen auf globalen Variablen gemacht werden.

Sie wundern sich wahrscheinlich – wie wissen wir, wann ein Interrupt loslegt? Was löst ihn aus? Die dritte Funktion in der `setup()` Routine setzt den Interrupt für das komplette System. Diese Funktion `attachInterrupt()` erfordert drei Parameter:

1. Den Interrupt Vektor, welcher festlegt welcher Pin einen Interrupt generieren kann. Es ist nicht die Nummer des Pins selbst – es ist tatsächlich eine Referenz, wo im Speicher der Arduino Prozessor nachsehen muss, um zu sehen, ob es einen Interrupt gibt. Ein bestimmter Platz in diesem Vektor entspricht einem bestimmten, externen Pin und nicht alle Pins können einen Interrupt auslösen. Auf dem Arduino Uno sind die Pins 2 und 3 in der Lage Interrupts auszulösen und sie entsprechen den Interrupt Vektoren 0 und 1. Für eine Übersicht der Pins, die als Interrupt Pins verfügbar sind, schauen Sie sich die Arduino Anleitung zu `attachInterrupt()` an.

2. Den Funktionsnamen der Interrupt Dienstroutine – dies legt den ablaufenden Code fest, wenn die Unterbrechungsvoraussetzungen erfüllt werden.

3. Den Interrupt Modus welcher festlegt, welche Pin-Aktion einen Interrupt auslöst. Der Arduino Uno unterstützt vier Interrupt Modi:

  • `RISING`, welches einen Interrupt auf einer ansteigenden Flanke des Interrupt Pin aktiviert,
  • `FALLING`, welches bei einer abfallenden Flanke aktiviert wird,
  • `CHANGE`, was auf jede Änderung des Wertes eines Interrupt Pins reagiert,
  • `LOW`, der immer auslöst, wenn der Pin geringe Spannung aufweist.

Nur zur Wiederholung – unsere Einstellung von `attachInterrupt()` versetzt uns in die Lage Interrupt Vektor 0/Pin 2 zu überwachen, auf Interrupts mit `pin_ISR()` zu reagieren und `pin_ISR()` aufzurufen, wann immer wir eine Änderung in Zustand von Pin 2 sehen.

Volatil – Nicht Schütteln!

Unsere ISR verwendet die Variable `buttonState` um Pin-Zustände abzulegen. Schauen Sie sich die Definition von `buttonState` an – anstelle des Typs `int` haben wir es als Typ `volatile int` . Was ist hier los? `volatile` ist ein C-Schlüsselwort, das auf Variablen angewendet wird. Das bedeutet, dass der Wert dieser Variable nicht komplett unter Programmkontrolle ist. Es reflektiert, dass sich der Wert von `buttonState` ändern könnte und in etwas ändern könnte, was das Programm selbst nicht vorausberechnen kann – in diesem Fall: Benutzer-Input.

Ein zusätzlicher Nutzen der das `volatile` Schlüsselwort hat, ist der Schutz vor einer ungewollten Compiler-Optimierung. Compiler, wie sich herausstellt, haben einige Verwendungszwecke neben dem Übersetzen von Quellcode in maschinenlesbare Sprache. Eine der Aufgaben ist das Herauskürzen von ungenutzten Quellcode-Variablen aus dem Maschinencode. Da die Variable `buttonState` nicht verwendet oder direkt `loop()` oder `setup()` Funktionen aufgerufen wird, besteht die Gefahr, dass der Compiler es als ungenutzte Variable entfernt. Das ist offensichtlich falsch – wir brauchen die Variable. Das `volatile` Schlüsselwort sagt als Nebenwirkung dem Compiler, ruhig zu bleiben und an der Variable festzuhalten – es ist kein Dicke-Finger-Fehler!

Zusammenfassend

Interrupts sind eine einfache Möglichkeit Ihr System besser auf zeitempfindliche Aufgaben reagieren zu lassen. Sie haben zudem den zusätzlichen Nutzen, Ihre Haupt- `loop()` frei zu machen, für einen Fokus auf Primäraufgaben im System. (Aus meiner Sicht macht dies meinen Code etwas organisierter, wenn ich ihn verwende – es ist einfacher zu erkennen, wofür der Hauptteil des Codes entworfen wurde, während Interrupts periodische Ereignisse handhaben.) Das hier gezeigte Beispiel ist nur der grundlegendste Fall zur Nutzung eines Interrupt – Sie können diese zum Auslesen von I2C-Geräten verwenden, zum Senden oder Empfang von Funkdaten oder sogar einen Motor starten oder stoppen.