XIAO ESP32S3 – Erweiterung: Taster als Ein/Aus-Schalter
Diese Erweiterungslektion baut auf der ersten Anleitung (XIAO ESP32S3 mit MicroPython und Thonny) auf. Dort hast du MicroPython geflasht und eine externe rote LED an D1 / GPIO2 blinken lassen. Jetzt kommt der umgekehrte Weg dazu: Der Mikrocontroller soll nicht nur etwas ausgeben, sondern auch etwas einlesen – den Zustand eines Tasters.
Ziele:
- verstehen, warum ein Eingang einen Pull-down- oder Pull-up-Widerstand braucht
- einen Taster mit 10-kΩ-Pull-down-Widerstand anschließen und auslesen
- die LED mit dem Taster steuern – erst „solange gedrückt“, dann als Ein/Aus-Schalter
- das Prellen eines Tasters kennenlernen und per Software beheben
- die Variante mit dem internen Pull-up-Widerstand des ESP32-S3 kennenlernen
Wichtig: Auch hier gilt: Der XIAO arbeitet mit 3,3 V. Der Taster wird an
3V3angeschlossen – niemals an5V.
1. Benötigte Materialien
Zusätzlich zum Aufbau aus Teil 1 (XIAO, Breadboard, rote LED mit 1 kΩ an D1):
- Drucktaster (Mikrotaster, 4 Beinchen, z. B. 6 × 6 mm)
- Widerstand 10 kΩ (Farbringe braun–schwarz–orange–gold)
- 2–3 Jumperkabel
Verwechslungsgefahr: 1 kΩ (braun–schwarz–rot) und 10 kΩ (braun–schwarz–orange) sehen sehr ähnlich aus. Der dritte Ring entscheidet!
2. Warum braucht ein Eingang einen Widerstand?
Das Problem: der „schwebende“ Eingang
Ein GPIO-Pin, der als Eingang geschaltet ist, misst nur, ob an ihm eine Spannung nahe 0 V (→ 0) oder nahe 3,3 V (→ 1) liegt. Dabei fließt fast kein Strom in den Pin hinein – der Eingang ist sehr hochohmig.
Schließt man einen Taster nur zwischen 3V3 und den Pin an, passiert Folgendes:
- Taster gedrückt: Pin ist mit 3,3 V verbunden → eindeutig
1. - Taster losgelassen: Pin ist mit nichts verbunden. Er „schwebt“ (engl. floating) und reagiert auf jede elektrische Störung – schon das Annähern eines Fingers kann den Wert ändern. Ergebnis: zufällige Nullen und Einsen.
Die Lösung: Pull-down-Widerstand
Ein Widerstand von dem Pin nach GND „zieht“ den Pin auf 0 V herunter, solange der Taster offen ist. Wird der Taster gedrückt, liegt der Pin direkt an 3,3 V – der Widerstand verhindert dabei, dass 3V3 und GND kurzgeschlossen werden.
| Tasterzustand | Spannung am Pin | taster.value() |
|---|---|---|
| losgelassen | ≈ 0 V (über 10 kΩ an GND) | 0 |
| gedrückt | ≈ 3,3 V (direkt an 3V3) | 1 |
Warum gerade 10 kΩ?
Bei gedrücktem Taster fließt ein Strom von 3V3 durch den Widerstand nach GND:
| Widerstand | Strom beim Drücken | Beurteilung |
|---|---|---|
| 100 Ω | 33 mA | Viel zu viel – verschwendet Strom, Widerstand wird warm |
| 10 kΩ | 0,33 mA | Üblicher Standardwert: sparsam und trotzdem störsicher |
| 1 MΩ | 0,0033 mA | Zu schwach – der Pin wird anfällig für Störungen |
10 kΩ ist ein bewährter Kompromiss: wenig Strom, aber „kräftig“ genug, um den Pin sicher auf 0 V zu halten.
3. Den richtigen Pin wählen
Wir verwenden für den Taster D3, das ist GPIO4 im ESP32-S3 – im Code also Pin(4, ...).
Warum gerade dieser Pin?
D3 / GPIO4ist ein ganz normaler GPIO ohne Zusatzaufgabe im Kurs (kein I²C, SPI oder UART).D2(GPIO3) meiden wir: GPIO3 ist beim ESP32-S3 ein sogenannter Strapping-Pin. Solche Pins liest der Chip beim Einschalten aus, um seinen Startmodus festzulegen. Ein Taster mit Widerstand daran kann den Start stören.D1ist schon durch die LED belegt.
Digital- oder Analogpin? Ein Taster liefert nur zwei Zustände (an/aus) – das ist ein digitales Signal. Wir lesen ihn deshalb mit
Pin.IN. DassD3zusätzlich die BeschriftungA3trägt, heißt nur, dass dieser Pin auch analoge Spannungen messen könnte. Für den Taster brauchen wir das nicht.
Ausschnitt aus dem Pinout
Board von oben, USB-C-Buchse zeigt nach oben:
| linke Seite | ▲ USB-C ▲ | rechte Seite |
|---|---|---|
| D0 · GPIO1 | ┌───────┐ | 5V (VIN) – nicht benutzen! |
| D1 · GPIO2 ◀ LED | │ │ | GND ◀ LED + Taster |
| D2 · GPIO3 ⚠ Strapping | │ XIAO │ | 3V3 ◀ Taster |
| D3 · GPIO4 ◀ Taster | │ ESP32 │ | D10 · GPIO9 |
| D4 · GPIO5 | │ S3 │ | D9 · GPIO8 |
| D5 · GPIO6 | │ │ | D8 · GPIO7 |
| D6 · GPIO43 | └───────┘ | D7 · GPIO44 |
4. Schaltung aufbauen
Der Taster auf dem Breadboard
Ein Mikrotaster hat vier Beinchen, aber nur zwei Kontakte: Je zwei Beinchen sind im Inneren fest miteinander verbunden. Welche das sind, sieht man von außen nicht sicher.
Sichere Regel: Benutze immer zwei diagonal gegenüberliegende Beinchen. Die sind garantiert nicht fest verbunden, sondern werden erst beim Drücken verbunden.
text ● ─ ─ ─ ●
│ ┌───┐ │ ① und ② diagonal gegenüber:
│ │ │ │ → werden nur beim Drücken verbunden
│ └───┘ │
① ②
Setze den Taster so auf das Breadboard, dass er über der Mittelrinne sitzt. Dann liegen seine Beinchen in getrennten Reihen.
Steckplan
So liest du den Plan: Die Zahlen unten sind die Spalten, die Buchstaben links die Reihen des Breadboards. a17 heißt also: Spalte 17, Reihe a. Felder mit gleicher Hintergrundfarbe sind elektrisch verbunden – in der oberen Hälfte (a–e) und in der unteren Hälfte (f–j) jeweils eine Spalte, auf der blauen Schiene die ganze Zeile.
Schaltbild als Mermaid
Vereinfacht als Text:
textTaster: XIAO 3V3 ── Taster ──┬── XIAO D3 / GPIO4
└── 10 kΩ ── XIAO GND
LED: XIAO D1 / GPIO2 ── 1 kΩ ── Anode (+) LED Kathode (−) ── XIAO GND
Tipp: Nutze die blaue (−) Längsschiene des Breadboards als gemeinsame GND-Leitung. Dann brauchen LED und Taster nur ein einziges Kabel zum
GND-Pin des XIAO.
Checkliste vor dem Einschalten
- Taster an
3V3– nicht an5V - 10-kΩ-Widerstand (braun–schwarz–orange) vom Pin
D3nachGND - Taster sitzt über der Mittelrinne, diagonale Beinchen benutzt
- LED-Schaltung aus Teil 1 unverändert an
D1
5. Programm 1: Taster auslesen
Dieses Programm zeigt nur an, was der Pin gerade „sieht“.
pythonfrom machine import Pin
import time
print("Taster-Test an D3 / GPIO4 – Abbruch mit dem Stop-Knopf")
# Eingang ohne internen Widerstand (pull=None),
# denn wir haben einen externen 10-kΩ-Pull-down eingebaut.
taster = Pin(4, Pin.IN, pull=None)
while True:
print("Taster:", taster.value())
time.sleep_ms(200)
Erwartetes Ergebnis
- Taster losgelassen: In der Shell erscheint immer wieder
Taster: 0. - Taster gedrückt: Es erscheint
Taster: 1. - Mit dem roten Stop-Knopf in Thonny beendest du die Endlosschleife.
Experiment: Was passiert ohne Widerstand?
- Programm stoppen.
- Den 10-kΩ-Widerstand vorsichtig herausziehen.
- Programm erneut starten und den Taster nicht drücken.
- Halte einen Finger in die Nähe des Drahts an
D3oder berühre ihn.
Beobachtung: Die Werte können zufällig zwischen 0 und 1 wechseln oder sich „unerklärlich“ verhalten. Das ist der schwebende Eingang aus Abschnitt 2. Danach den Widerstand wieder einsetzen!
6. Programm 2: LED leuchtet, solange der Taster gedrückt ist
Jetzt verbinden wir Eingang und Ausgang: Der Zustand des Tasters wird direkt an die LED weitergegeben.
pythonfrom machine import Pin
import time
taster = Pin(4, Pin.IN, pull=None) # D3 / GPIO4, externer Pull-down
led = Pin(2, Pin.OUT, value=0) # D1 / GPIO2, startet aus
print("LED leuchtet, solange der Taster gedrückt ist.")
while True:
led.value(taster.value()) # 1 → LED an, 0 → LED aus
time.sleep_ms(10)
Das ist noch kein Ein/Aus-Schalter, sondern eine Klingeltaste: Loslassen schaltet die LED sofort wieder aus.
7. Das Problem mit dem Prellen
Ein echter Ein/Aus-Schalter soll bei jedem Drücken umschalten: einmal drücken → an, nochmal drücken → aus. Dafür muss das Programm erkennen, wann der Taster gedrückt wird – also den Wechsel von 0 nach 1 (eine steigende Flanke).
Dabei gibt es eine Überraschung: Mechanische Kontakte prellen. Die Metallzungen im Taster federn beim Schließen ein paar Mal zurück, bevor sie fest aufliegen. Für den Mikrocontroller, der tausende Male pro Sekunde nachsehen kann, sieht ein einziger Tastendruck dann so aus:
textTaster: losgelassen | Druck (prellt) | fest gedrückt
Signal: 0 0 0 0 0 0 0 | 1 0 1 1 0 0 1 0 1 1 | 1 1 1 1 1 1
└── meist nur wenige Millisekunden ──┘
Ohne Gegenmaßnahme zählt das Programm mehrere „Drücke“ – die LED schaltet dann zufällig an oder aus.
Lösung (Entprellen per Software): Nach einer erkannten Flanke wartet das Programm eine kurze Zeit (z. B. 50 ms) und ignoriert in dieser Zeit alle weiteren Wechsel. 50 ms sind für das Prellen lang genug, für einen Menschen aber kaum spürbar.
8. Programm 3: Ein/Aus-Schalter
Die Idee als Zustandsdiagramm
Das Loslassen des Tasters ändert nichts – nur das Drücken schaltet um.
Version A: einfach (zum Einstieg)
Diese Version wartet nach dem Drücken, bis der Taster wieder losgelassen wird.
pythonfrom machine import Pin
import time
taster = Pin(4, Pin.IN, pull=None) # D3 / GPIO4, externer Pull-down
led = Pin(2, Pin.OUT, value=0) # D1 / GPIO2
ENTPRELLZEIT_MS = 50
print("Ein/Aus-Schalter – einfache Version")
while True:
if taster.value() == 1: # Taster wurde gedrückt
led.toggle() # LED umschalten: an ↔ aus
print("LED ist jetzt", "an" if led.value() else "aus")
time.sleep_ms(ENTPRELLZEIT_MS) # Prellen beim Drücken abwarten
while taster.value() == 1: # warten, bis losgelassen wird
time.sleep_ms(10)
time.sleep_ms(ENTPRELLZEIT_MS) # Prellen beim Loslassen abwarten
time.sleep_ms(10)
Nachteil: Solange jemand den Taster festhält, hängt das Programm in der inneren while-Schleife und kann nichts anderes tun – zum Beispiel keine zweite LED blinken lassen.
Version B: ohne Blockieren (für Fortgeschrittene)
Diese Version merkt sich den letzten Zustand und den Zeitpunkt des letzten Wechsels. Die Schleife läuft dabei ununterbrochen weiter.
pythonfrom machine import Pin
import time
taster = Pin(4, Pin.IN, pull=None) # D3 / GPIO4, externer Pull-down
led = Pin(2, Pin.OUT, value=0) # D1 / GPIO2
ENTPRELLZEIT_MS = 50
letzter_zustand = taster.value()
letzte_aenderung = time.ticks_ms()
print("Ein/Aus-Schalter – Version ohne Blockieren")
while True:
zustand = taster.value()
jetzt = time.ticks_ms()
# Hat sich der Zustand geändert – und ist die Entprellzeit vorbei?
if zustand != letzter_zustand and time.ticks_diff(jetzt, letzte_aenderung) > ENTPRELLZEIT_MS:
letzter_zustand = zustand
letzte_aenderung = jetzt
if zustand == 1: # steigende Flanke: gerade gedrückt
led.toggle()
print("LED ist jetzt", "an" if led.value() else "aus")
# Hier könnte das Programm noch andere Dinge erledigen.
time.sleep_ms(5)
Warum
time.ticks_diff()statt einfachjetzt - letzte_aenderung? Der Millisekunden-Zählerticks_ms()läuft irgendwann über und beginnt wieder bei 0.ticks_diff()rechnet den Abstand trotzdem richtig aus.
Als Dauerprogramm speichern
Wie in Teil 1: Programm zuerst mit Ausführen testen, dann Datei → Speichern unter… → MicroPython device und exakt main.py als Namen wählen. Nach einem Druck auf RST arbeitet der XIAO auch ohne PC als Lichtschalter (Stromversorgung über USB-Netzteil oder Powerbank).
9. Variante: interner Pull-up-Widerstand
Der ESP32-S3 hat in jedem GPIO-Pin eingebaute, zuschaltbare Widerstände. Laut Datenblatt liegen sie typischerweise bei etwa 45 kΩ. Damit lässt sich der externe Widerstand einsparen.
Wir nutzen dafür den Pull-up: Der interne Widerstand zieht den Pin auf 3,3 V hoch, und der Taster verbindet ihn beim Drücken mit GND.
Umbau:
- 10-kΩ-Widerstand entfernen.
- Das Taster-Kabel von
3V3abziehen und anGNDstecken.
Code-Änderung:
pythontaster = Pin(4, Pin.IN, Pin.PULL_UP) # interner Pull-up eingeschaltet
Achtung, die Logik dreht sich um!
| Tasterzustand | Pull-down extern (bisher) | Pull-up intern (neu) |
|---|---|---|
| losgelassen | 0 |
1 |
| gedrückt | 1 |
0 |
Man nennt so einen Eingang active-low – genau wie die gelbe Onboard-LED aus Teil 1, die bei 0 leuchtet. Im Programm prüfst du deshalb:
pythongedrueckt = (taster.value() == 0)
Vergleich der beiden Varianten
| Pull-down extern (10 kΩ) | Pull-up intern (≈ 45 kΩ) | |
|---|---|---|
| Bauteile | Taster + Widerstand | nur Taster |
| Taster verbunden mit | 3V3 |
GND |
| gedrückt bedeutet | 1 („logisch“, leicht zu verstehen) |
0 (active-low) |
| Störsicherheit | gut | etwas geringer (schwächerer Widerstand) |
| typischer Einsatz | Lernen, längere Kabel | kompakte Schaltungen, Profi-Praxis |
10. Bonus: Der BOOT-Taster ist auch ein Taster
Der kleine B / BOOT-Taster auf dem XIAO hängt am Pin GPIO0 und verbindet ihn beim Drücken mit GND – also genau die Pull-up-Schaltung aus Abschnitt 9, nur fest eingebaut. Nach dem Start kannst du ihn deshalb im Programm abfragen:
pythonfrom machine import Pin
import time
boot_taster = Pin(0, Pin.IN, Pin.PULL_UP) # BOOT-Taster auf dem Board
while True:
if boot_taster.value() == 0: # active-low: 0 = gedrückt
print("BOOT-Taster gedrückt!")
time.sleep_ms(200)
Wichtig: GPIO0 ist ein Strapping-Pin. Wird BOOT beim Einschalten oder bei RST gedrückt gehalten, startet der XIAO im Flash-Modus statt mit deinem Programm – das kennst du aus Teil 1. Während das Programm läuft, ist das Drücken unproblematisch.
11. Fehlersuche
| Beobachtung | Mögliche Ursache | Abhilfe |
|---|---|---|
Wert ist immer 1 |
Taster-Beinchen innen fest verbunden (falsches Paar benutzt) | Taster um 90° drehen oder diagonale Beinchen nehmen |
Wert ist immer 0 |
Taster nicht an 3V3, falscher Pin, Taster steht nicht über der Mittelrinne |
Verkabelung mit Abschnitt 4 vergleichen; im Code Pin(4, ...) prüfen |
| Werte springen zufällig | Pull-down-Widerstand fehlt oder steckt in der falschen Reihe | Widerstand muss zwischen D3 und GND liegen |
| Ein Druck schaltet mehrfach | Prellen nicht abgefangen | Programm 3 benutzen, ggf. ENTPRELLZEIT_MS auf 80 erhöhen |
Bei Pull-up-Variante: gedrückt = 1? |
Taster noch an 3V3 statt an GND |
Kabel an GND umstecken |
| XIAO startet nicht mehr normal | main.py hängt in einer Schleife |
In Thonny Stop drücken, main.py umbenennen (siehe Teil 1) |
12. Aufgaben
- Zähler: Erweitere Programm 3 (Version B) so, dass in der Shell mitgezählt wird, wie oft der Taster gedrückt wurde.
- Prellen sichtbar machen: Setze
ENTPRELLZEIT_MS = 0und drücke mehrmals. Wie oft schaltet die LED pro Druck? Erhöhe den Wert schrittweise. Ab welcher Zeit verhält sich der Taster zuverlässig? - Langer und kurzer Druck: Ein kurzer Druck (< 1 s) schaltet die LED um, ein langer Druck (≥ 1 s) lässt sie dreimal schnell blinken. Tipp: Merke dir mit
time.ticks_ms()den Zeitpunkt des Drückens und berechne beim Loslassen die Dauer. - Reaktionsspiel: Die LED geht nach einer zufälligen Wartezeit zwischen 2 und 5 Sekunden an. Miss mit
time.ticks_ms(), wie schnell der Taster gedrückt wird, und gib die Reaktionszeit in Millisekunden aus. Tipp:import randomundrandom.randint(2000, 5000). - Zwei Taster: Baue einen zweiten Taster an
D0 / GPIO1ein – diesmal mit internem Pull-up. Taster 1 schaltet die LED ein, Taster 2 schaltet sie aus.
13. Ausblick: Interrupts
In allen Programmen hier fragt die Schleife den Taster ständig ab (Polling). Mikrocontroller können stattdessen auch automatisch eine Funktion aufrufen, sobald sich ein Pin ändert – das nennt man Interrupt. In MicroPython geht das mit taster.irq(...). Interrupts haben aber eigene Regeln (die aufgerufene Funktion muss sehr kurz sein, und das Prellen löst trotzdem mehrere Interrupts aus). Für den Einstieg ist Polling mit Entprellen deshalb die robustere Wahl.
14. Quellen und weiterführende Links
- MicroPython: Kurzreferenz ESP32 (Abschnitt Pins and GPIO): https://docs.micropython.org/en/latest/esp32/quickref.html#pins-and-gpio
- MicroPython: Klasse
machine.Pin(Pull-up/Pull-down,irq): https://docs.micropython.org/en/latest/library/machine.Pin.html - MicroPython: Regeln für Interrupt-Handler: https://docs.micropython.org/en/latest/reference/isr_rules.html
- Seeed Studio: Pin-Multiplexing XIAO ESP32S3 (Pin-Zuordnung, BOOT = GPIO0, Taster-Beispiel mit Pull-up): https://wiki.seeedstudio.com/xiao_esp32s3_pin_multiplexing/
- Espressif ESP-IDF: GPIO beim ESP32-S3 (Strapping-Pins GPIO0, GPIO3, GPIO45, GPIO46): https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/gpio.html