- -
- 100%
- +
Der Schaltplan
Der Schaltplan gleicht dem der Abfrage eines Tasters aus einem vorherigen Bastelprojekt:

Abb. 7: Der Schaltplan für das Toggeln der LED über den Taster
Der Schaltungsaufbau
Der Schaltungsaufbau gleicht dem aus dem Bastelprojekt 3 über die Abfrage des Tasterstatus. Mit diesen technischen Grundlagen wenden wir uns dem Sketch zu.
Der Arduino-Sketch
Der folgende Sketch wird immer dann den Status der angeschlossenen LED wechseln, wenn der Taster gedrückt wird. Hält man den Taster für längere Zeit gedrückt, ändert sich der Status nach dem Wechsel nicht mehr.
int tasterPin = 2; // Taster-Pin 2 int ledPin = 8; // LED-Pin 8 int ledStatus; // LED-Status int tasterStatusActual; // Speichert aktuellen Tasterstatus int prevTasterStatus; // Speichert letzten Tastersstatus int debouncedTasterStatus; // Speichert debounced Tasterstatus int debounceInterval = 50; // 50ms Intervall unsigned long lastDebounceTime = 0; // Zeit, wann LED-Status sich // geändert hat void setup( ) { pinMode(ledPin, OUTPUT); // Taster-Pin als Eingang pinMode(tasterPin, INPUT); // LED-Pin als Ausgang } void loop() { tasterStatusActual = digitalRead(tasterPin); // Tasterstatus lesen unsigned long currentTime = millis(); // Debounce-Zeit lesen if(tasterStatusActual != prevTasterStatus) lastDebounceTime = currentTime; if(currentTime - lastDebounceTime > debounceInterval){ if(tasterStatusActual != debouncedTasterStatus){ debouncedTasterStatus = tasterStatusActual; // LED toggeln, wenn Tasterstatus gleich HIGH-Pegel if(debouncedTasterStatus == HIGH) ledStatus = !ledStatus; } } digitalWrite(ledPin, ledStatus); // LED ansteuern prevTasterStatus = tasterStatusActual; // Letzten Tasterstatus sichern }
Schauen wir uns die Erklärungen zu diesem Sketch an.
Den Code verstehen
Ich möchte an diesem Beispiel zeigen, was ich über ein Warteschleifenverfahren realisiert habe. Zu Beginn der loop-Schleife wird immer der gerade vorherrschende Tastenpegel in die Variable tasterStatusActual eingelesen. Der gerade vorherrschende Zeitstempel seit Sketch-Start wird über die millis-Funktion in die Variable currentTime eingelesen. Wenn sich der aktuelle Tasterstatus vom vorherigen unterscheidet, wird die Debounce-Zeit auf den neuesten Stand gebracht. Diese wird in der folgenden Abfrage benötigt, um darüber zu entscheiden, ob das vorgegebene Intervall abgelaufen ist:
if(currentTime - lastDebounceTime > debounceInterval){...}
Ist das der Fall, wird der aktuelle Tasterstatus mit dem Debounced-Tasterstatus verglichen und erst wenn sie unterschiedlich sind, kommt es zur Abfrage, ob ein HIGH-Pegel zur Statusänderung vorliegt. Denn erst dann soll die LED in ihrem Status geändert werden, was über die Invertierung des Status in der Variablen ledStatus erfolgt:
if(debouncedTasterStatus == HIGH) ledStatus = !ledStatus;
Abschließend muss lediglich die LED über eben diese Variable ledStatus angesteuert werden:
digitalWrite(ledPin, ledStatus);
... und der aktuellen Tasterstatus in den vorherigen Status überführt werden:
prevTasterStatus = tasterStatusActual;
Das Spiel beginnt von vorn.
Troubleshooting
Falls die LED beim Tasterdruck nicht leuchtet beziehungsweise nicht toggelt, kann es mehrere Gründe dafür geben:
Die LED ist verpolt, also falsch eingesteckt worden. Erinnere dich an die beiden unterschiedlichen Anschlüsse einer LED mit Anode und Kathode.
Die LED ist vielleicht defekt und durch Überspannung aus vorausgegangenen Bastelprojekten durchgebrannt. Teste sie mit einem Vorwiderstand an einer 5V-Spannungsquelle.
Kontrolliere noch einmal die Verbindungen auf deinen Breadboard, in die du die LED beziehungsweise die Bauteile eingesteckt hast.
Überprüfe noch einmal den Sketch, den du in den Editor der Entwicklungsumgebung eingegeben hast. Hast du eine Zeile vergessen oder hast du dich verschrieben? Und ist der Sketch wirklich korrekt übertragen worden?
Überprüfe die Funktionsfähigkeit des von dir verwendeten Tasters mit einem Durchgangsprüfer oder Multimeter.
Was haben wir gelernt?
Du hast erfahren, dass mechanische Bauteile, Taster oder Schalter zum Beispiel, Kontakte nicht unmittelbar schließen oder öffnen. Durch verschiedene Faktoren wie Fertigungstoleranzen, Verunreinigungen oder schwingende Materialien können mehrere und kurz hintereinander folgende Unterbrechungen stattfinden, bevor ein stabiler Zustand erreicht wird. Dieses Verhalten wird vom Mikrocontroller registriert und entsprechend verarbeitet. Möchtest du zum Beispiel die Anzahl von Tastendrücken zählen, können sich solche Mehrfachimpulse als außerordentlich störend erweisen.
Du hast gelernt, was eine serielle Schnittstelle ist und wie diese beim Arduino arbeitet.
Das Prellverhalten kann durch unterschiedliche Ansätze kompensiert werden:Durch eine Softwarelösung (beispielsweise durch eine Verzögerungsstrategie beim Abfragen des Eingangssignals).Durch eine Hardwarelösung (zum Beispiel sogenannte RC-Glieder). Informationen dazu sind im Internet zu finden: http://www.mikrocontroller.net/articles/Entprellung.
Bastelprojekt 6:
Ein Lauflicht
Du hast jetzt schon einiges über die Ansteuerung von LEDs erfahren, so dass wir in kommenden Bastelprojekten die unterschiedlichsten Schaltungen aufbauen können, um mehrere Leuchtdioden blinken zu lassen. Das mag sich im Moment recht simpel anhören, aber lass dich überraschen, welche hübschen Sachen sich damit machen lassen. Wir wollen mit einem Lauflicht beginnen. Hierbei werden LEDs so angesteuert, dass sie nacheinander angehen und dabei der Effekt eines Lauflichts entsteht. Wenn du dich an das Bastelprojekt 2 erinnerst, haben wir so etwas bereits programmiert. Doch dabei ging es primär um die Ansteuerung der LEDs mithilfe der Ports durch Manipulation der korrespondierenden Register. In diesem Bastelprojekt möchte ich Vergleichbares zeigen, doch diesmal mit einem anderen Lösungsweg.
Immer der Reihe nach
Die an den digitalen Pins angeschlossenen LEDs sollen nach dem folgenden Muster angesteuert werden:

