Upload
amar-amour
View
78
Download
3
Embed Size (px)
Citation preview
Arduino Interrupt-Handling (Teil 1) Seite 2
Scope Interrupt-Handling (Teil 1)
Version 1.1
Created 29.12.2011
Autor Erik Bartmann
Internet http://www.erik-bartmann.de
Email [email protected]
Updates
15.01.2012 Volatile Erklärung erweitert
Arduino Interrupt-Handling (Teil 1) Seite 3
Inhalt Interrupt-Handling (Teil 1) ............................................................................................................................. 4
Interrupt-Nummer..................................................................................................................................... 9
Interrupt-Funktion ..................................................................................................................................... 9
Interrupt-Modus ...................................................................................................................................... 10
Unberechenbare Variablen ..................................................................................................................... 11
Anwendungsbeispiel ............................................................................................................................... 16
Arduino Interrupt-Handling (Teil 1) Seite 4
Interrupt-Handling (Teil 1) Liebe Freunde, ich möchte in diesem Arduino-AddOn ein paar Worte über eine Technik
verlieren, die einen wichtigen Stellenwert in der Mikrocontrollerprogrammierung einnimmt.
Meistens wird dieser Themenbereich für Anfänger ausgespart und ich habe ihn einfach aus
Platzgründen nicht in mein Buch mit aufnehmen können. Die meisten anfänglichen Projekte
kommen ohne eine Interrupt-Steuerung aus. Aber ich hatte euch ja angedroht, dass ich mit
einigen Zusatzkapiteln um die Ecke kommen werde, die sicherlich sehr interessant sind.
In diesem AddOn möchte ich etwas über Interrupts erzählen. Dieser Begriff bedeutet übersetzt
Unterbrechung. Wir unterscheiden zwischen
Externen Interrupts
Internen Interrupts
Teil 1 beschäftigt sich ausschließlich mit externen Interrupts. Warum aber sollte an irgendeiner
Stelle in einem Programm bzw. Sketch eine Unterbrechung erfolgen? Das macht ja auf den
ersten Blick vielleicht überhaupt keinen Sinn. Ein Sketch soll arbeiten, bis der Arzt kommt und
sich nicht zur Ruhe begeben. Die Abarbeitung erfolgt in einer Endlosschleife, die loop-Funktion
genannt wird und nach dem einmaligen Ausführen der setup-Funktion immer und immer
durchlaufen wird (Siehe Seite 153 im Arduino Buch). In der loop-Funktion werden jetzt z.B. alle
zu überwachenden Sensoren berücksichtigt, die es auszuwerten gilt. Je mehr dieser Aufgaben
die loop-Funktion belasten, desto langsamer wird die Ausführung erfolgen. Dieses Verfahren
wird Polling genannt und beschreibt eine programmtechnische Methode, den Status eines bzw.
mehrerer Sensoren zyklisch abzufragen. Das birgt jedoch einen großen Nachteil in sich. Ich hatte
es gerade erwähnt, dass diese rollierende Abfrage u.U. recht zeitkritisch sein kann. Sollen aber
z.B. die abgegebenen Impulse eines Sensors gezählt werden, dann kann der ein oder andere
Impuls schon mal unter den Tisch fallen, wenn das Polling gerade mit einem anderen Sensor
beschäftigt ist.
Nehmen wir z.B. eine angeschlossene Tastatur an einem PC. Die Ausführung der Programme
erfolgt in ähnlicher Weise, wobei das Windows Betriebssystem in einer gigantischen
Endlosschleife alle Programme ausführt und auch z.B. auf Maus- bzw. Tastatureingaben
entsprechend reagieren muss. Da sind wir schon beim Thema. Die Abfrage der Tastatur würde
bei einer fortlaufenden Überprüfung aller Tasten schon einige Zeit in Anspruch nehmen. Diese
Vorgehensweise ist aber nicht notwendig, denn der User sitzt nicht vor seinem Rechner und
hackt pausenlos in einer wahnsinnigen Geschwindigkeit Texte ein. Man hat sich für eine andere
Strategie entschieden. Die Tastatur wird nicht ununterbrochen abgefragt!
Arduino Interrupt-Handling (Teil 1) Seite 5
Das Ganze ist Unterbrechungsgesteuert. Wird keine Taste auf der Tastatur gedrückt, dann wird
die besagte Endlosschleife schön regelmäßig durchlaufen. Drückt jetzt irgendjemand auch nur
eine Taste, dann wird ein externer Interrupt ausgelöst. Dieser veranlasst die reguläre
Ausführung der Schleife zu einer Unterbrechung und sagt quasi: „Hey Betriebssystem, da wurde
eine Taste gedrückt. Schau mal nach, welche das ist und reagiere entsprechend darauf.
Anschließend kannst Du ja mit Deinem Kram weitermachen.“ In der folgenden Grafik habe ich
das einmal darzustellen versucht.
Stopp mal kurz! Wie soll dann der Rechner meine
Tastendrücke registrieren, wenn er nicht ständig auf die
Tastatur schaut?
Arduino Interrupt-Handling (Teil 1) Seite 6
Auf der linken Seite seht Ihr den Hauptprogramm (Haupt-Thread) mit seiner Endlosschleife.
Wird jetzt eine Taste gedrückt, wird sofort eine Unterbrechung ausgelöst und zu einer
Unterbrechungsroutine (Neben-Thread) verzweigt. Sie erledigt alles Notwendige und springt
nach ihrer Abarbeitung zum Haupt-Thread zurück. In diesem Beispiel ist der Haupt-Thread mit
der Abarbeitung des Steps 2 beschäftigt, als der Interrupt um die Ecke kommt. Bevor jetzt zum
Neben-Thread verzweigt wird, muss noch die Ausführungsadresse des Haupt-Threads gesichert
werden. Sie wird auf dem Stack (Stapel-Speicher) abgelegt, der ein spezieller Speicherbereich im
RAM ist und nach dem LIFO-Prinzip (Last-In-First-Out) arbeitet. Das zuletzt auf den Stapel
gelegte Element, wird auch zuerst wieder entnommen. Erst jetzt werden die Steps a bis c der
Interruptverarbeitung in Angriff genommen und nach Beendigung zum Haupt-Thread
zurückgekehrt, in dem die zuvor auf den Stack gelegte Adresse wieder entnommen wird und an
der Stelle weiter gemacht wird, bevor der Interrupt auftrat. Ganz so, als wäre nichts
Außergewöhnliches passiert.
Bei einem Thread handelt es sich um einen Ausführungsstrang, der eine
Abarbeitungsreihenfolge mehrerer Befehle repräsentiert. Kommen wir jetzt jedoch wieder
zurück zu unserem Arduino. Als Beispiel sehen wir uns den folgenden Sketch an.
int ledPin[5] = 8, 9, 10, 11, 12; // LED-Array
int tasterPin = 2; // Taster
int tasterLED = 6; // Taster-LED
void setup()
for(int i = 0; i < 5; i++)
pinMode(ledPin[i], OUTPUT);
pinMode(tasterLED, OUTPUT);
pinMode(tasterPin, INPUT);
void loop()
for(int i = 0; i < 5; i++)
digitalWrite(ledPin[i], HIGH);
delay(500);
digitalWrite(ledPin[i], LOW);
delay(500);
digitalWrite(tasterLED, digitalRead(tasterPin)); // Erst jetzt wird der Sensor überprüft
Der Sketch steuert 5 LED’s der Reihe nach an, die in einer Art Lauflicht nacheinander kurz
leuchten sollen. Des Weiteren soll aber noch auf einen Tastendruck zeitnah reagiert werden,
der zur optischen Kontrolle eine separate LED ansteuert. Der Schaltungsaufbau in Fritzing dazu
schaut wie folgt aus:
Arduino Interrupt-Handling (Teil 1) Seite 7
Das Problem, was sich uns darstellt, ist folgendes. Wir wollen ja eigentlich, dass die Taster-LED
unmittelbar dann aufleuchtet, wenn der Taster gedrückt wird. Das ist aber mit dem
vorliegenden Sketch nicht realisierbar. Warum? Ganz einfach. Der folgende Sketchabschnitt
for(int i = 0; i < 5; i++)
digitalWrite(ledPin[i], HIGH);
delay(500);
digitalWrite(ledPin[i], LOW);
delay(500);
nimmt für die Abarbeitung schon eine ganze Zeit in Anspruch. Es sind mehrere delay-Aufrufe
hintereinander platziert, so dass die Abfrage des Tasters
digitalWrite(tasterLED, digitalRead(tasterPin));
bzw. die Ansteuerung der LED nur ganz kurz nach Beendigung der for-Schleife erfolgt. Das ist
jedoch nicht das gewünschte Verhalten der Schaltung bzw. des Codes.
Bauteile:
- LED rot 5x
- LED grün 1x
- 330 Ω 6x
- 10 KΩ 1x
- Mikrotaster 1x
- Breadboard 1x
- Steckbrücken
Arduino Interrupt-Handling (Teil 1) Seite 8
Ok, dann wenden wir uns dem folgenden Sketch-Code zu. Der Verdacht liegt nahe, dass es sich
damit wohl anders verhält und jetzt das gewünschte Verhalten erfolgt.
int ledPin[5] = 8, 9, 10, 11, 12; // LED-Array
int tasterPin = 2; // Taster
int tasterLED = 6; // Taster-LED
int interruptNumber = 0; // Interrupt-Nummer
void setup()
for(int i = 0; i < 5; i++)
pinMode(ledPin[i], OUTPUT);
pinMode(tasterLED, OUTPUT);
pinMode(tasterPin, INPUT);
attachInterrupt(interruptNumber, interruptroutine, CHANGE);
void loop()
for(int i = 0; i < 5; i++)
digitalWrite(ledPin[i], HIGH);
delay(500);
digitalWrite(ledPin[i], LOW);
delay(500);
void interruptroutine()
digitalWrite(tasterLED, digitalRead(tasterPin));
Wir können erkennen, dass in der loop-Funktion lediglich die Ansteuerung der besagten 5 LED’s
erfolgt und in keinster Weise der Taster abgefragt wird. Du könntest Dich jetzt natürlich fragen,
wann die Sketch-Verarbeitung an einen Punkt gerät, dass der Taster doch irgendwann einmal
abgefragt wird. Nun ja, da gibt es eine vielversprechende Funktion, die sich interruptroutine
nennt. In ihr wird der Taster hinsichtlich des Status berücksichtigt, der dann unmittelbar die
Taster-LED ansteuert.
void interruptroutine()
digitalWrite(tasterLED, digitalRead(tasterPin));
Die Frage ist nur, wann und wie wird diese Funktion aufgerufen? Eigentlich sollte sie ja
kontinuierlich aufgerufen werden, so dass der Taster die grüne LED mit seinem Status versorgt.
Doch das ist – wie wir das ja schon besprochen haben – in diesem Fall nicht gewünscht.
Innerhalb der setup-Funktion wird ein Interrupt vorbereitet bzw. scharf geschaltet, der ein paar
Argumente besitzt, die der genaueren Betrachtung bedürfen. Um den besagten Interrupt zu
aktivieren, wird die attachInterrupt-Funktion verwendet.
attachInterrupt(interruptNumber, interruptroutine, CHANGE);
Interrupt-Nummer Funktion Modus
Arduino Interrupt-Handling (Teil 1) Seite 9
Was bedeuten aber die 3 Argumente, die der Funktion übergeben werden?
Interrupt-Nummer
Da ein Mikrocontroller über mehrere Interrupts verfügt, müssen diese unterscheidbar sein.
Deswegen bekommt jeder Interrupt eine eigene Nummer zugewiesen. Betrachten wir doch
einfach einmal die beiden verfügbaren externen Interrupts an den digitalen Pins 2 und 3.
Was Du hier auf keinen Fall verwechseln solltest, sind die Pin-Nummer bzw. Interrupt-Nummer.
Auf unser Beispiel bezogen, bei dem der Taster über einen Pulldown-Widerstand an Pin 2
angeschlossen ist, nutzen wir natürlich Interrupt INT 0. Deswegen lautet der Sketch-Code:
...
int interruptNumber = 0; // Interrupt-Nummer
...
...
...
attachInterrupt(interruptNumber, interruptroutine, CHANGE);
...
Andere Arduino-Boards haben ggf. abweichende oder verfügen über zusätzliche Interrupts. Das
spielt aber in diesem Fall keine Rolle, denn es geht um das Prinzip bzw. die Funktionsweise.
Interrupt-Funktion
Wird ein externer Interrupt ausgelöst, dann hat das ja einen Grund bzw. es steckt eine Absicht
dahinter, was denn dann passieren soll. Zu diesem Zweck wird der attachInterrupt-Funktion ein
weiteres Argument übergeben. Es handelt sich dabei um den Funktionsnamen der Funktion, die
aufgerufen werden soll, wenn der Interrupt feuert.
INT 0 INT 1
Arduino Interrupt-Handling (Teil 1) Seite 10
void setup()
...
attachInterrupt(interruptNumber, interruptroutine, CHANGE);
void loop()
...
void interruptroutine()
digitalWrite(tasterLED, digitalRead(tasterPin));
Beachte, dass die Angabe des Funktionsnamens ohne das runde Klammerpaar erfolgt!
Interrupt-Modus
Der Interrupt-Modus legt fest, wann der Interrupt ausgelöst werden soll.
Das ist natürlich richtig, doch wir haben noch eine weitere Möglichkeit die Sache etwas zu
differenzieren. So ein digitaler Pegel kann ein zeitliches Verhalten haben.
stetig den gleichen Pegel (LOW oder HIGH)
Pegelwechsel (von LOW nach HIGH bzw. von HIGH nach LOW)
Durch die Angabe des dritten Argumentes teilen wir dem Interrupt mit, wann er zu reagieren
hat. Aus diesem Grund sind 4 Konstanten vordefiniert, derer wir uns bedienen können.
Hast Du nicht eben gesagt, der Interrupt wird ausgelöst, wenn
der Taster gedrückt wird, also +5V an Pin 2 anliegen? Zu
diesem Zeitpunkt sollte der Interrupt doch feuern – oder!?
Arduino Interrupt-Handling (Teil 1) Seite 11
Konstante Pegelwechsel Erklärung
LOW Der Interrupt wird ausgelöst, wenn der Pegel am Interrupt-Pin LOW ist.
CHANGE Der Interrupt wird ausgelöst, wenn der Pegel am Interrupt-Pin seinen Pegel wechselt. LOW → HIGH HIGH → LOW
RISING Der Interrupt wird ausgelöst, wenn der Pegel am Interrupt-Pin seinen Pegel von LOW → HIGH wechselt.
FALLING Der Interrupt wird ausgelöst, wenn der Pegel am Interrupt-Pin seinen Pegel von HIGH → LOW wechselt.
Für unser Sketch-Beispiel lautet der Code:
attachInterrupt(interruptNumber, interruptroutine, CHANGE);
was bedeutet, dass bei jedem Drücken, bzw. Loslassen des Tasters der Interrupt ausgelöst wird,
da ich den CHANGE-Modus gewählt habe. Die grüne LED leuchtet natürlich nur dann, wenn der
Taster gedrückt wird.
Unberechenbare Variablen
Ein weiterer wichtiger Aspekt muss bei der Verwendung von Variablen innerhalb einer
Interrupt-Routine angesprochen werden. Aber warum ist das so? Wir deklarieren z.B. eine
Variable als global am Anfang des Sketches, die dann in allen Funktionen sichtbar ist und darauf
zugegriffen werden kann. Soweit so gut. Das stellt kein Problem dar, solange der Haupt-Thread,
also der eigentliche Sketch mit dieser Variablen arbeitet. Innerhalb einer Interrupt-Routine, die
ihren eignen Thread besitzt, sieht die Sache schon ganz anders aus. Theoretisch gesehen könnte
der Inhalt dieser Variablen quasi gleichzeitig im Haupt- als auch Neben-Thread modifiziert
werden.
Arduino Interrupt-Handling (Teil 1) Seite 12
Arbeitet der Mikrocontroller mit temporären Variablen, wie z.B. lokalen Variablen, werden
diese in der Regel in internen Speicherzellen, auch Register genannt, bearbeitet bzw. verwaltet.
Sie werden auch aus dem SRAM-Hauptspeicher geladen. Das funktioniert jedoch nur
reibungslos, wenn der Haupt-Thread die alleinige Kontrolle hat. Kommt jetzt noch ein Neben-
Thread wie z.B. in einer Interrupt-Routine dazu, kann das u.U. nicht mehr funktionieren. Es
kommt ggf. zu falschen Ergebnissen. Um dieses Problem zu beheben, gibt es einen Modifizierer
bei der Variablendeklaration, der dem Compiler mitteilt, dass die zu deklarierende Variable
außerhalb des Haupt-Threads modifiziert werden kann und er jegliche Art von Optimierung
vergessen soll. Mit Hilfe des Modifizierers wird sie also immer aus dem SRAM-Hauptspeicher
gelesen und wandert nicht zur Zwischenspeicherung in ein Register des Mikrocontrollers. Das
Schlüsselwort lautet volatile, was übersetzt unberechenbar bedeutet. Wir werden unseren
Sketch ein wenig modifizieren und den Zustand der anzusteuernden LED in eine Variable
auslagern, die dann später in der Interrupt-Routine modifiziert wird. Doch werfen wir zuvor
einen kurzen Blick auf dieses Szenario:
Neulich auf dem Arduino-Board...
Gib mir mal sofort den Wert
an der Speicherstelle 0815
Kommt sofort...
Hier ist die Nummer 5!
Arduino Interrupt-Handling (Teil 1) Seite 13
Ok, danke! Ich muss den
Wert verdauen und neu
erschaffen... Moment...
Hab’s gleich... Gnnn.
Ahhhhh....
Hey hey hey. Ich komme vom
Interrupt 0. Der braucht den
Wert an der Speicherstelle 0815
Kommt sofort...
Hier ist die Nummer 5!
Arduino Interrupt-Handling (Teil 1) Seite 14
So Kollege, hier ist der neue
Wert für Speicherstelle 0815 Bestens. Ich lege
ihn sofort ab!
Ach wie gut das ist, immer mit
den aktuellsten Daten versorgt
zu werden!
Arduino Interrupt-Handling (Teil 1) Seite 15
Doch nun zum versprochenen Sketch, der das geschilderte Szenario verhindert.
int ledPin[5] = 8, 9, 10, 11, 12; // LED-Array
int tasterPin = 2; // Taster
int tasterLED = 6; // Taster-LED
int interruptNumber = 0; // Interrupt-Nummer
volatile int statusLED = LOW; // LED-Status
void setup()
for(int i = 0; i < 5; i++)
pinMode(ledPin[i], OUTPUT);
pinMode(tasterLED, OUTPUT);
pinMode(tasterPin, INPUT);
attachInterrupt(interruptNumber, interruptroutine, RISING);
void loop()
for(int i = 0; i < 5; i++)
digitalWrite(ledPin[i], HIGH);
delay(500);
digitalWrite(ledPin[i], LOW);
delay(500);
void interruptroutine()
statusLED = !statusLED;
digitalWrite(tasterLED, statusLED);
Der Interrupt-Modus steht jetzt auf RISING, was bedeutet, dass nur auf die ansteigende Flanke
des Tasters reagiert wird. Die rot umrandete Zeile zeigt Dir die Variable statusLED, die nun mit
dem Zusatz volatile versehen wurde, so dass sie sich, wie schon besprochen verhält. Bei jedem
Tastendruck wird jetzt der Status der Variablen getoggelt, so dass die LED an- bzw. ausgeht. Du
wirst sicherlich feststellen, dass die LED sich manchmal recht merkwürdig verhält, was mit dem
Prellen des Tasters in Verbindung steht. Schaue Dir dazu das Projekt 4 in meinem Buch an. Dort
wird das merkwürdige Tasterverhalten genauestens beschrieben.
Experimentire doch ein wenig mit den unterschiedlichen Interrupt-Modi und analysiere das
Verhalten der LED.
Arduino Interrupt-Handling (Teil 1) Seite 16
Anwendungsbeispiel
Ein interessantes Anwendungsbeispiel ist die Modifikation meines im Buch beschriebenen
ArduBots. Die Motorsteuerung erfolgt im Moment nur auf Grundlage einer Zeitsteuerung und
nicht, wie es sicherlich besser wäre, anhand einer Ermittlung der erfolgten Umdrehungen der
Räder. Wie ich das schon im Buch beschrieben habe, gibt es Probleme mit der Genauigkeit,
wenn die angeschlossene Batterie ihre volle Ladung verliert und die Drehung der Räder
langsamer erfolgt. Über angeschlossene Sensoren, die die Impulse der einzelnen Räder
registrieren, kann eine genauere Positionierung des ArduBots erfolgen. Die Impulse werden
natürlich über eine Interrupt-Steuerung ausgewertet, so dass kein einziger Impuls eventuell
unter den Tisch fällt, weil die loop-Schleife vielleicht gerade mit anderen Dingen beschäftigt ist
und im wahrsten Sinne des Wortes keine Zeit hatte. Doch dazu später mehr.
Viel Spaß beim Frickeln