Im letzten Beitrag „ESP32 DC-Motor mit MicroPython: PWM-Drehzahlregelung Schritt für Schritt“ habe ich gezeigt, wie sich ein DC-Motor am ESP32 sicher anschließen und mithilfe von MicroPython, PWM und einem Drehpotentiometer steuern lässt. Dabei lag der Fokus bewusst auf der einfachen Ansteuerung: Der Motor erhält über ein PWM-Signal mehr oder weniger Leistung und dreht sich entsprechend schneller oder langsamer.
Was dabei jedoch offen bleibt, ist eine entscheidende Frage:
Woher weiß der ESP32 eigentlich, wie schnell sich der Motor wirklich dreht?
Denn ein PWM-Wert allein sagt noch nichts über die tatsächliche Drehzahl aus. Last, Reibung oder die Versorgungsspannung haben direkten Einfluss darauf, wie schnell sich ein DC-Motor tatsächlich bewegt.
Genau hier setzt dieser Beitrag an.
In diesem Projekt zeige ich dir, wie du die Drehzahl eines DC-Motors mit dem ESP32 misst – mithilfe einer Gabellichtschranke und einer einfachen Scheibe auf der Motorachse. Auf Basis dieser Messung können wir anschließend nicht nur die aktuelle Drehzahl bestimmen, sondern den Motor auch gezielt auf eine definierte Drehzahl einstellen.




Damit machen wir den nächsten logischen Schritt:
von der reinen Motoransteuerung hin zu einer messbaren und kontrollierten Drehzahl – umgesetzt in MicroPython und mit möglichst wenig zusätzlicher Hardware.
Warum die Drehzahl überhaupt messen?
Im bisherigen Projekt wurde der DC-Motor über ein PWM-Signal gesteuert. Das funktioniert gut, um den Motor schneller oder langsamer drehen zu lassen – doch dabei wird schnell klar:
Ein PWM-Wert allein sagt nichts über die tatsächliche Drehzahl des Motors aus.
Ein DC-Motor reagiert nicht ausschließlich auf das Steuersignal, sondern auch auf äußere Einflüsse. Dazu zählen unter anderem:
- mechanische Last (z. B. ein Getriebe, ein Linearantrieb oder Reibung),
- die Versorgungsspannung,
- sowie der Zustand des Motors selbst.
Wird der Motor stärker belastet, sinkt die Drehzahl – selbst wenn der PWM-Wert unverändert bleibt. Umgekehrt kann sich die Drehzahl erhöhen, wenn die Last wegfällt. Der ESP32 „weiß“ davon jedoch nichts, solange er nur ein PWM-Signal ausgibt.
Genau hier liegt der entscheidende Punkt:
Ohne Messung gibt es kein Feedback.
Erst wenn wir die Drehzahl des Motors erfassen, wissen wir, wie schnell er sich tatsächlich dreht. Dieses Wissen ist die Grundlage für:
- reproduzierbare Drehzahlen,
- definierte Bewegungen,
- und eine gezielte Regelung des Motors.
In der Praxis bedeutet das:
Statt zu sagen „der Motor läuft ungefähr so schnell“, können wir künftig sagen
„der Motor läuft mit 500 Umdrehungen pro Minute“ – unabhängig davon, ob Last anliegt oder nicht.
Die Drehzahlmessung ist damit der nächste logische Schritt auf dem Weg von einer einfachen Motoransteuerung hin zu einer kontrollierten und verlässlichen Motorsteuerung.
Messprinzip: Wie wird die Drehzahl erfasst?
Um die Drehzahl eines DC-Motors zu bestimmen, müssen wir zunächst eine einfache Frage beantworten:
Wie oft dreht sich die Motorwelle innerhalb einer bestimmten Zeit?
Genau darauf basiert das Messprinzip in diesem Projekt.
Grundidee
Auf der Motorachse sitzt eine Scheibe mit einem Loch. Diese Scheibe dreht sich gemeinsam mit dem Motor.
Eine Gabellichtschranke ist so positioniert, dass sich die Scheibe zwischen Sender und Empfänger befindet.

