Sie sind auf Seite 1von 16

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 arduino@erik-bartmann.de

Updates
15.01.2012 Volatile Erklrung 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 mchte in diesem Arduino-AddOn ein paar Worte ber eine Technik
verlieren, die einen wichtigen Stellenwert in der Mikrocontrollerprogrammierung einnimmt.
Meistens wird dieser Themenbereich fr Anfnger ausgespart und ich habe ihn einfach aus
Platzgrnden nicht in mein Buch mit aufnehmen knnen. Die meisten anfnglichen 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 mchte ich etwas ber Interrupts erzhlen. Dieser Begriff bedeutet bersetzt
Unterbrechung. Wir unterscheiden zwischen
Externen Interrupts
Internen Interrupts
Teil 1 beschftigt sich ausschlielich 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 Ausfhren 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 bercksichtigt, die es auszuwerten gilt. Je mehr dieser Aufgaben
die loop-Funktion belasten, desto langsamer wird die Ausfhrung erfolgen. Dieses Verfahren
wird Polling genannt und beschreibt eine programmtechnische Methode, den Status eines bzw.
mehrerer Sensoren zyklisch abzufragen. Das birgt jedoch einen groen Nachteil in sich. Ich hatte
es gerade erwhnt, dass diese rollierende Abfrage u.U. recht zeitkritisch sein kann. Sollen aber
z.B. die abgegebenen Impulse eines Sensors gezhlt werden, dann kann der ein oder andere
Impuls schon mal unter den Tisch fallen, wenn das Polling gerade mit einem anderen Sensor
beschftigt ist.
Nehmen wir z.B. eine angeschlossene Tastatur an einem PC. Die Ausfhrung der Programme
erfolgt in hnlicher Weise, wobei das Windows Betriebssystem in einer gigantischen
Endlosschleife alle Programme ausfhrt und auch z.B. auf Maus- bzw. Tastatureingaben
entsprechend reagieren muss. Da sind wir schon beim Thema. Die Abfrage der Tastatur wrde
bei einer fortlaufenden berprfung 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 fr 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 gedrckt, dann wird
die besagte Endlosschleife schn regelmig durchlaufen. Drckt jetzt irgendjemand auch nur
eine Taste, dann wird ein externer Interrupt ausgelst. Dieser veranlasst die regulre
Ausfhrung der Schleife zu einer Unterbrechung und sagt quasi: Hey Betriebssystem, da wurde
eine Taste gedrckt. Schau mal nach, welche das ist und reagiere entsprechend darauf.
Anschlieend 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
Tastendrcke registrieren, wenn er nicht stndig 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 gedrckt, wird sofort eine Unterbrechung ausgelst und zu einer
Unterbrechungsroutine (Neben-Thread) verzweigt. Sie erledigt alles Notwendige und springt
nach ihrer Abarbeitung zum Haupt-Thread zurck. In diesem Beispiel ist der Haupt-Thread mit
der Abarbeitung des Steps 2 beschftigt, als der Interrupt um die Ecke kommt. Bevor jetzt zum
Neben-Thread verzweigt wird, muss noch die Ausfhrungsadresse 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
zurckgekehrt, 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 wre nichts
Auergewhnliches passiert.
Bei einem Thread handelt es sich um einen Ausfhrungsstrang, der eine
Abarbeitungsreihenfolge mehrerer Befehle reprsentiert. Kommen wir jetzt jedoch wieder
zurck 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 berprft
}

Der Sketch steuert 5 LEDs 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 gedrckt 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 fr 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 gewnschte Verhalten der Schaltung bzw. des Codes.




Bauteile:
- LED rot 5x
- LED grn 1x
- 330 6x
- 10 K 1x
- Mikrotaster 1x
- Breadboard 1x
- Steckbrcken
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 verhlt und jetzt das gewnschte 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 knnen erkennen, dass in der loop-Funktion lediglich die Ansteuerung der besagten 5 LEDs
erfolgt und in keinster Weise der Taster abgefragt wird. Du knntest Dich jetzt natrlich fragen,
wann die Sketch-Verarbeitung an einen Punkt gert, 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 bercksichtigt, 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 grne LED mit seinem Status versorgt.
Doch das ist wie wir das ja schon besprochen haben in diesem Fall nicht gewnscht.
Innerhalb der setup-Funktion wird ein Interrupt vorbereitet bzw. scharf geschaltet, der ein paar
Argumente besitzt, die der genaueren Betrachtung bedrfen. 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 verfgt, mssen diese unterscheidbar sein.
Deswegen bekommt jeder Interrupt eine eigene Nummer zugewiesen. Betrachten wir doch
einfach einmal die beiden verfgbaren 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 natrlich Interrupt INT 0. Deswegen lautet der Sketch-Code:
...
int interruptNumber = 0; // Interrupt-Nummer
...
...
...
attachInterrupt(interruptNumber, interruptroutine, CHANGE);
...

Andere Arduino-Boards haben ggf. abweichende oder verfgen ber zustzliche Interrupts. Das
spielt aber in diesem Fall keine Rolle, denn es geht um das Prinzip bzw. die Funktionsweise.
Interrupt-Funktion
Wird ein externer Interrupt ausgelst, 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 ausgelst werden soll.

Das ist natrlich richtig, doch wir haben noch eine weitere Mglichkeit 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 knnen.






Hast Du nicht eben gesagt, der Interrupt wird ausgelst, wenn
der Taster gedrckt 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 Erklrung
LOW Der Interrupt wird ausgelst, wenn der Pegel
am Interrupt-Pin LOW ist.
CHANGE Der Interrupt wird ausgelst, wenn der Pegel
am Interrupt-Pin seinen Pegel wechselt.
LOW HIGH
HIGH LOW
RISING Der Interrupt wird ausgelst, wenn der Pegel
am Interrupt-Pin seinen Pegel von
LOW HIGH wechselt.
FALLING Der Interrupt wird ausgelst, wenn der Pegel
am Interrupt-Pin seinen Pegel von
HIGH LOW wechselt.

Fr unser Sketch-Beispiel lautet der Code:

attachInterrupt(interruptNumber, interruptroutine, CHANGE);

was bedeutet, dass bei jedem Drcken, bzw. Loslassen des Tasters der Interrupt ausgelst wird,
da ich den CHANGE-Modus gewhlt habe. Die grne LED leuchtet natrlich nur dann, wenn der
Taster gedrckt 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 knnte
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 temporren 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
auerhalb 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
Schlsselwort 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 spter 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...
Habs 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 fr 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 verhlt. 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 merkwrdig verhlt, was mit dem
Prellen des Tasters in Verbindung steht. Schaue Dir dazu das Projekt 4 in meinem Buch an. Dort
wird das merkwrdige 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 wre, anhand einer Ermittlung der erfolgten Umdrehungen der
Rder. 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 Rder
langsamer erfolgt. ber angeschlossene Sensoren, die die Impulse der einzelnen Rder
registrieren, kann eine genauere Positionierung des ArduBots erfolgen. Die Impulse werden
natrlich ber eine Interrupt-Steuerung ausgewertet, so dass kein einziger Impuls eventuell
unter den Tisch fllt, weil die loop-Schleife vielleicht gerade mit anderen Dingen beschftigt ist
und im wahrsten Sinne des Wortes keine Zeit hatte. Doch dazu spter mehr.

Viel Spa beim Frickeln