Abb. 1: Die Leuchtsequenz der sieben LEDs
Bei jedem neuen Durchlauf leuchtet also die LED eine Position weiter nach links. Ist das Ende erreicht, beginnt das Spiel von vorn. Du kannst die Programmierung der einzelnen Pins, die allesamt als Ausgänge arbeiten sollen, auf unterschiedliche Weise angehen. Mit dem Wissen, das du bisher hast, musst du sieben Variablen deklarieren und mit den entsprechenden Pin-Werten initialisieren. Das würde vielleicht wie folgt aussehen:
int ledPin1 = 7; int ledPin2 = 8; int ledPin3 = 9;
Und immer so weiter. Anschließend muss jeder einzelne Pin in der setup-Funktion mit pinMode als Ausgang programmiert werden, was ebenfalls eine mühsame Tipparbeit erfordert:
pinMode(ledPin1, OUTPUT); pinMode(ledPin2, OUTPUT); pinMode(ledPin3, OUTPUT);
Und so fort. Aber die Rettung naht! Ich möchte dir einen interessanten Variablentyp vorstellen, der in der Lage ist, mehrere Werte des gleichen Datentyps unter einem Namen zu speichern. Diese spezielle Form der Variablen nennt man Array. Der Zugriff darauf erfolgt nicht nur über den eindeutigen Namen, sondern eine solche Variable besitzt zusätzlich einen Index. Dieser Index ist eine Ganzzahl, die hochgezählt werden kann. Auf diese Weise werden die einzelnen Elemente des Arrays (so werden die gespeicherten Werte genannt) aufgerufen und geändert. Du wirst das im nun folgenden Sketch-Code sehen.
Was wir brauchen
Für dieses Bastelprojekt benötigen wir die folgenden Bauteile:
Tabelle 1: Bauteilliste Bauteil Bild LED rot (oder auch grün) 7x

