16

Arduino Interrupt Steuer Ung Teil 1

Embed Size (px)

Citation preview

Page 1: Arduino Interrupt Steuer Ung Teil 1
Page 2: Arduino Interrupt Steuer Ung Teil 1

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

Page 3: Arduino Interrupt Steuer Ung Teil 1

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

Page 4: Arduino Interrupt Steuer Ung Teil 1

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!

Page 5: Arduino Interrupt Steuer Ung Teil 1

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?

Page 6: Arduino Interrupt Steuer Ung Teil 1

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:

Page 7: Arduino Interrupt Steuer Ung Teil 1

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

Page 8: Arduino Interrupt Steuer Ung Teil 1

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

Page 9: Arduino Interrupt Steuer Ung Teil 1

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

Page 10: Arduino Interrupt Steuer Ung Teil 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!?

Page 11: Arduino Interrupt Steuer Ung Teil 1

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.

Page 12: Arduino Interrupt Steuer Ung Teil 1

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!

Page 13: Arduino Interrupt Steuer Ung Teil 1

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!

Page 14: Arduino Interrupt Steuer Ung Teil 1

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!

Page 15: Arduino Interrupt Steuer Ung Teil 1

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.

Page 16: Arduino Interrupt Steuer Ung Teil 1

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