Es ist Zeit unsere Ampel etwas interessanter zu gestalten, indem wir ihr einen Knopf hinzufügen. Bereit zu erfahren, wie du den digitalen Eingang deines Arduino nutzen kannst?
Im letzten Tutorial haben wir gelernt, wie man Pins des Arduinos als digitalen Ausgang zur Steuerung von LEDs nutzt und so eine Miniatur-Ampel baut. Wir haben dabei die pinMode
-Prozedur genutzt, um die Pins als Ausgang zu konfigurieren. Erinnerst du dich daran, dass es noch andere Modi gab? Diese werden wir heute verwenden, wenn wir die Ampel mit einem Taster erweitern.
Was ist unser neues Ziel? Nun, in einfacheren Situationen, in denen eine Ampel nur verwendet wird, um Fußgänger sicher über die Straße zu lassen, ist es nicht erforderlich, dass Autos anhalten müssen, wenn niemand da ist. Für dieses Szenario brauchen wir einen Knopf. Sobald ein Fußgänger diesen Knopf drückt, wird die Ampel rot, die Autos stoppen und der Fußgänger kann die Straße sicher überqueren. Wenn niemand die Straße überqueren muss, bleibt die Ampel einfach grün. Damit dies funktioniert, müssen wir einen Knopf an den Arduino anschließen und die Verzögerung von 10 s, die wir derzeit in der Grünphase verwenden, durch Code ersetzen, der prüft, ob ein Fußgänger den Knopf drückt.
Beginnen wir damit den Taster anzuschließen. Dazu stecken wir den Taster in das Breadboard und verbinden eine Seite mit 5 V
und die andere Seite mit Pin 2
. Achten darauf, dass du den Taster richtig herum einzustecken. Die meisten Taster haben vier Anschlüsse und immer zwei davon sind fest miteinander verbunden und bilden ein Paar. Wenn du auf den Knopf drückst, werden die beiden Paare miteinander verbunden. Wenn du den Knopf in der falschen Richtung einsteckst, dann wird Pin 2
und die 5 V
dauerhaft miteinander verbunden sein. Wir wollen, dass sie nur dann verbunden sind, wenn der Knopf gedrückt wird. Du kannst ein Multimeter verwenden, um zu überprüfen, welche Pins miteinander verbunden sind und welche nicht.
Was haben wir damit erreicht? Sobald der Knopf gedrückt wird, wird Pin 2
mit 5 V
verbunden, und wir sollten in der Lage sein mit dem Arduino den hohen Spannungspegel an Pin 2 zu erkennen. Aber was passiert, wenn der Knopf nicht gedrückt wird? An Pin 2
ist dann nichts angeschlossen und wir bekommen einen potenzialfreien Pin ohne definierten Spannungspegel. Der Arduino wird zufällig HIGH
oder LOW
lesen. Das ist nicht das, was wir wollen. Wir wollen, dass das Arduino LOW
anzeigt, wenn wir den Knopf nicht drücken. Wir können dies erreichen, indem wir einen 10 kΩ Widerstand hinzufügen, um Pin 2
mit GND
zu verbinden. Wenn der Knopf nicht gedrückt wird, zieht dieser Widerstand den Spannungspegel auf 0 V und wir lesen LOW
. Sobald wir den Knopf drücken, lesen wir immer noch HIGH
und alles funktioniert wie erwartet. Das liegt daran, dass es keinen Widerstand zwischen Pin 2
und 5 V
gibt. Auf diese Weise hat die Verbindung zu 5 V
einen viel stärkeren Einfluss auf den Spannungspegel an Pin 2
als die Verbindung zu GND
. Beachten, dass ich mit 10 kΩ einen ziemlich hohen Widerstand gewählt habe. Er stellt sicher, dass kein großer Strom zwischen GND
und 5 V
fließt und wir einen klaren HIGH
Pegel an Pin 2
erhalten, wenn der Knopf gedrückt wird.
Dieser Widerstand wird auch als "Pulldown-Widerstand" bezeichnet, da er den Spannungspegel an Pin 2
auf einen stabilen 0 V-Pegel herunterzieht, wenn nichts anderes an den Pin angeschlossen ist. Es gibt genauso auch einen Pullup-Widerstand. Er zieht den Spannungspegel auf die 5 V. In unserem Fall ist dies jedoch nicht das, was wir brauchen.
Jetzt müssen wir den Code anpassen. Der erste Schritt ist die Konfiguration von Pin 2
als Eingangspin in der setup
-Prozedur. Erinnerst du dich noch, wie das geht?
Wir nutzen dafür die folgende Prozedur:
pinMode(pin, mode)
pin
: Nummer des digitalen Pins (0
bis 13
) oder A1
bis A5
für analoge Pinsmode:
OUTPUT
, INPUT
, INPUT_PULLUP
Für unseren Taster müssen wir den Modus auf INPUT
setzen, um den Pin als Eingang zu konfigurieren. Nach der Änderung sieht unsere setup
-Prozedur wie folgt aus:
void setup() {
pinMode(8, OUTPUT); // Green LED
pinMode(9, OUTPUT); // Yellow LED
pinMode(10, OUTPUT); // Red LED
pinMode(2, INPUT); // Button
}
Bereit für den schweren Teil? Wir müssen in der grünen Phase darauf warten, dass der Knopf gedrückt wird. Wie können wir dies erkennen?
Arduino bietet zu diesem Zweck eine Funktion namens digitalRead
an. Wie du beim letzten Mal gelernt hast, besitzt eine Funktion einen Rückgabewert. Der Rückgabewert von digitalRead
ist entweder HIGH
oder LOW
, abhängig vom Spannungspegel am Pin. In unserem Fall erwarten wir den Wert HIGH
, wenn der Knopf gedrückt wird.
Hier ist die kurze Zusammenfassung dieser Funktion und ihres Arguments der Pin-Nummer:
digitalRead(pin)
pin
: Nummer des digitalen Pins (0
bis 13
) oder A1
bis A5
für analoge PinsHIGH
oder LOW
zurückDu hast vielleicht bemerkt, dass dies nur ein Teil der Lösung ist. Wir müssen die Ausgabe dieser Funktion mit dem vergleichen, was wir erwarten, und in der Phase warten, bis der Knopf gedrückt wird. Zu diesem Zweck brauchen wir eine Kontrollstruktur. Eine Kontrollstruktur steuert, unter welchen Bedingungen und in welcher Reihenfolge Anweisungen ausgeführt werden. Wir werden neue Kontrollanweisungen Schritt für Schritt lernen. An dieser Stelle möchte ich dir die while
-Schleife vorstellen. Die while
-Schleife führt eine einzelne Anweisung oder einen Codeblock aus, solange eine bestimmte Bedingung erfüllt ist. In unserem Fall müssen wir so lange warten, wie die Taste nicht gedrückt ist. Die while
-Schleife ermöglicht uns genau das.
Schauen wir uns zunächst an, wie wir Bedingungen ausdrücken. Bedingungen werden als boolesche Ausdrücke formuliert. Diese können entweder als true
(wahr) oder false
(falsch) ausgewertet werden. Nehmen wir als Beispiel den Ausdruck 3
ist gleich 4
. Er wird immer als "falsch" ausgewertet werden, da die Behauptung, dass "3" gleich "4" ist, einfach nicht wahr ist. In dieser Behauptung verwenden wir einen sogenannten Vergleichsoperator. Der "ist gleich"-Operator wird verwendet, um die beiden Zahlen auf Gleichheit zu überprüfen.
In C++ gibt es die folgenden Vergleichsoperatoren:
Operator in Worten | C++ Syntax | Boolesches Ergebnis |
---|---|---|
3 ist gleich 4 |
3 == 4 |
false |
3 ist ungleich 4 |
3 != 4 |
true |
3 ist größer als 4 |
3 > 4 |
false |
3 ist größergleich 4 |
3 >= 4 |
false |
3 ist kleiner als 4 |
3 < 4 |
true |
3 ist kleinergleich 4 |
3 <= 4 |
true |
Zusätzlich zu den Vergleichsoperatoren gibt es auch noch logische Operatoren. Diese erlauben es uns, mehrere boolesche Ausdrücke miteinander zu kombinieren. In C++ gibt es die folgenden logischen Operatoren, die selbst auch beliebig kombiniert werden können:
Operator | C++ Syntax | Boolesches Ergebnis |
---|---|---|
AND-Operator: Beide Ausrücke sind wahr | 3 < 4 && 3 > 4 |
false |
OR-Operator : MIndestens einer der Ausdrücke ist wahr | 3 < 4 || 3 > 4 |
true |
NOT-Operator: Der Ausdruck ist unwahr | !(3 > 4) |
true |
Wie verwenden wir sie zusammen mit der while
-Schleife? Die Syntax für die while
-Schleife ist die folgende:
while(condition) statement;
Wenn wir in der Schleife mehrere Anweisungen also einen ganzen Codeblock wiederholen wollen, verwenden wir stattdessen diese Form:
while(condition) {
statement1;
statement2;
}
Meistens wirst du vermutlich die zweite Option sehen. Du kannst diese Variante auch mit nur einer einzigen Anweisung innerhalb des Codeblocks verwenden. Du kannst diese Option also immer verwenden und so einen einheitlichen Stil im gesamten Programm nutzen.
In unserem Fall ist die Frage jedoch eine andere: Welche Anweisung wollen wir ausführen, solange die Taste nicht gedrückt ist? Vielleicht erwägst du ein delay
innerhalb der Schleife zu verwenden. Das wäre sicherlich möglich, aber wir müssten aufpassen, dass wir den Knopfdruck nicht verpassen, während das delay
ausgeführt wird. Aus diesem Grund müssten wir entweder eine winzige Verzögerung verwenden oder wir verwenden die Funktion einfach gar nicht. Die Verwendung von delay
bringt uns hier keinen Vorteil. Die Antwort ist, dass wir gar nichts ausführen wollen, wir wollen nur warten, bis der Knopf gedrückt wird. Glücklicherweise zwingt uns die while
-Schleife nicht dazu, etwas auszuführen. Wir können auch einfach einen leeren Codeblock angeben.
Zusammen mit unserer Bedingung sieht das dann so aus:
while(digitalRead(2) == LOW) {}
Alternativ kannst du auch eine leere Anweisung nach dem while
einfügen. Schreibe dazu einfach ein Semikolon, um die Anweisung abzuschließen ohne irgendwelchen Code davor.
Das Ganze sieht dann folgendermaßen aus:
while(digitalRead(2) == LOW);
Du kannst die Variante nutzen, die dir am besten gefällt. Funktional, sind beide Codezeilen identisch.
Wenn wir alles zusammenführen, dann erhalten wir am Ende die folgende loop
-Prozedur:
void loop() {
// Green
digitalWrite(8, HIGH);
while(digitalRead(2) == LOW);
digitalWrite(8, LOW);
// Yellow
digitalWrite(9, HIGH);
delay(2000);
digitalWrite(9, LOW);
// Red
digitalWrite(10, HIGH);
delay(10000);
// Red and Yellow
digitalWrite(9, HIGH);
delay(1000);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
}
Sobald wir mit der Codierung fertig sind und den Code auf den Arduino Uno hochgeladen haben, sehen wir eine grüne LED, die dauerhaft leuchtet. Sobald wir die Taste drücken, wird die Ampel gelb. Alles funktioniert wie erwartet. Wie du jedoch vielleicht gemerkt hast, brauchen wir ziemlich viel Platz für unseren Taster. Wir benötigen einen externen Widerstand und drei Verbindungskabel. Für einen so klassischen Anwendungsfall muss es eine einfachere Lösung ohne externe Komponenten geben. Diese Lösung nennt sich interner Pullup-Widerstand. Lass uns gemeinsam angucken, wie man ihn benutzt.
Es gibt einen alternativen Eingabemodus namens INPUT_PULLUP
und wie du vielleicht schon vermutest hast, hat er etwas mit Pullup-Widerständen zu tun. Das Arduino besitzt für jeden Pin einen internen Pull-up-Widerstand. Das macht es sehr viel einfacher, Dinge an digitale Eingänge anzuschließen, da wir keinen externen Widerstand benötigen. Aber, warten mal! Wir haben doch eben einen Pulldown-Widerstand verwendet, oder? Ja, richtig! Leider hat der Arduino Uno nur Pullup-Widerstände. Es gibt auch andere Mikrocontroller, die ebenfalls Pulldown-Widerstände enthalten. Wie können wir mit dieser Einschränkung zurechtkommen?
Nun, es gibt eine ganz einfache Lösung. Bisher haben wir beim Drücken des Knopfes Pin 2
mit 5 V
verbunden. So konnten wir mit dem Arduino beim Drücken des Knopfes HIGH
zurücklesen. HIGH
oder 1
für den Fall, dass wir den Knopf drücken, scheint irgendwie intuitiv zu sein, aber es ist keine gottgegebene Sache. Wir können es auch andersherum machen. Bei Verwendung des internen Pullup-Widerstands wird Pin 2
über diesen mit 5 V
verbunden. Wenn nichts anderes angeschlossen ist, erhalten wir so einen 5 V-Spannungspegel an Pin 2
. Der Taster verbindet Pin 2
mit GND
, sobald er gedrückt wird. Auf diese Weise werden wir HIGH
lesen, wenn der Knopf nicht gedrückt ist, und LOW
, wenn er gedrückt ist. Diese Umkehrung mag zunächst etwas verwirrend sein, aber sie erlaubt es uns, den internen Pullup-Widerstand zu benutzen. Übrigens, was ist eigentlich der Wert des internen Pullups? Im Datenblatt des Mikrocontrollers wird er mit einem Wert zwischen 20 kΩ und 50 kΩ angegeben. Er ist also tatsächlich etwas größer als mein 10 kΩ Widerstand, den wir zuvor als externen Pull-down-Widerstand verwendet haben. Für unseren Anwendungsfall ist das jedoch kein Problem.
Lass uns nun die Schaltung nach dem neuen Schema aufbauen:
Wir müssen auch unseren Code an das neue Szenario anpassen. Ersetze dazu in der Setup-Prozedur einfach pinMode(2, INPUT)
durch pinMode(2, INPUT_PULLUP)
. Als Nächstes müssen wir die Bedingung anpassen, mit der überprüft wird, ob der Knopf gedrückt ist. Da wir jetzt LOW
lesen werden, sobald der Knopf gedrückt ist, müssen wir in der Schleife warten, solange wir HIGH
lesen und der Knopf nicht gedrückt ist. Ändere dazu einfach digitalRead(2) == LOW
in digitalRead(2) == HIGH
um, laden den Code auf deinen Arduino hoch und dann sollte alles funktionieren.
void setup() {
pinMode(8, OUTPUT); // Green LED
pinMode(9, OUTPUT); // Yellow LED
pinMode(10, OUTPUT); // Red LED
pinMode(2, INPUT_PULLUP); // Button
}
void loop() {
// Green
digitalWrite(8, HIGH);
while(digitalRead(2) == HIGH);
digitalWrite(8, LOW);
// Yellow
digitalWrite(9, HIGH);
delay(2000);
digitalWrite(9, LOW);
// Red
digitalWrite(10, HIGH);
delay(10000);
// Red and Yellow
digitalWrite(9, HIGH);
delay(1000);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
}
Schauen wir uns unser Ergebnis an. Das Verhalten deiner Ampel sollte dem im untern gezeigten Video entsprechen. Wenn du nichts tust, bleibt die Ampel einfach grün, und wenn du den Knopf drückst, wird sie sofort gelb und danach rot. Ziel erreicht? Ja, irgendwie schon, aber mal ehrlich, es wäre eine ziemlich lästige Ampel für die Autofahrer. Die meisten Ampeln reagieren nicht sofort, wenn ein Fußgänger den Knopf drückt. In unserer Stadt gibt es eine, die es tut - allerdings nur, wenn vorher lange Zeit niemand den Knopf gedrückt hat. Unsere Ampel wird quasi nie grün, wenn wir den Knopf ständig drücken, also müssen wir dies verbessern und eine Art Cooldown hinzufügen. Dies wird unsere Aufgabe im nächsten Tutorial sein. Wie würdest du diese Aufgabe lösen?