Der Schaltplan
Der Schaltplan gleicht dem der Abfrage eines Tasters aus dem vorherigen Bastelprojekt 2:

Abb. 2: Die Ansteuerung der sieben LEDs für das Lauflicht
Der Schaltungsaufbau
Auch der Schaltungsaufbau ähnelt dem aus dem vorherigen Bastelprojekt und das Arduino Discoveryboard mit seinen zehn LEDs im Block leistet auch hier wieder sehr gute Dienste. Es werden hierbei nur sieben der LEDs angesteuert.

Abb. 3: Der Schaltungsaufbau für das Lauflicht mit sieben LEDs
Der Arduino-Sketch
Der Sketch für das Lauflicht sieht wie folgt aus:
int ledPin[] = {7, 8, 9, 10, 11, 12, 13}; // LED-Array mit Pin-Werten int wartezeit = 200; // Pause zwischen den Wechseln in ms void setup() { for(int i = 0; i < 7; i++) pinMode(ledPin[i], OUTPUT); // Alle Pins des Arrays als Ausgang } void loop() { for(int i = 0; i < 7; i++) { digitalWrite(ledPin[i], HIGH); // Array-Element auf HIGH-Pegel delay(wartezeit); // Eine Pause zwischen den Wechseln digitalWrite(ledPin[i], LOW); // Array-Element auf LOW-Pegel } }
Schauen wir uns die Erklärungen zu diesem Sketch an, denn wir haben es mit einigen programmtechnischen Neuerungen zu tun.
Den Code verstehen
Im Lauflicht-Sketch begegnest du zum ersten Mal einem Array und einer Schleife. Die Schleife wird benötigt, um komfortabel die einzelnen Array-Elemente über die darin enthaltenen Pin-Nummern anzusprechen. Es werden so zum einen alle Pins als Ausgänge programmiert und zum anderen die digitalen Ausgänge ausgelesen. Ich hatte erwähnt, dass jedes einzelne Element über einen Index angesprochen wird, und da die Schleife, die wir hier nutzen, einen bestimmten Wertebereich verwendet, ist dieses Konstrukt für unsere Aufgabe geeignet. Beginnen sollten wir mit der Array-Variablen. Die Deklaration ähnelt der bei einer ganz normalen Variablen, wobei aber zusätzlich das eckige Klammerpaar hinter dem Namen erforderlich ist.

Abb. 4: Die Array-Deklaration
Welche Dinge sind zu beachten?
Der Datentyp legt fest, welchen Typ die einzelnen Array-Elemente haben sollen.
Der Array-Name ist ein eindeutiger Name für den Zugriff auf die Variable.
Das Kennzeichen für das Array sind die eckigen Klammern mit der Größenangabe, wie viele Elemente das Array aufnehmen soll.
Du kannst dir ein Array wie einen Schrank mit mehreren Schubladen vorstellen. Jede einzelne Schublade trägt ein Schildchen mit einer fortlaufenden Nummer auf der Außenseite. Wenn ich dir die Anweisung gebe, doch bitte die Schublade mit der Nummer 3 zu öffnen, um zu sehen, was drin ist, dann ist das eine eindeutige Anweisung, oder? Ähnlich verhält es sich bei einem Array.