Immer dann, wenn das Loch in der Scheibe durch die Lichtschranke läuft, passiert Folgendes:
- der Lichtstrahl wird kurz nicht unterbrochen,
- die Lichtschranke erzeugt ein Signalwechsel,
- dieser Wechsel wird vom ESP32 als Impuls erkannt.
👉 Ein Impuls entspricht einer vollständigen Umdrehung der Motorachse, da die Scheibe genau ein Loch besitzt.
Von Impulsen zur Drehzahl
Sobald wir diese Impulse zählen, lässt sich die Drehzahl sehr einfach berechnen.
Beispiel:
- Der ESP32 zählt 10 Impulse pro Sekunde
- Das bedeutet: 10 Umdrehungen pro Sekunde
- Hochgerechnet ergibt das:
10 × 60 = 600 Umdrehungen pro Minute (RPM)
Die Berechnung lautet also:
Drehzahl (RPM) = Impulse pro Sekunde × 60
Dieses Verfahren ist bewusst einfach gehalten und ideal für Einsteiger, da:
- keine komplizierte Mathematik nötig ist,
- die Messung gut nachvollziehbar bleibt,
- und die Hardware leicht zu beschaffen ist.
Warum nur ein Loch?
In diesem Projekt verwende ich bewusst nur ein Loch in der Scheibe. Das hat mehrere Vorteile:
- Die Berechnung bleibt übersichtlich
- Jeder Impuls entspricht exakt einer Umdrehung
- Fehlerquellen werden minimiert
Später ließe sich die Auflösung problemlos erhöhen, indem man mehrere Löcher oder Schlitze verwendet. Für den Einstieg ist ein einzelnes Loch jedoch völlig ausreichend.
Vorteil dieses Messverfahrens
Das Messprinzip mit Gabellichtschranke und Lochscheibe bietet mehrere praktische Vorteile:
- berührungslos (kein mechanischer Verschleiß)
- sehr zuverlässig
- gut sichtbar und leicht verständlich
- perfekt kombinierbar mit MicroPython
Damit haben wir nun eine einfache Möglichkeit, die tatsächliche Drehzahl des Motors zu erfassen – und genau dieses Feedback benötigen wir im nächsten Schritt, um den Motor gezielt und reproduzierbar auf eine gewünschte Drehzahl einzustellen.
3D-Druckteile für den Aufbau (Download)
Um den Aufbau reproduzierbar und sauber auf dem Schreibtisch umzusetzen, habe ich für dieses Projekt einige 3D-Druckteile erstellt.
Diese erleichtern nicht nur die Montage von Motor und Gabellichtschranke, sondern sorgen auch für zuverlässige Messergebnisse.
Alle verwendeten 3D-Modelle stelle ich hier und unter Thingiverse kostenlos zum Download zur Verfügung.
Empfohlene Druckeinstellungen:
- Material: PLA
- Layerhöhe: 0,2 mm
- Infill: 20 %
- Support: nein
Aufbau der Schaltung: ESP32 mit Gabellichtschranke, Drehpotentiometer & DC-Motor
Die Schaltung aus dem Beitrag ESP32 DC-Motor mit MicroPython: PWM-Drehzahlregelung Schritt für Schritt nehmen wir einfach und entfernen hier die LEDs und setzen die Gabellichtschranke ein.
Für den Aufbau der Schaltung verwende ich:
- einen ESP32* zbsp. ESP32 D1 R32*,
- einen DC-Motor Typ TT Motor,
- ein 50 kOhm Drehpotentiometer*,
- ein IRF520 Modul*,
- ein 0,91″ I2C OLED Display*,
- eine Gabellichtschranke*, 10mm Öffnung, sowie
- Breadboardkabel, männlich-männlich*
- Breadboardkabel, männlich-weiblich*
- ein 400 Pin Breadboard*
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Verwendete Gabellichtschranke
Für die Drehzahlmessung kommt in diesem Projekt eine Infrarot-Gabellichtschranke zum Einsatz, wie sie häufig in Arduino- und Raspberry-Pi-Projekten verwendet wird. Diese Sensoren eignen sich ideal, um Impulse, Geschwindigkeiten oder Drehzahlen zu erfassen – zum Beispiel in Verbindung mit einer Lochscheibe oder einem Encoder.





