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:

Wichtig: Auch hier gilt: Der XIAO arbeitet mit 3,3 V. Der Taster wird an 3V3 angeschlossen – niemals an 5V.


1. Benötigte Materialien

Zusätzlich zum Aufbau aus Teil 1 (XIAO, Breadboard, rote LED mit 1 kΩ an D1):

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:

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.

Taster gedrückt

3V3

Taster geschlossen

GPIO4 → liest 1

10 kΩ

GND

Taster losgelassen

3V3

Taster offen

GPIO4 → liest 0

10 kΩ

GND

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:

I=UR=3,3 V10 000 Ω=0,00033 A=0,33 mAI = \frac{U}{R} = \frac{3{,}3\,\text{V}}{10\,000\,\Omega} = 0{,}00033\,\text{A} = 0{,}33\,\text{mA}
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?

Digital- oder Analogpin? Ein Taster liefert nur zwei Zustände (an/aus) – das ist ein digitales Signal. Wir lesen ihn deshalb mit Pin.IN. Dass D3 zusätzlich die Beschriftung A3 trä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

Steckplan: Taster an D3 und LED an D1 auf dem Breadboard
Abb. 1: Steckplan: Taster an D3 und LED an D1 auf dem Breadboard

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

XIAO: 3V3

Taster
(diagonale Beinchen)

XIAO: D3 / GPIO4

Pull-down: 10 kΩ

XIAO: GND

XIAO: D1 / GPIO2

Vorwiderstand: 1 kΩ

Rote LED
(Anode → Kathode)

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


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

Experiment: Was passiert ohne Widerstand?

  1. Programm stoppen.
  2. Den 10-kΩ-Widerstand vorsichtig herausziehen.
  3. Programm erneut starten und den Taster nicht drücken.
  4. Halte einen Finger in die Nähe des Drahts an D3 oder 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)

ja

nein

Taster lesen

gedrückt?
value = 1

LED an

LED aus

10 ms warten

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

Taster gedrückt

Taster gedrückt

LED_aus

LED_an

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 einfach jetzt - letzte_aenderung? Der Millisekunden-Zähler ticks_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.

im ESP32-S3

3,3 V

interner Pull-up
≈ 45 kΩ

GPIO4

Pin D3

Taster

XIAO: GND

Umbau:

  1. 10-kΩ-Widerstand entfernen.
  2. Das Taster-Kabel von 3V3 abziehen und an GND stecken.

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

  1. Zähler: Erweitere Programm 3 (Version B) so, dass in der Shell mitgezählt wird, wie oft der Taster gedrückt wurde.
  2. Prellen sichtbar machen: Setze ENTPRELLZEIT_MS = 0 und 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?
  3. 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.
  4. 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 random und random.randint(2000, 5000).
  5. Zwei Taster: Baue einen zweiten Taster an D0 / GPIO1 ein – 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.