Multitasking am ESP32: Sensoren lesen und WLAN gleichzeitig bedienen ist in der Praxis weniger Magie als saubere Architektur. Viele Projekte starten mit einem simplen Loop: Sensor auslesen, Wert ausgeben, kurz warten. Sobald jedoch WLAN, MQTT, HTTP-Requests, Webserver oder OTA-Updates dazukommen, wird der Code anfällig: Messwerte stottern, der Webserver reagiert träge, MQTT reconnectet unzuverlässig oder die Firmware „hängt“ scheinbar zufällig. Der Grund ist fast immer derselbe: Blockierende Abläufe (z. B. delay, lange Sensor-Readouts, große JSON-Verarbeitung) kollidieren mit Netzwerk-Stacks, die regelmäßige CPU-Zeit benötigen. Der ESP32 bietet hier einen großen Vorteil: Er bringt (je nach Variante) Dual-Core-CPU und FreeRTOS mit, sodass parallele Tasks möglich sind. Damit Multitasking wirklich stabil funktioniert, müssen Sie aber verstehen, wie Scheduling, Prioritäten, Task-Stacks, Queues und Zeitsteuerung zusammenspielen. Dieser Leitfaden zeigt praxisnah, wie Sie Sensordaten zuverlässig erfassen und gleichzeitig WLAN-Dienste bedienen – ohne Spaghetti-Code, ohne Timing-Glitches und ohne Watchdog-Resets.
Warum „gleichzeitig“ am ESP32 oft scheitert
Auf Mikrocontrollern bedeutet „gleichzeitig“ in den meisten Fällen „schnell abwechselnd“. Wenn ein Teil Ihrer Firmware lange am Stück läuft (z. B. Sensorabfrage über I2C mit Retries, HTTP-Download, TLS-Handshake, Logging), bekommt ein anderer Teil zu wenig Zeit. Besonders WLAN ist empfindlich, weil Netzwerk-Stacks Puffer verwalten, Timeouts einhalten und Hintergrundarbeiten erledigen müssen. Typische Symptome eines schlecht strukturierten Systems sind:
- Messaussetzer: Sensorwerte kommen unregelmäßig oder sprunghaft.
- Träge Netzwerkantworten: Webserver reagiert nur sporadisch, MQTT-Latenz steigt.
- Reconnect-Schleifen: WLAN/MQTT „fängt sich“ nach einem Ausfall nicht sauber.
- Watchdog-Resets: lange Blockaden verhindern, dass Systemaufgaben rechtzeitig laufen.
- „Random“ Abstürze: Stack zu klein, Heap fragmentiert, Race Conditions.
Die Lösung ist nicht, „noch mehr delays“ einzubauen oder Sensoren seltener zu lesen, sondern die Firmware in klar getrennte, kooperierende Einheiten zu zerlegen.
FreeRTOS als Basis: Was Multitasking am ESP32 wirklich bedeutet
Viele ESP32-Boards nutzen FreeRTOS als Betriebssystemkern. FreeRTOS teilt die CPU-Zeit in Tasks auf, die anhand von Prioritäten und Zuständen (laufend, blockiert, bereit) geplant werden. Das zentrale Konzept: Ein Task sollte möglichst oft blockieren, wenn er nichts zu tun hat, damit andere Tasks laufen können. „Blockieren“ heißt dabei nicht „busy waiting“, sondern kontrolliertes Warten auf Ereignisse, Zeitpunkte oder Daten (z. B. Queue, Semaphore, Delay in Ticks).
- Tasks: getrennte Ausführungseinheiten für Sensorik, Netzwerk, UI, Logging.
- Scheduler: entscheidet, welcher Task läuft, abhängig von Priorität und Bereitschaft.
- Queues/Notifications: sichere Kommunikation zwischen Tasks.
- Semaphores/Mutex: Schutz gemeinsam genutzter Ressourcen (I2C, SPI, globale Daten).
Eine solide, offizielle Referenz zu FreeRTOS im Espressif-Umfeld finden Sie hier: FreeRTOS-API in ESP-IDF.
Dual-Core nutzen: Arbeit sinnvoll aufteilen statt „blind pinnen“
Der klassische ESP32 besitzt zwei Kerne, viele neuere Varianten sind je nach Modell ein- oder zweikernig. Dual-Core hilft, ist aber kein Ersatz für gute Struktur. In der Praxis ist es sinnvoll, zeitkritische Sensorik und rechenlastige Verarbeitung von Netzwerk- und UI-Aufgaben zu trennen. Dabei sollten Sie nicht automatisch „einen Kern für WLAN, einen für Sensoren“ erzwingen, sondern zuerst sauber entkoppeln und anschließend gezielt optimieren.
- Entkopplung zuerst: Sensor-Task erzeugt Daten, Netzwerk-Task sendet Daten.
- Lastspitzen beachten: TLS, OTA und JSON können kurzzeitig stark belasten.
- Pinning sparsam: Tasks nur dann an einen Kern binden, wenn es messbar Vorteile bringt.
Das bewährte Architektur-Muster: Producer–Consumer
Für „Sensoren lesen und WLAN gleichzeitig bedienen“ hat sich ein simples Muster bewährt: Ein Sensor-Task produziert Messwerte in einem festen Rhythmus, ein Netzwerk-Task konsumiert die Werte und sendet sie über MQTT/HTTP/Websocket. Dazwischen liegt eine Queue oder ein Ringpuffer. Dieses Design verhindert, dass Netzwerkprobleme (z. B. Reconnect, DNS, TLS) den Sensor-Loop blockieren.
- Producer (Sensor): liest Messwerte, timestamped, schreibt in Queue.
- Consumer (Network): wartet auf neue Werte, serialisiert und sendet.
- Backpressure: Queue-Größe begrenzt Speicher; bei Vollzustand definieren Sie ein Verhalten (droppen, überschreiben, aggregieren).
Queue-Größe pragmatisch dimensionieren
Die Queue muss nicht „riesig“ sein, sondern nur Lastspitzen abfedern. Beispiel: Sensor soll alle 2 Sekunden messen. Netzwerk darf maximal 20 Sekunden „hinterherhinken“, ohne dass Daten verloren gehen. Dann benötigen Sie mindestens 10 Einträge Puffer. Das lässt sich sauber ausdrücken als:
Mit
Nicht blockieren: Der wichtigste Grundsatz im Sensor- und Netzwerkpfad
Blockierende Calls sind der Feind stabiler Nebenläufigkeit. Gemeint sind vor allem lange Wartezeiten ohne Rückgabe an den Scheduler oder Aufrufe, die unvorhersehbar lange dauern können. Dazu zählen:
- Lange delays im Hauptloop: besonders in Arduino-Sketches verbreitet.
- Sensor-Reads mit Retries: „warte bis Sensor antwortet“ kann Sekunden kosten.
- Synchrones DNS/TLS: Handshakes können stark variieren.
- Große JSON-Objekte: dynamische Allokationen und Serialisierung belasten Heap und CPU.
Stattdessen sollten Sie Zeitsteuerung über Ticks/Timer, Zustandsautomaten und Events lösen. Das Ziel ist, dass jeder Task schnell wieder blockiert, wenn er gerade nichts zwingend tun muss.
Sensorik sauber lesen: Timing, Bus-Sharing und Messqualität
Sensoren hängen häufig an I2C oder SPI. Dort teilen sich mehrere Bauteile den Bus, und das erfordert Zugriffskontrolle. Ein typisches Problem ist, dass gleichzeitig ein Display, ein BME280 und ein IO-Expander über denselben I2C laufen. Ohne Mutex entstehen Kollisionen, fehlerhafte Reads oder „hängerartige“ Zustände durch fehlschlagende Übertragungen.
- I2C-Mutex: schützt den Buszugriff, damit nur ein Task gleichzeitig kommuniziert.
- Messfenster: Sensor-Task liest in festen Intervallen und schreibt Ergebnisse in eine Datenstruktur.
- Fehler robust behandeln: Timeouts, Retry mit Limit, notfalls Sensor neu initialisieren.
Wenn Sie I2C/SPI konsequent kapseln, reduzieren Sie nebenbei auch Seiteneffekte: Nur ein Modul kennt die Details der Sensoransteuerung, der Rest der Firmware arbeitet mit „Messwerten“ als Datenobjekten.
WLAN und Netzwerk stabil halten: Verbindungen als eigener Zustandsbereich
Netzwerklogik sollte nie „quer im Sensorcode“ stecken. Ein besserer Ansatz ist, die Verbindung als eigenständigen Bereich zu behandeln: WLAN verbinden, IP abwarten, MQTT verbinden, Reconnect-Strategie, Backoff, Online/Offline-Modus. Das lässt sich hervorragend als State Machine modellieren, während der Sensor-Task unabhängig weiterläuft (oder in einen Low-Power-Modus wechselt).
- Connectivity-Task: verwaltet WLAN und höhere Protokolle (MQTT/HTTP/Websocket).
- Event-gesteuert: reagiert auf „got IP“, „disconnected“, „timeout“.
- Backoff: verhindert Reconnect-Stürme und schont Akku/Netz.
Für WLAN-Details im ESP-IDF-Kontext ist die offizielle Dokumentation ein guter Einstieg: Wi-Fi Programming in ESP-IDF.
Prioritäten richtig setzen: Nicht „höher = besser“
Eine der häufigsten Ursachen für instabile Systeme ist falsche Priorisierung. Wenn Sie dem Sensor-Task eine extrem hohe Priorität geben und er dabei häufig läuft, kann er Netzwerk- und Systemtasks verdrängen. Umgekehrt kann ein aggressiver Netzwerk-Task die Sensorik ausbremsen. Gute Prioritäten orientieren sich an:
- Deadline: Wie schnell muss reagiert werden? (z. B. Interrupt-Auswertung, Encoder)
- Periodik: Wie regelmäßig muss etwas passieren? (Sensor-Sampling)
- Blockierverhalten: Tasks, die oft blockieren, sind „freundlicher“ für das System.
Pragmatisch heißt das: Sensor-Task mittel, Netzwerk-Task mittel, UI/Logging eher niedrig – und echte Echtzeitpfade (z. B. Pulse Counting) in Hardware-Peripherie (PCNT/RMT) oder sehr schlanke ISRs auslagern.
Watchdog und „Yield“: Warum der ESP32 manchmal neu startet
Wenn Tasks zu lange laufen, ohne dem System Zeit zu geben, greifen Watchdogs. Das ist kein „Bug“, sondern eine Schutzfunktion gegen Hänger. Um Watchdog-Probleme zu vermeiden, sollten Sie:
- lange Schleifen aufteilen: in kleine Schritte, mit kontrollierten Wartepunkten.
- Blockieren statt Busy-Wait: auf Events oder Timer warten, nicht „polling“ mit kurzer Pause.
- Große Operationen entkoppeln: z. B. JSON-Building im Worker-Task, nicht im Netzwerk-Callback.
Gerade in Arduino-Umgebungen wird „yield“ oft als Allheilmittel gesehen. Besser ist, die Architektur so zu bauen, dass Yield-Pflaster kaum nötig sind: Tasks blockieren korrekt, statt ständig „zu rennen“.
Gemeinsame Daten sauber teilen: Mutex, Copy, Snapshot
Sensordaten werden oft in globalen Strukturen gespeichert, die gleichzeitig von Webserver, MQTT und Display gelesen werden. Ohne Schutz drohen Race Conditions: halb aktualisierte Werte, inkonsistente Strukturen oder seltene Abstürze. In der Praxis funktionieren drei Strategien besonders gut:
- Mutex-geschützte Struktur: kurz locken, aktualisieren, unlocken (kritische Abschnitte klein halten).
- Copy-on-Write: Sensor-Task schreibt in „next“, dann atomarer Zeigerwechsel auf „current“.
- Snapshot per Queue: Consumer erhält komplette Messpakete, keine gemeinsame Struktur nötig.
Für Einsteiger ist ein Mutex oft der schnellste Weg. Für Profis in hochfrequenten Systemen ist ein Snapshot/Double-Buffering häufig effizienter.
Speicher im Blick behalten: Stacks, Heap und Fragmentierung
Multitasking erhöht den Speicherbedarf, weil jeder Task einen eigenen Stack benötigt. Ein zu kleiner Stack führt zu schwer erklärbaren Abstürzen, ein zu großer Stack verschwendet RAM. Zusätzlich erzeugen Netzwerkbibliotheken und JSON-Verarbeitung dynamische Allokationen, was Fragmentierung fördern kann. Bewährte Maßnahmen:
- Task-Stacks messen: Stack-High-Water-Mark prüfen und realistisch dimensionieren.
- Buffer wiederverwenden: feste Puffer statt ständig new/malloc.
- Payloads klein halten: JSON minimal, binäre Protokolle oder komprimierte Formate erwägen.
Wenn Sie tiefer in Speicherstrategien einsteigen möchten, ist die Speicherallokations-Dokumentation hilfreich: Memory Allocation in ESP-IDF.
Praktische Muster für reale Projekte
Im Alltag bewähren sich wiederkehrende Muster, die die Komplexität begrenzen und gleichzeitig gut skalieren. Die folgenden Bausteine lassen sich kombinieren, ohne dass die Firmware „auseinanderfällt“.
- Sensor-Task (periodisch): liest alle X Sekunden, schreibt Messpaket in Queue.
- Network-Task (eventgetrieben): wartet auf Queue oder Connectivity-Events, sendet/queued.
- Connectivity-State-Machine: verwaltet WLAN/MQTT mit Timeouts und Backoff.
- UI/Webserver-Task: bedient HTTP/Websocket, liest nur Snapshots.
- Logger-Task: entkoppelt Logging von Echtzeitpfaden (optional, aber sehr hilfreich).
Wenn Sensoren „zu langsam“ sind: Sampling und Aggregation
Ein häufiger Engpass entsteht, wenn Sensoren lange Messzeiten haben oder wenn viele Sensoren seriell ausgelesen werden. Statt alles „in Echtzeit“ zu senden, ist Aggregation oft die sauberste Lösung: Mittelwerte, Min/Max, oder nur Änderungen („delta-based“) übertragen. Dadurch sinkt Netzwerktraffic, und Tasks werden gleichmäßiger planbar.
OTA, Webserver und MQTT gleichzeitig: Nebenläufigkeit ohne Chaos
Viele Projekte scheitern, wenn zusätzlich OTA-Updates integriert werden. Ein OTA kann große Downloads, Flash-Schreibvorgänge und Reboots auslösen. Damit das sauber bleibt:
- OTA als eigener Modus: State Machine wechselt in „MAINTENANCE/OTA“.
- Sensorik definieren: weiterlaufen lassen (mit Puffer) oder bewusst pausieren.
- Netzwerkpriorität: OTA darf temporär Vorrang haben, aber nicht „alles blockieren“.
- Reboot kontrolliert: erst senden/flushen, dann rebooten, nicht mitten im Paket.
Die offiziellen OTA-Informationen im ESP-IDF-Ökosystem sind ein guter Referenzpunkt: OTA-API in ESP-IDF.
Outbound-Links zu relevanten Informationsquellen
- FreeRTOS in ESP-IDF: Tasks, Queues, Scheduling und Synchronisation
- Wi-Fi Programming Guide: WLAN-Architektur und Best Practices
- Speicherallokation: Heap, Caps, Fragmentierung und PSRAM-Strategien
- OTA Updates: Update-Mechanismen und sichere Update-Flows
- Arduino-ESP32 Dokumentation: Framework-Details und Plattformhinweise
IoT-PCB-Design, Mikrocontroller-Programmierung & Firmware-Entwicklung
PCB Design • Arduino • Embedded Systems • Firmware
Ich biete professionelle Entwicklung von IoT-Hardware, einschließlich PCB-Design, Arduino- und Mikrocontroller-Programmierung sowie Firmware-Entwicklung. Die Lösungen werden zuverlässig, effizient und anwendungsorientiert umgesetzt – von der Konzeptphase bis zum funktionsfähigen Prototyp.
Diese Dienstleistung richtet sich an Unternehmen, Start-ups, Entwickler und Produktteams, die maßgeschneiderte Embedded- und IoT-Lösungen benötigen. Finden Sie mich auf Fiverr.
Leistungsumfang:
-
IoT-PCB-Design & Schaltplanerstellung
-
Leiterplattenlayout (mehrlagig, produktionstauglich)
-
Arduino- & Mikrocontroller-Programmierung (z. B. ESP32, STM32, ATmega)
-
Firmware-Entwicklung für Embedded Systems
-
Sensor- & Aktor-Integration
-
Kommunikation: Wi-Fi, Bluetooth, MQTT, I²C, SPI, UART
-
Optimierung für Leistung, Stabilität & Energieeffizienz
Lieferumfang:
-
Schaltpläne & PCB-Layouts
-
Gerber- & Produktionsdaten
-
Quellcode & Firmware
-
Dokumentation & Support zur Integration
Arbeitsweise:Strukturiert • Zuverlässig • Hardware-nah • Produktorientiert
CTA:
Planen Sie ein IoT- oder Embedded-System-Projekt?
Kontaktieren Sie mich gerne für eine technische Abstimmung oder ein unverbindliches Angebot. Finden Sie mich auf Fiverr.