Das Funktionsprinzip ist einfach:
In der Gabel sitzen ein Infrarot-Sender und ein Empfänger. Wird der Lichtstrahl unterbrochen oder freigegeben (z. B. durch ein Loch in einer rotierenden Scheibe), erzeugt der Sensor ein entsprechendes Signal am Ausgang. Genau dieses Signal nutzen wir, um die Umdrehungen des Motors zu zählen.
Pinbelegung
- VCC: Versorgungsspannung (3,3 V – 5 V)
- GND: Masse
- DO: Digitaler Ausgang (Impuls-Signal, im Projekt verwendet)
- AO: Analoger Ausgang (hier nicht genutzt)
Technische Daten
- Infrarot-Sensor: F249
- Komparator: LM393
- Betriebsspannung: 3,3 V – 5 V
- Abmessungen: 32 × 14 × 7 mm
- Gewicht: ca. 8 g
- 3 mm Montagebohrung zur einfachen Befestigung
Für dieses Projekt nutze ich ausschließlich den digitalen Ausgang (DO). Er liefert ein sauberes HIGH/LOW-Signal, das sich perfekt für die Auswertung per Interrupt am ESP32 eignet. Der analoge Ausgang wäre zwar ebenfalls nutzbar, bringt hier jedoch keinen zusätzlichen Vorteil und würde die Auswertung unnötig komplizieren.
Programmieren der Schaltung in MicroPython
Wie zuvor programmieren wir die Schaltung in MicroPython unter Thonny. Wie du deinen ESP32 für MicroPython flashen musst habe ich dir bereits im Beitrag Flashen des ESP32-D0WDQ6 gezeigt, mit Thonny geht das noch deutlich einfacher wie du im YouTube Video sehen kannst.
Für das ansteuern des 0,91″ I²C OLED Displays mit SSD1306 Treiberchip benötigen wir ein zusätzliches Modul welches wir vom GitHub Repository cwyark/ssd1306.py laden. Dafür legen wir auf dem Mikrocontroller das Verzeichnis lib an und laden diese Datei mit Thonny hoch. Wie das genau funktioniert zeige ich dir im oben verlinkten Video.
"""
Titel:
ESP32 DC-Motor: Drehzahl messen mit Gabellichtschranke, OLED (I2C) & MicroPython
Beschreibung:
Dieses MicroPython-Programm steuert einen DC-Motor per PWM am ESP32 (über ein IRF520 MOSFET-Modul)
und misst gleichzeitig die Drehzahl (RPM / U/min) mit einer Gabellichtschranke.
- Drehpotentiometer (ADC) -> bestimmt die Motorleistung (PWM Duty)
- Gabellichtschranke (Digital Input) -> erzeugt pro Umdrehung einen Impuls (bei 1 Loch = 1 Impuls/U)
- Zeitfenster (window_ms) -> Impulse pro Zeit -> Umrechnung auf U/min
- OLED (SSD1306, I2C) -> zeigt die berechnete Drehzahl an
Wichtig:
Ein DC-Motor darf niemals direkt am ESP32 GPIO betrieben werden!
Der ESP32 liefert nur das PWM-Steuersignal, die Motorleistung kommt aus einer externen Versorgung
und wird über das IRF520-Modul geschaltet.
Blogbeitrag (Aufbau, Schaltung, Erklärung):
https://draeger-it.blog/esp32-dc-motor-drehzahl-messen-mit-gabellichtschranke-micropython/
Autor: Stefan Draeger
Stand: Januar 2026
"""
from machine import Pin, ADC, PWM, I2C
import time
import ssd1306
# ------------------------------------------------------------
# Pinbelegung / Hardware-Setup
# ------------------------------------------------------------
drehPotiPin = 2 # ADC: Schleifer des Drehpotentiometers
motorPin = 4 # PWM-Ausgang: SIG am IRF520-Modul (NICHT direkt an Motor!)
lichtschrankePin = 17 # Digital-Eingang: DO der Gabellichtschranke (Impulse)
# OLED I2C Pins (ESP32 Standardbelegung kann variieren!)
SCL_PIN = 18
SDA_PIN = 19
# OLED Displaydaten (häufig: 128x32 oder 128x64, Adresse meist 0x3C)
OLED_W = 128
OLED_H = 32
OLED_ADDR = 0x3C
# ------------------------------------------------------------
# Drehpotentiometer (ADC) konfigurieren
# ------------------------------------------------------------
# ADC liefert bei 12 Bit Werte von 0..4095
poti = ADC(Pin(drehPotiPin))
poti.atten(ADC.ATTN_11DB) # Messbereich bis ca. 3,3V (wichtig bei 3,3V Versorgung)
poti.width(ADC.WIDTH_12BIT) # 12 Bit Auflösung -> 0..4095
# ------------------------------------------------------------
# Motorsteuerung per PWM konfigurieren
# ------------------------------------------------------------
motor_pwm = PWM(Pin(motorPin))
motor_pwm.freq(1000) # 1 kHz PWM (Startwert, kann man später anpassen)
# ------------------------------------------------------------
# OLED Display per I2C initialisieren
# ------------------------------------------------------------
# I2C-Bus 0, 400 kHz = schnell genug für flüssige Updates
i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)
# SSD1306 OLED Treiber initialisieren
oled = ssd1306.SSD1306_I2C(OLED_W, OLED_H, i2c, addr=OLED_ADDR)
# Kurzer Startbildschirm, damit man sieht: Display läuft
oled.fill(0) # Display löschen
oled.text("ESP32 DC Motor", 0, 10) # Text schreiben
oled.show() # anzeigen
time.sleep(1)
# ------------------------------------------------------------
# Variablen für die Drehzahlmessung
# ------------------------------------------------------------
pulse_count = 0 # Zählt die erkannten Impulse innerhalb eines Zeitfensters
# "armed" dient als einfache Entprell-/Sperrlogik:
# Idee: Nach einem erkannten Impuls wird kurz "gesperrt", bis der Sensor wieder "frei" ist.
armed = True
# Zeitfenster für die RPM-Berechnung:
# 1000 ms -> wir zählen 1 Sekunde lang Impulse
window_ms = 1000
last_window_ms = time.ticks_ms()
# ------------------------------------------------------------
# Interrupt-Handler: wird bei jedem Impuls aufgerufen
# ------------------------------------------------------------
# Achtung Praxis:
# Im Interrupt sollte man sehr wenig machen. print() ist zum Debuggen okay,
# kann aber bei hohen Drehzahlen die Messung verfälschen oder das System belasten.
def interruption_handler(pin):
global pulse_count, armed
# Aktuellen Pegel am Sensorpin lesen (0 oder 1)
level = pin.value()
# Wenn Pegel 0 und "armed", dann zählt das als neuer Impuls
if level == 0 and armed:
pulse_count += 1
armed = False
print(1) # Debug: Impuls erkannt
# Wenn Pegel wieder 1 ist, "entsperren" wir für den nächsten Impuls
elif level == 1:
armed = True
print(0) # Debug: wieder bereit
# ------------------------------------------------------------
# Lichtschranke als Eingang + Interrupt aktivieren
# ------------------------------------------------------------
lichtschranke = Pin(lichtschrankePin, Pin.IN)
# Trigger aktuell: FALLING
# Hinweis: Deine "armed"-Logik arbeitet aber zusätzlich mit level==1 zum Entsperren,
# dafür wäre häufig auch IRQ_RISING oder IRQ_FALLING|IRQ_RISING sinnvoll.
# (Da dein Code läuft, lassen wir es hier bewusst so wie bei dir.)
lichtschranke.irq(trigger=Pin.IRQ_FALLING, handler=interruption_handler)
# ------------------------------------------------------------
# Funktion: Motor per PWM steuern
# ------------------------------------------------------------
def set_motor_speed(value: int):
"""
Wandelt den ADC-Wert (0..4095) in einen PWM-Duty (0..1023) um
und setzt damit die Motorgeschwindigkeit über das IRF520-Modul.
"""
# ADC (0..4095) -> Duty (0..1023)
duty = int(value / 4095 * 1023)
print(duty) # Debug-Ausgabe
motor_pwm.duty(duty)
# ------------------------------------------------------------
# Hauptschleife
# ------------------------------------------------------------
last_value = -1 # Letzter Poti-Wert, um unnötige Updates zu vermeiden
while True:
# --------------------------------------------------------
# 1) Potentiometer lesen & Motor nur bei Änderung anpassen
# --------------------------------------------------------
poti_value = poti.read()
# "Deadband" gegen Rauschen: erst ab einer Änderung > 10 wird neu gesetzt
if abs(poti_value - last_value) > 10:
set_motor_speed(poti_value)
last_value = poti_value
# --------------------------------------------------------
# 2) Drehzahl in festen Zeitfenstern berechnen
# --------------------------------------------------------
now = time.ticks_ms()
# Prüfen, ob unser Zeitfenster (z. B. 1000 ms) abgelaufen ist
if time.ticks_diff(now, last_window_ms) >= window_ms:
# Impulse sichern und Zähler für das nächste Fenster zurücksetzen
pc = pulse_count
pulse_count = 0
last_window_ms = now
# RPM-Berechnung:
# pc = Impulse pro window_ms
# Umrechnung auf U/min:
# RPM = Impulse * (60000 / window_ms)
# Bei window_ms=1000 -> Faktor 60
rpm = pc * (60000 // window_ms)
print("RPM:", rpm)
# OLED aktualisieren
oled.fill(0)
oled.text(str(rpm) + " U/min", 0, 10)
oled.show()
# --------------------------------------------------------
# 3) Kleine Pause zur CPU-Entlastung
# --------------------------------------------------------
time.sleep(0.05)
Fazit
Das Messen der Umdrehungen eines DC-Motors klingt im ersten Moment ziemlich einfach – und grundsätzlich ist es das auch. Hat man den grundlegenden Aufbau verstanden, lassen sich die Impulse der Gabellichtschranke sauber erfassen und in eine Drehzahl umrechnen.
In der Praxis hat sich aber schnell gezeigt, dass es nicht immer sofort zuverlässig funktioniert. Der Beitrag ist im Laufe der Zeit gewachsen, weil es einige Anläufe gebraucht hat, bis die Werte wirklich sinnvoll und stabil ausgelesen wurden. Ein typisches Problem dabei waren doppelt erkannte Impulse, die die Berechnung der Drehzahl verfälschen oder sogar komplett unbrauchbar machen.
Gerade solche Effekte sieht man erst, wenn der Motor tatsächlich läuft und nicht nur auf dem Papier existiert. Mechanische Ungenauigkeiten, kleine Vibrationen oder ungünstige Flanken am Sensorsignal spielen hier eine größere Rolle, als man anfangs erwartet.
Wenn diese Hürden einmal genommen sind, ist die Drehzahlmessung jedoch kein Hexenwerk mehr. Genau dieses Verständnis ist die Grundlage für den nächsten Schritt: den Motor nicht nur zu messen, sondern seine Drehzahl gezielt zu regeln.
Letzte Aktualisierung am: 10. Januar 2026