Bei dem hier gezeigten Array wurden nach der Deklaration alle Elemente implizit mit dem Wert 0 initialisiert. Die Initialisierung kann jedoch explizit auf zwei unterschiedliche Weisen erfolgen. Wir haben den komfortablen Weg gewählt und die Werte, mit denen das Array versehen werden soll, in geschweiften Klammern hinter der Deklaration, durch Komma separiert, aufgelistet.
int ledPin[] = {7, 8, 9, 10, 11, 12, 13};
Nach dieser Befehlszeile sieht der Array-Inhalt wie folgt aus:

Bei der Deklaration des Arrays ist das eckige Klammernpaar leer. Dort sollte doch die Größe des Arrays angegeben sein. Warum ist das so? In diesem Fall erkennt der Compiler anhand der mitgelieferten Informationen bei der Initialisierung, die ja in derselben Zeile erfolgt, um wie viele Elemente es sich handelt. Aus diesem Grund kannst du sie weglassen. Die etwas aufwendigere Art der Initialisierung besteht darin, die einzelnen Werte jedem Array-Element explizit zuzuweisen:
int ledPin[7]; // Deklaration des Arrays mit 7 Elementen void setup() { ledPin[0] = 7; ledPin[1] = 8; ledPin[2] = 9; ledPin[3] = 10; ledPin[4] = 11; ledPin[5] = 12; ledPin[6] = 13; // ... }
Was ist beim Index zu beachten?

Das erste Array-Element hat immer den Index mit der Nummer 0. Deklarierst du etwa ein Array mit zehn Elementen, dann ist der höchste zulässige Index der mit der Nummer 9, also immer eins weniger als die Anzahl der Elemente. Hältst du dich nicht daran, dann provozierst du möglicherweise einen Laufzeitfehler, denn der Compiler, der hinter der Entwicklungsumgebung steckt, bemerkt das weder zur Entwicklungszeit noch später zur Laufzeit, und deshalb solltest du doppelte Sorgfalt walten lassen.
Kommen wir jetzt zur Schleife und schauen uns die Syntax ein wenig genauer an.

Abb. 5: Die for-Schleife
Die Schleife wird mit dem Schlüsselwort for eingeleitet und wird deswegen auch for-Schleife genannt. Ihr werden, in runde Klammern eingeschlossen, bestimmte Informationen geliefert, die Auskunft über folgende Eckpunkte geben:
Mit welchem Wert soll die Schleife beim Zählen beginnen (Initialisierung)?
Wie weit soll gezählt werden (Bedingung oder Test)?
Um welchen Betrag soll der ursprüngliche Wert verändert werden (Update)?
Diese drei Informationseinheiten legen das Verhalten der for-Schleife fest und bestimmen ihr Verhalten beim Aufruf.
Wann kommt eine for-Schleife zum Einsatz?

