Ein Taster gehört zu den einfachsten Bauteilen, die an einen ESP32 angeschlossen werden können.
Und trotzdem kann man damit bereits eine Menge lernen.
Zum Beispiel:
- digitale Eingänge
- HIGH und LOW
digitalRead()INPUT_PULLUP- Bedingungen mit
if - Zustandsänderungen
- Tasterprellen
- kurzes und langes Drücken
In diesem Beitrag schauen wir uns den Taster am ESP32 Schritt für Schritt an.
Ganz einfach erklärt und ohne unnötige Fachbegriffe.
Was ist ein Taster?
Ein Taster ist zunächst nichts anderes als ein kleiner Schalter.
Allerdings gibt es einen wichtigen Unterschied.
Ein Schalter bleibt normalerweise in seiner Position.
Zum Beispiel:
Licht an → Schalter bleibt an
Ein Taster springt dagegen nach dem Loslassen wieder zurück.
Ein typisches Beispiel ist eine Türklingel:
drücken → Klingel
loslassen → Klingel aus
Genau dieses Prinzip können wir mit dem ESP32 erkennen.
Wie funktioniert ein Taster elektrisch?
Ein einfacher Taster besitzt zwei Zustände.
Nicht gedrückt
Die elektrische Verbindung ist unterbrochen.
────── ──────
Gedrückt
Die Kontakte werden miteinander verbunden.
──────────────
Der ESP32 kann diese beiden Zustände erkennen.
Damit haben wir einen klassischen digitalen Eingang.
Warum besitzt mein Taster vier Beine?
Viele kleine Taster für Breadboards besitzen vier Anschlüsse.
Das wirkt zunächst merkwürdig.
Schließlich brauchen wir eigentlich nur zwei Kontakte.
Der Grund ist einfach:
Jeweils zwei Anschlüsse sind intern bereits miteinander verbunden.
Vereinfacht sieht das so aus:
1 ───── 2
│
TASTER
│
3 ───── 4
Beim Drücken werden die beiden Seiten miteinander verbunden.
Die vier Beine sorgen außerdem dafür, dass der Taster stabil auf einer Platine oder einem Breadboard sitzt.
Taster auf dem Breadboard
Beim Einsetzen eines vierbeinigen Tasters sollte darauf geachtet werden, dass er richtig auf dem Breadboard sitzt.
Besonders praktisch ist es, ihn über die mittlere Trennung des Breadboards zu setzen.
Dadurch befinden sich die beiden Seiten des Tasters auf getrennten Kontaktreihen.
Wie schließen wir den Taster an?
Für unser erstes Beispiel benötigen wir:
- ESP32
- Breadboard
- Taster
- zwei Jumper-Kabel
Wir verwenden beispielsweise:
GPIO 21
Eine Seite des Tasters verbinden wir mit:
GPIO 21
Die andere Seite mit:
GND
Vereinfacht:
ESP32
GPIO 21
│
│
TASTER
│
│
GND
Das ist schon die komplette Schaltung.
Es fehlt kein Widerstand.
Warum?
Das schauen wir uns jetzt an.
Das Problem mit einem offenen Eingang
Stellen wir uns zunächst vor, wir würden einfach schreiben:
pinMode(21, INPUT);
Wenn der Taster gedrückt wird, ist GPIO 21 mit GND verbunden.
Der ESP32 erkennt einen eindeutigen Zustand.
Aber was passiert, wenn der Taster nicht gedrückt wird?
Dann ist der GPIO mit nichts verbunden.
Er hängt gewissermaßen:
in der Luft.
Ein solcher Eingang wird als:
Floating Input
bezeichnet.
Der ESP32 könnte dann zufällige Zustände erkennen.
Zum Beispiel:
HIGH
LOW
HIGH
HIGH
LOW
LOW
HIGH
obwohl niemand den Taster berührt.
Das ist natürlich nicht besonders hilfreich.
Pull-up-Widerstand einfach erklärt
Wir benötigen deshalb einen definierten Zustand.
Dafür gibt es einen sogenannten:
Pull-up-Widerstand
Dieser zieht den Eingang vereinfacht gesagt auf HIGH, solange nichts anderes passiert.
Das Praktische:
Der ESP32 besitzt solche Widerstände bereits eingebaut.
Wir müssen also keinen zusätzlichen Widerstand auf das Breadboard stecken.
Stattdessen schreiben wir:
pinMode(21, INPUT_PULLUP);
Damit aktivieren wir den internen Pull-up-Widerstand.
Was bedeutet INPUT_PULLUP?
Zerlegen wir den Begriff.
INPUT
bedeutet:
Eingang
und:
PULLUP
bedeutet vereinfacht:
Ziehe den Eingang auf HIGH.
Solange der Taster nicht gedrückt wird, erkennt der ESP32 deshalb:
HIGH
Warum ist gedrückt plötzlich LOW?
Jetzt kommt etwas, das am Anfang häufig verwirrt.
Unsere Schaltung sieht so aus:
GPIO 21
│
TASTER
│
GND
Der interne Pull-up-Widerstand sorgt normalerweise für:
HIGH
Drücken wir den Taster, verbinden wir GPIO 21 direkt mit:
GND
Dadurch erkennt der ESP32:
LOW
Deshalb gilt bei unserer Schaltung:
Taster nicht gedrückt = HIGH
Taster gedrückt = LOW
Das sollte man sich merken.
INPUT_PULLUP + Taster gegen GND
nicht gedrückt → HIGH
gedrückt → LOW
Diese Art der Beschaltung ist bei Mikrocontrollern sehr verbreitet.
Unser erstes Taster-Programm
Jetzt wollen wir herausfinden, ob der ESP32 den Taster erkennt.
#define BUTTON_PIN 21
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
Serial.println(buttonState);
delay(100);
}
Das Programm ist bereits ausreichend.
Was macht digitalRead()?
Diese Zeile ist besonders wichtig:
digitalRead(BUTTON_PIN);
digitalRead() bedeutet:
digitalen Zustand lesen
Der ESP32 schaut also nach:
Ist dieser Eingang HIGH oder LOW?
Das Ergebnis speichern wir:
int buttonState = digitalRead(BUTTON_PIN);
in der Variable:
buttonState
Das Ergebnis im Serial Monitor
Mit:
Serial.println(buttonState);
geben wir den Zustand aus.
Ohne gedrückten Taster sehen wir beispielsweise:
1
1
1
1
1
1
Jetzt drücken wir den Taster:
0
0
0
0
0
0
Wir lassen ihn los:
1
1
1
1
1
Dabei gilt:
HIGH = 1
LOW = 0
Also:
1 = nicht gedrückt
0 = gedrückt
Unser Taster funktioniert.
Statt 0 und 1 lieber verständlichen Text anzeigen
Die Zahlen funktionieren.
Aber wir können es besser machen.
Unser ESP32 soll direkt schreiben:
Taster gedrückt
oder:
Taster nicht gedrückt
Dafür benötigen wir eine Entscheidung.
if einfach erklärt
if bedeutet:
wenn
Wir schreiben:
if (buttonState == LOW) {
Serial.println("Taster gedrueckt");
}
Das lässt sich fast wie ein normaler Satz lesen:
WENN der Zustand des Tasters LOW ist, DANN schreibe „Taster gedrückt“.
Was bedeutet ==?
Hier lauert ein klassischer Anfängerfehler.
Wir schreiben:
buttonState == LOW
mit zwei Gleichheitszeichen.
Das bedeutet:
vergleichen
Der ESP32 fragt:
Ist buttonState gleich LOW?
Ein einzelnes:
=
hat eine andere Bedeutung.
Damit wird ein Wert gespeichert.
Also:
= Wert zuweisen
== Werte vergleichen
else: Was passiert ansonsten?
Jetzt ergänzen wir:
else
else bedeutet:
ansonsten
Unser Programm:
#define BUTTON_PIN 21
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW) {
Serial.println("Taster gedrueckt");
}
else {
Serial.println("Taster nicht gedrueckt");
}
delay(100);
}
Jetzt zeigt der Serial Monitor:
Taster nicht gedrueckt
Taster nicht gedrueckt
Taster nicht gedrueckt
Taster gedrueckt
Taster gedrueckt
Taster gedrueckt
Taster nicht gedrueckt
Damit können wir bereits auf den Taster reagieren.
Eine LED mit dem Taster steuern
Jetzt kombinieren wir Eingang und Ausgang.
Wir verwenden:
GPIO 21 → Taster
und beispielsweise:
GPIO 18 → LED
Unser Ziel:
Taster gedrückt
↓
LED an
Taster losgelassen:
Taster losgelassen
↓
LED aus
Das Programm
#define BUTTON_PIN 21
#define LED_PIN 18
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (buttonState == LOW) {
digitalWrite(LED_PIN, HIGH);
}
else {
digitalWrite(LED_PIN, LOW);
}
}
Jetzt haben wir eine vollständige kleine Steuerung:
TASTER
↓
digitalRead()
↓
ESP32
↓
if()
↓
digitalWrite()
↓
LED
Das ist bereits das Grundprinzip sehr vieler Mikrocontroller-Projekte.
Aber jetzt kommt ein neues Problem
Bisher fragen wir ständig:
Ist der Taster gerade gedrückt?
Manchmal möchten wir aber etwas anderes wissen:
Wurde der Taster gerade eben gedrückt?
Das klingt ähnlich.
Für ein Programm ist es aber ein großer Unterschied.
Zustand oder Ereignis?
Angenommen, wir möchten bei jedem Tastendruck einen Zähler erhöhen.
Wir schreiben:
if (buttonState == LOW) {
zaehler++;
}
Jetzt drücken wir den Taster für eine halbe Sekunde.
Was passiert?
Vielleicht erwarten wir:
1
Tatsächlich könnte der ESP32 aber zählen:
1
2
3
4
5
6
7
8
9
...
Warum?
Der ESP32 arbeitet loop() extrem schnell ab.
Solange der Taster gedrückt bleibt, lautet die Antwort immer wieder:
LOW
Unser Programm zählt deshalb immer weiter.
Wir müssen den Tastendruck erkennen
Wir möchten nicht wissen:
Ist der Taster gedrückt?
Sondern:
Hat sich der Zustand gerade von nicht gedrückt auf gedrückt verändert?
Wir müssen uns deshalb den vorherigen Zustand merken.
Zum Beispiel:
int lastButtonState = HIGH;
Jetzt können wir vergleichen:
vorher HIGH
und:
jetzt LOW
Das bedeutet:
Der Taster wurde gerade gedrückt.
Einen einzelnen Tastendruck erkennen
#define BUTTON_PIN 21
int lastButtonState = HIGH;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (lastButtonState == HIGH && buttonState == LOW) {
Serial.println("Taster wurde gedrueckt!");
}
lastButtonState = buttonState;
}
Jetzt interessiert uns nicht mehr nur der aktuelle Zustand.
Wir vergleichen:
VORHER
mit
JETZT
Was bedeutet &&?
Im Programm steht:
lastButtonState == HIGH && buttonState == LOW
Die beiden Zeichen:
&&
bedeuten:
UND
Unser Programm fragt:
War der Taster vorher HIGH UND ist er jetzt LOW?
Wenn beide Bedingungen stimmen, wurde der Taster gerade gedrückt.
Was ist eine Flanke?
Für diese Zustandsänderung gibt es einen Fachbegriff:
Flanke
Keine Sorge, das Prinzip ist einfach.
Wir betrachten eine Veränderung:
HIGH → LOW
oder:
LOW → HIGH
Bei unserer INPUT_PULLUP-Schaltung bedeutet:
HIGH → LOW
Taster wurde gedrückt
und:
LOW → HIGH
Taster wurde losgelassen
Damit können wir zwischen:
Taster halten
und:
Taster neu drücken
unterscheiden.
LED mit jedem Tastendruck umschalten
Jetzt können wir etwas Interessanteres bauen.
Unser Ziel:
einmal drücken → LED an
noch einmal drücken → LED aus
noch einmal drücken → LED an
Der Taster funktioniert also ähnlich wie ein Lichtschalter.
Dafür müssen wir uns zusätzlich merken, ob die LED gerade an oder aus ist.
Zustand der LED speichern
Dafür verwenden wir:
bool ledState = false;
bool kann zwei Zustände speichern:
true
oder
false
Wir verwenden:
false = LED aus
true = LED an
Das Programm
#define BUTTON_PIN 21
#define LED_PIN 18
int lastButtonState = HIGH;
bool ledState = false;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int buttonState = digitalRead(BUTTON_PIN);
if (lastButtonState == HIGH && buttonState == LOW) {
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
}
lastButtonState = buttonState;
}
Jetzt schaltet jeder Tastendruck die LED um.
Was bedeutet !ledState?
Diese Zeile sieht zunächst merkwürdig aus:
ledState = !ledState;
Das Ausrufezeichen bedeutet hier vereinfacht:
Gegenteil
Ist:
ledState = false
wird daraus:
true
Ist:
ledState = true
wird daraus:
false
Also:
AUS → AN
AN → AUS
AUS → AN
Perfekt für unseren Taster.
Und trotzdem kann etwas Merkwürdiges passieren
Man drückt den Taster einmal.
Eigentlich sollte die LED:
einmal umschalten
Aber manchmal passiert:
AN
AUS
AN
obwohl der Taster nur einmal gedrückt wurde.
Ist der ESP32 kaputt?
Nein.
Der Taster macht tatsächlich etwas, das wir nicht sehen können.
Was ist Tasterprellen?
Ein echter mechanischer Taster schließt seinen Kontakt nicht immer sofort sauber.
Beim Drücken können die Metallkontakte für wenige Millisekunden hin und her springen.
Vereinfacht erwarten wir:
HIGH
HIGH
HIGH
LOW
LOW
LOW
LOW
In Wirklichkeit könnte das Signal kurz so aussehen:
HIGH
HIGH
LOW
HIGH
LOW
HIGH
LOW
LOW
LOW
Der Taster „prellt“.
Für uns Menschen passiert das extrem schnell.
Der ESP32 ist aber ebenfalls extrem schnell.
Er kann diese kurzen Zustandsänderungen erkennen.
Dadurch denkt unser Programm möglicherweise:
Taster gedrückt!
Taster gedrückt!
Taster gedrückt!
obwohl wir nur einmal gedrückt haben.
Taster entprellen
Dieses Problem nennt man:
Tasterprellen
Die Lösung heißt:
Entprellen
oder auf Englisch:
Debouncing
Auch Newbiely behandelt das Entprellen als eigenes Thema, weil es besonders bei der Erkennung einzelner Tastendrücke wichtig wird.
Die einfache Lösung: kurz warten
Für erste Experimente findet man häufig diese Lösung:
delay(50);
Nach einem erkannten Tastendruck wartet das Programm beispielsweise 50 Millisekunden.
Das kann bei einfachen Versuchen funktionieren.
Allerdings hat delay() einen Nachteil:
Während dieser Zeit macht der ESP32 nichts anderes.
Bei einer kleinen Übung ist das oft egal.
Bei größeren Projekten kann das problematisch werden.
Warum delay() später stören kann
Stellen wir uns vor, der ESP32 soll gleichzeitig:
- einen Taster prüfen
- Temperatur messen
- ein Display aktualisieren
- WLAN-Daten empfangen
- einen Motor steuern
Wenn das Programm ständig mit delay() wartet, werden auch die anderen Aufgaben ausgebremst.
Deshalb wird bei größeren Projekten häufig mit Zeitmessungen gearbeitet, ohne das gesamte Programm anzuhalten.
Dafür gibt es beispielsweise:
millis()
Das ist aber bereits ein Thema für einen späteren Beitrag.
Für den Einstieg reicht zunächst:
Taster können prellen und müssen bei Anwendungen mit einzelnen Tastendrücken gegebenenfalls entprellt werden.
Kurzes und langes Drücken erkennen
Jetzt kennen wir bereits:
Taster nicht gedrückt
Taster gedrückt
Taster wurde gerade gedrückt
Taster wurde losgelassen
Damit können wir noch einen Schritt weitergehen.
Wir können messen:
Wie lange wurde der Taster gedrückt?
Zum Beispiel:
kurz drücken
→ LED einschalten
und:
lange drücken
→ LED ausschalten
Dafür merken wir uns beim Drücken die aktuelle Zeit.
Beim Loslassen prüfen wir:
Wie viel Zeit ist vergangen?
Auch hierfür eignet sich:
millis()
Damit lassen sich später unterschiedliche Bedienfunktionen mit nur einem einzigen Taster realisieren.
Was kann man mit einem Taster alles machen?
Ein einzelner Taster kann erstaunlich viele Funktionen übernehmen.
Zum Beispiel:
Aktion auslösen
Taster drücken
→ Summer ertönt
Zustand umschalten
Taster drücken
→ LED an
erneut drücken
→ LED aus
Zähler erhöhen
Taster drücken
→ +1
Programm starten
Taster drücken
→ Ablauf beginnt
Zwischen Betriebsarten wechseln
Taster drücken
→ Modus 1
erneut drücken
→ Modus 2
Kurzes und langes Drücken
kurz
→ Funktion A
lang
→ Funktion B
Damit ist der einfache Taster eines der vielseitigsten Eingabebauteile überhaupt.
Typische Fehler beim Taster
Wenn der Taster nicht funktioniert, lohnt sich ein Blick auf einige typische Fehler.
Taster falsch auf dem Breadboard
Bei vierbeinigen Tastern sind jeweils zwei Pins bereits miteinander verbunden.
Ist der Taster falsch eingesetzt, kann die Schaltung dauerhaft verbunden sein.
GND fehlt
Bei unserer INPUT_PULLUP-Schaltung muss der Taster den Eingang beim Drücken mit GND verbinden.
INPUT statt INPUT_PULLUP
Bei:
pinMode(BUTTON_PIN, INPUT);
fehlt bei unserer einfachen Schaltung der definierte Zustand.
Wir verwenden deshalb:
pinMode(BUTTON_PIN, INPUT_PULLUP);
HIGH und LOW verwechselt
Bei unserer Schaltung gilt:
nicht gedrückt = HIGH
gedrückt = LOW
Taster löst mehrfach aus
Mögliche Ursache:
Tasterprellen
Dann muss der Tastendruck entprellt werden.
Programm reagiert ständig
Wird nur geprüft:
if (buttonState == LOW)
läuft die Aktion ständig weiter, solange der Taster gehalten wird.
Soll eine Aktion nur einmal pro Tastendruck ausgeführt werden, muss die Zustandsänderung erkannt werden.
Die wichtigsten Taster-Befehle
Für einfache Tasterprojekte reichen bereits wenige Befehle.
Taster vorbereiten:
pinMode(BUTTON_PIN, INPUT_PULLUP);
Taster lesen:
digitalRead(BUTTON_PIN);
Zustand prüfen:
if (buttonState == LOW)
Zustandsänderung erkennen:
if (lastButtonState == HIGH && buttonState == LOW)
Damit können bereits viele Taster-Projekte umgesetzt werden.
Das Wichtigste in 60 Sekunden
Ein Taster ist ein digitaler Eingang.
Eine einfache ESP32-Schaltung besteht aus:
GPIO
│
Taster
│
GND
Mit:
pinMode(BUTTON_PIN, INPUT_PULLUP);
verwenden wir den internen Pull-up-Widerstand des ESP32.
Deshalb gilt:
nicht gedrückt = HIGH
gedrückt = LOW
Mit:
digitalRead(BUTTON_PIN);
lesen wir den Zustand.
Mit:
if (buttonState == LOW)
können wir darauf reagieren.
Soll eine Aktion nur einmal pro Tastendruck ausgeführt werden, müssen wir die Zustandsänderung erkennen:
HIGH → LOW
Und wenn ein Tastendruck scheinbar mehrfach erkannt wird, kann das am:
Tasterprellen
liegen.
Dann benötigen wir eine Entprellung.
Damit sind bereits die wichtigsten Grundlagen für Taster am ESP32 verstanden.
Wie geht es weiter?
Nachdem wir einen digitalen Eingang ausführlich kennengelernt haben, schauen wir uns als Nächstes die andere Seite genauer an:
ESP32 und LEDs
Dabei geht es unter anderem um:
- LED richtig anschließen
- Anode und Kathode
- Vorwiderstand berechnen
- LED ein- und ausschalten
- mehrere LEDs steuern
- Ampelschaltung
- LED blinken lassen
- unterschiedliche Blinkgeschwindigkeiten
- warum
delay()irgendwann problematisch wird millis()stattdelay()- LED dimmen
- PWM einfach erklärt
Damit lernen wir digitale Ausgänge deutlich genauer kennen.
Alle Beiträge dieser Serie
- Teil 1: ESP32 kennenlernen
- Teil 2: ESP32 programmieren
- Teil 3: GPIO verstehen
- Teil 4: Taster anschließen und entprellen (Aktuell)
- Teil 5: LEDs steuern
- Teil 6: Analoge Werte und ADC


Add your first comment to this post