Eine for-Schleife kommt meistens dann zum Einsatz, wenn von vornherein bekannt ist, wie oft bestimmte Anweisungen ausgeführt werden sollen. Diese Eckdaten werden im sogenannten Schleifenkopf, der von runden Klammern umschlossen ist, definiert.
Aber werden wir etwas konkreter. Die folgende Codezeile deklariert und initialisiert eine Variable i vom Datentyp int mit dem Wert 0:
for(int i = 0; i < 7; i++)
Die Angabe des Datentyps innerhalb der Schleife besagt, dass es sich um eine lokale Variable handelt, die nur so lange existiert, wie die for-Schleife iteriert, also ihren Durchlauf hat. Beim Verlassen der Schleife wird die Variable i aus dem Speicher entfernt. Die genaue Bezeichnung für eine Variable innerhalb einer Schleife lautet Laufvariable. Sie durchläuft so lange einen Bereich, wie die Bedingung (i < 7) erfüllt ist, die hier mit Test bezeichnet wurde. Anschließend erfolgt ein Update der Variablen durch den Update-Ausdruck. Der Ausdruck i++ erhöht die Variable i um den Wert 1.
Wir haben den Ausdruck i++ verwendet. Was bedeutet das genau? Er soll den Wert um 1 erhöhen, doch die Schreibweise ist irgendwie komisch. Bei den beiden hintereinander angeführten Pluszeichen ++ handelt es sich um einen Operator, der den Inhalt des Operanden, also der Variablen, um den Wert 1 erhöht. Programmierer sind von Haus aus faule Zeitgenossen und versuchen alles, was eingetippt werden muss, irgendwie kürzer zu formulieren. Wenn man bedenkt, wie viele Codezeilen ein Programmierer in seinem Leben eingeben muss, kommt es da auf jeden Tastendruck an. In der Summe könnte es sich um Monate oder Jahre an Lebenszeit handeln, die sich durch kürzere Schreibweisen einsparen lassen und für wichtigere Dinge, wie noch mehr Code, genutzt werden könnten. Jedenfalls sind die beiden folgenden Ausdrücke in ihren Auswirkungen vollkommen identisch:
i++;
und
i = i + 1;
Es wurden zwei Zeichen weniger verwendet, was eine Einsparung von immerhin 40% ausmacht. Doch weiter im Text. Die Laufvariable i wird als Indexvariable im Array eingesetzt und spricht somit die einzelnen Array-Elemente nacheinander an.

Bei diesem Snapshot eines Schleifendurchlaufs hat die Variable i den Wert 3 und spricht somit das vierte Element an, das wiederum den Inhalt 10 besitzt. Das bedeutet, dass mit den zwei folgenden Zeilen innerhalb der setup-Funktion alle im Array ledPin hinterlegten Pins als Ausgänge programmiert werden:
for(int i = 0; i < 7; i++) pinMode(ledPin[i], OUTPUT);
Folgendes ist noch sehr wichtig zu erwähnen: Wenn keine Blockbildung mit einer for-Schleife mittels geschweifter Klammern stattfindet, wie wir es gleich in der loop-Funktion sehen werden, wird nur die Zeile, die der for-Schleife unmittelbar folgt, von dieser berücksichtigt. Der Code der loop-Funktion beinhaltet lediglich eine for-Schleife, die durch ihre Blockstruktur jetzt mehrere Befehle anspricht:
for(int i = 0; i < 7; i++) { digitalWrite(ledPin[i], HIGH); // Array-Element auf HIGH-Pegel delay(wartezeit); digitalWrite(ledPin[i], LOW); // Array-Element auf LOW-Pegel }
Ich möchte dir an einem kurzen Sketch zeigen, wie die Laufvariable i heraufgezählt, was man auch Inkrementieren nennt:
void setup() { Serial.begin(9600); // Serielle Schnittstelle konfigurieren for(int i = 0; i < 7; i++) Serial.println(i); // Ausgabe an die serielle Schnittstelle } void loop(){ /* leer */ }
Da unser Arduino von Haus aus kein Ausgabefenster besitzt, müssen wir uns etwas anderes einfallen lassen. Die serielle Schnittstelle, an der er quasi angeschlossen ist, können wir dazu nutzen, Daten zu versenden. Die Entwicklungsumgebung verfügt über einen Serial Monitor, der diese Daten bequem empfangen und darstellen kann. Du kannst ihn sogar dazu verwenden, Daten an das Arduino-Board zu schicken, die anschließend dort verarbeitet werden können. Doch dazu gleich mehr. Der folgende Befehl initialisiert die serielle Schnittstelle mit einer Übertragungsrate von 9600 Baud:
Serial.begin(9600);
Die folgende Zeile sendet dann mittels der println-Funktion den Wert der Variablen i an die Schnittstelle:
Serial.println(i);
Du musst jetzt lediglich den Serial Monitor öffnen und die Werte werden angezeigt (Abbildung 6).
Du siehst hier, wie die Werte der Laufvariablen i von 0 bis 6 ausgegeben werden, die wir in unserem eigentlichen Sketch zur Auswahl der Array-Elemente benötigen. Ich habe den Code innerhalb der setup-Funktion platziert, damit die for-Schleife nur einmal ausgeführt wird und die Anzeige nicht ständig durchläuft.

Abb. 6: Die Ausgabe der Werte im Serial Monitor
Die folgende Abbildung 7 zeigt dir die einzelnen Durchläufe der for-Schleife etwas genauer:

Abb. 7: Das Verhalten der for-Schleife
Wie die serielle Schnittstelle zu konfigurieren ist und wie man etwas dahin versendet, hast du schon gesehen. Die Methode begin initialisiert das Serial-Objekt mit der angeforderten Übertragungsrate und die Methode println (print line bedeutet so viel wie Drucke und mache einen Zeilenvorschub) gibt etwas auf der seriellen Schnittstelle aus. Das Bindeglied zwischen Objekt und Methode ist der Punktoperator (.), der beide verbindet.
Die serielle Schnittstelle bei der Fehlersuche

Du hast jetzt erfahren, wie etwas an die serielle Schnittstelle geschickt werden kann. Du kannst dir diesen Umstand zunutze machen, wenn du einen oder mehrere Fehler in einem Sketch finden möchtest. Funktioniert der Sketch nicht so, wie du es dir vorstellst, dann positioniere an unterschiedlichen Stellen im Code, die dir wichtig erscheinen, Ausgabebefehle in Form von Serial.println(...); und lass dir bestimmte Variableninhalte oder auch Texte ausgeben. Auf diese Weise erfährst du, was dein Sketch treibt und warum er möglicherweise nicht korrekt abläuft. Du musst lediglich lernen, die ausgegebenen Daten zu interpretieren. Das ist manchmal nicht so einfach und es gehört ein wenig Übung dazu.
Register direkt beeinflussen
Im Bastelprojekt 2 über die Low-Level-Programmierung des Arduino haben wir gesehen, wie einfach es ist, die digitalen Pins über die Manipulation von Registern zu beeinflussen. Hinsichtlich des Lauflichts kann man sich die Sache zunutze machen. Der nachfolgende Schaltplan verfügt lediglich über sechs LEDs mit den entsprechenden Vorwiderständen, wobei ich aber jeder LED eine kleine Zahl beigestellt habe. Wozu das ganze sinnvoll ist, werden wir gleich sehen.

Abb. 8: Der Schaltplan für unser kleines Lauflicht
Werfen wir noch einmal einen Blick auf das Register PORT B.

Die acht Bits eines Ports werden zu einem sogenannten Byte zusammengefasst. Jedes einzelne Bit dieses Bytes besitzt eine Nummer, die von rechts nach links aufsteigend bei 0 beginnt, wie das in der oberen Reihe zu erkennen ist. Somit können wir jedes einzelne Bit eindeutig von 0 bis 7 adressieren. Jedes einzelne Bit besitzt neben seiner Position innerhalb des Bytes einen Stellenwert oder eine Wertigkeit, die eben genau von der betreffenden Position innerhalb des Bytes abhängt und ebenfalls von rechts nach links zunimmt. Die untere Reihe zeigt die Wertigkeit jedes einzelnen Bits an. Die Frage ist nur, wie diese Werte eigentlich zustande kommen. Das ist sehr einfach! Da das binäre System lediglich die beiden Zustände 0 und 1 kennt, ist die Basis zur Berechnung der Stellenwertigkeit die Zahl 2. Wir erinnern uns, dass unser Dezimalsystem die Ziffern 0 bis 9 kennt und demnach zehn mögliche Zustände vorhanden sind. Die Basis zur Berechnung der Stellenwertigkeit ist demnach die Zahl 10.