State Machines – auf Deutsch oft Zustandsautomaten genannt – sind eine der wirksamsten Methoden, um Mikrocontroller- und IoT-Projekte langfristig stabil, verständlich und erweiterbar zu halten. Viele Programme beginnen harmlos: Eine LED blinkt, ein Button schaltet ein Relais, ein Sensor sendet Werte. Mit jedem neuen Feature wächst aber die Komplexität: Es gibt mehrere Betriebsmodi, Zeitfenster, Sonderfälle, Fehlerzustände, manuelle Übersteuerungen, WLAN-Verbindungslogik, OTA-Updates oder Sleep-Zyklen. Wer das alles „einfach in die loop()“ packt, landet schnell bei verschachtelten if-else-Ketten, schwer nachvollziehbaren Flags und Bugs, die nur „manchmal“ auftreten. Genau hier schaffen Zustandsautomaten Ordnung. Eine State Machine modelliert das Verhalten eines Systems als klar definierte Zustände (z. B. STARTUP, CONNECTING, RUNNING, ERROR, SLEEP) und Übergänge (z. B. „WLAN verbunden“, „Timeout“, „Sensorfehler“, „Button gedrückt“). Der Code wird dadurch nicht nur lesbarer, sondern auch testbarer und robuster. In diesem Artikel lernen Sie, wie State Machines funktionieren, wann sie sich lohnen, welche Typen es gibt, wie Sie sie im Arduino-/ESP32-Alltag umsetzen, wie Timer und Interrupts sauber integriert werden und welche Muster Ihnen helfen, „spaghettifreien“ Code zu schreiben, der auch nach Monaten noch verständlich ist.
Warum Mikrocontroller-Code ohne Zustandsautomaten schnell „unsauber“ wird
In Mikrocontroller-Projekten laufen viele Dinge gleichzeitig: Eingaben (Buttons, Sensor-Interrupts), Ausgaben (LEDs, Motoren, Displays), Kommunikation (WLAN, BLE, MQTT), Zeitsteuerung (Delays, Timer) und Fehlerbehandlung. In einer linearen Programmlogik konkurrieren diese Aufgaben, und man beginnt, mit Flags und verzweigten Bedingungen zu arbeiten. Das Ergebnis ist oft ein System, das schwer zu erweitern ist, weil jede Änderung an einer Stelle unbeabsichtigt eine andere Stelle beeinflusst.
- Verschachtelte Logik: viele if-else-Blöcke und Sonderfälle
- „Magic Flags“: globale Variablen, die überall gesetzt werden
- Timing-Probleme: delay() blockiert, Ereignisse werden verpasst
- Fehlerzustände: keine klare Strategie für „was passiert bei Ausfall?“
Typisches Symptom: „Es funktioniert, aber ich traue mich nicht, etwas zu ändern“
Wenn Änderungen Angst machen, ist das ein Signal, dass Struktur fehlt. Zustandsautomaten ersetzen „Angst vor Nebenwirkungen“ durch ein klares Modell: Was darf in welchem Zustand passieren, und was nicht?
Grundidee: Zustand + Ereignis = definierter Übergang
Ein Zustandsautomat beschreibt ein System als Menge von Zuständen und Übergängen. Ein Zustand repräsentiert eine „Betriebsart“ mit klaren Regeln. Ein Übergang wird durch ein Ereignis ausgelöst: Zeitablauf, Buttondruck, Sensorwert, Netzwerkstatus, Fehlercode. In der Praxis denken Sie weniger in „was mache ich als Nächstes“, sondern in „wo bin ich gerade und was darf jetzt passieren“.
- Zustand: definierter Modus (z. B. CONNECTING)
- Ereignis: Auslöser (z. B. „WLAN verbunden“)
- Übergang: Wechsel in neuen Zustand (z. B. RUNNING)
- Aktion: was beim Wechsel oder im Zustand ausgeführt wird
Als allgemeine Grundlage eignet sich die Einordnung zu endlichen Automaten.
Welche Arten von State Machines es gibt
Im Maker-Alltag begegnen Ihnen vor allem endliche Zustandsautomaten (Finite State Machines, FSM). Je nach Komplexität gibt es Varianten: flache FSM (einfach), hierarchische Zustandsautomaten (HSM) und Zustandsautomaten mit getrennten Zustands- und Ereignis-Handling-Tabellen. Für viele Projekte reicht eine flache FSM völlig aus. Sobald Sie jedoch viele Modi haben, lohnt sich eine hierarchische Struktur (z. B. „NETZWERK“ als Oberzustand mit Unterzuständen).
- Flache FSM: wenige Zustände, einfache Übergänge
- Hierarchische State Machine: Zustände in Ebenen, reduziert Wiederholung
- Event-getrieben: Ereignis-Queue, klare Trennung zwischen Input und Logik
Wann eine hierarchische Struktur sinnvoll wird
Wenn mehrere Zustände sehr ähnliche Logik teilen (z. B. RUNNING und RUNNING_NO_WIFI) oder wenn Fehlerzustände überall gleich behandelt werden sollen, kann eine Hierarchie doppelten Code vermeiden und die Logik stabiler machen.
State Machines vs. „Switch-Case in loop()“: Wo liegt der Unterschied?
Viele Einsteiger setzen bereits eine Art Zustandslogik um, ohne sie so zu nennen: ein switch(state) in der loop() und je Zustand ein Codeblock. Das ist ein guter Start. Der entscheidende Unterschied zu einer „sauberen“ State Machine liegt in der Disziplin: klare Zustandsdefinitionen, kontrollierte Übergänge, getrennte Zustandsaktionen (Entry/Exit/Do) und ein sauberes Ereigniskonzept. Ein switch-case wird dann wirklich mächtig, wenn Sie ihn nicht als „Monolith“ verwenden, sondern als Struktur.
- Einfacher Switch: schnell gebaut, aber oft zu groß
- Saubere FSM: kleine Zustandsfunktionen, definierte Events, klare Übergänge
- Wartbarkeit: Zustände lassen sich unabhängig testen und erweitern
Das wichtigste Prinzip: Keine blockierenden Delays
Zustandsautomaten funktionieren am besten, wenn Ihr Programm nicht blockiert. Das bedeutet: keine langen delay()-Aufrufe, keine wartenden Schleifen, keine „warte bis verbunden“-Dauerschleifen. Stattdessen arbeiten Sie mit Zeitstempeln und prüfen in jedem loop-Durchlauf, ob Bedingungen erfüllt sind. So bleibt das System reaktionsfähig, und Zustandsübergänge können jederzeit stattfinden.
- Nicht-blockierend: millis()-basierte Timer statt delay()
- Reaktiv: Ereignisse können jederzeit verarbeitet werden
- Stabil: weniger Timing-Bugs, bessere Integration von Interrupts
Der praktische Vorteil: Ihr Mikrocontroller wirkt „multitaskingfähig“
Auch ohne echtes Multithreading fühlt sich ein nicht-blockierendes System so an, als würde es mehrere Dinge parallel erledigen. Genau das erwarten Nutzer im Smart Home: Button reagieren, Display aktualisieren, WLAN reconnecten – ohne Ruckeln.
Zustandsmodell aufbauen: Ein Vorgehen, das in der Praxis funktioniert
Der häufigste Fehler ist, zu früh Code zu schreiben. Ein Zustandsautomat wird stark, wenn Sie zuerst das Verhalten modellieren. Skizzieren Sie: Welche Zustände gibt es? Welche Ereignisse? Welche Übergänge? Welche Timeouts? Welche Fehler? Danach wird die Implementierung erstaunlich geradlinig.
- Use Cases sammeln: normaler Betrieb, Setup, Fehler, Wartung
- Zustände definieren: klare Namen, klare Verantwortung
- Events definieren: Button, Timer, Sensor, Netzwerk, System
- Übergänge zeichnen: „Wenn X passiert, dann nach Y“
- Timeouts planen: z. B. CONNECTING darf max. 10 Sekunden dauern
Beispielhafte Zustände in IoT-Projekten
- STARTUP: Initialisierung von Pins, Speicher, Sensoren
- CONFIG_MODE: Access-Point oder Setup-Portal aktiv
- CONNECTING: WLAN verbinden, ggf. MQTT verbinden
- RUNNING: normaler Betrieb (Messen, Schalten, UI)
- ERROR: definierte Fehlerstrategie (Retry, Fallback, Safe Mode)
- SLEEP: Deep Sleep, Wake-up-Logik
Entry/Do/Exit: Die drei Bausteine für saubere Zustandslogik
Ein bewährtes Muster ist die Trennung in Entry/Do/Exit-Aktionen:
- Entry: wird einmal beim Eintritt in den Zustand ausgeführt (z. B. Timer starten)
- Do: wird während des Zustands regelmäßig ausgeführt (z. B. prüfen, ob Event da ist)
- Exit: wird einmal beim Verlassen ausgeführt (z. B. Ressourcen freigeben)
Diese Struktur verhindert, dass Sie Initialisierungscode in jedem loop-Durchlauf erneut ausführen. Gleichzeitig macht sie Übergänge nachvollziehbar: Zustandswechsel sind klar definierte Punkte, an denen Sie gezielt Aktionen ausführen.
Praxisregel: Entry ist der Ort für „Startbedingungen“
Wenn ein Zustand eine Aktion starten soll (z. B. LED-Blinkmuster, Verbindungsversuch, Sensor-Conversion), gehört das in Entry. Das Looping und Prüfen gehört in Do. Das Aufräumen gehört in Exit.
Ereignisse richtig handhaben: Flags, Queues und Entkopplung
In kleinen Projekten reichen Flags: Ein Interrupt setzt ein Flag, die loop() liest es aus und verarbeitet es. Sobald mehr Ereignisse zusammenkommen, ist eine Event-Queue sinnvoll: Ereignisse werden gesammelt und nacheinander verarbeitet. Dadurch werden schnelle Ereignisse nicht „überschrieben“, und Sie behalten Reihenfolge und Nachvollziehbarkeit.
- Flag-basiert: einfach, gut für seltene Events
- Queue-basiert: robust bei vielen oder schnellen Events
- Entkopplung: ISR erzeugt nur Events, Logik arbeitet im Hauptcode
Warum Event-Queues Debugging erleichtern
Wenn Sie Ereignisse als Codes in einer Queue speichern, können Sie später ausgeben: „Event A kam, dann B, dann Timeout“. Damit lassen sich Timing-Probleme und seltene Fehler oft viel schneller finden als mit verstreuten Serial-Prints.
Interrupts und State Machines: So passt das zusammen
Interrupts liefern Ereignisse – State Machines verarbeiten Ereignisse. Das ist eine ideale Kombination, solange Sie die Regeln beachten: Die ISR bleibt kurz und schreibt nur in eine sichere Struktur (Flag oder Queue). Die State Machine reagiert im nächsten loop-Durchlauf auf das Ereignis und entscheidet über den Übergang. So bleibt Ihr System echtzeitnah, ohne instabil zu werden.
- ISR: setzt Event „BUTTON_PRESSED“ oder erhöht Zähler
- FSM: verarbeitet Event und wechselt z. B. von RUNNING nach MENU
- Vorteil: keine Logik in ISR, keine Nebenwirkungen
Timeouts und Timer: Zeit wird in Zustandsautomaten elegant
Time-basiertes Verhalten ist einer der größten Gründe für Spaghetti-Code. Zustandsautomaten lösen das sauber: Jeder Zustand kann einen eigenen Startzeitpunkt haben. In Do prüfen Sie mit millis() oder einem Timer, ob eine Frist abgelaufen ist. Damit bekommen Sie nachvollziehbare Regeln: „Wenn CONNECTING länger als 10 Sekunden dauert, gehe zu ERROR.“
- Zustandsstart merken: timestamp beim Entry speichern
- Regel prüfen: if (now – start > timeout) → Übergang
- Keine Warte-Schleifen: System bleibt reaktionsfähig
WLAN- und MQTT-Reconnects profitieren besonders
Netzwerklogik ist voller Sonderfälle: verbunden, getrennt, DNS langsam, Broker down. Mit Zustandsautomaten lassen sich diese Fälle klar modellieren: CONNECTING, CONNECTED, RETRY_WAIT, OFFLINE_MODE – statt unübersichtlicher if-Kaskaden.
Fehlerzustände und Recovery: Das unterschätzte Erfolgsmerkmal
Viele Projekte behandeln Fehler implizit: „Wenn etwas nicht klappt, probiere ich es einfach nochmal.“ Das kann funktionieren, kann aber auch zu Endlosschleifen führen, die Energie fressen (Batteriebetrieb) oder Geräte „hängen lassen“. Eine State Machine zwingt Sie zu einer klaren Fehlerstrategie: Wie viele Retries? Wie lange warten? Was ist der Fallback? Gibt es einen Safe Mode? Wird der Nutzer informiert?
- Retry begrenzen: z. B. 3 Versuche, dann Wartemodus
- Fallback definieren: z. B. lokale Steuerung ohne Cloud
- Safe Mode: minimaler Betrieb (z. B. OTA + Grundfunktion)
- Beobachtbarkeit: Status-LED, Fehlercode, Telemetrie
Besonders wichtig bei OTA und Batteriegeräten
OTA-Updates und Deep Sleep sind empfindlich gegenüber Fehlern: Ein falscher Ablauf kann Geräte unbrauchbar machen oder Batterien schnell leeren. Zustandsautomaten helfen, diese Prozesse in kontrollierte Schritte zu zerlegen, die jeweils überprüfbar sind.
Saubere Struktur im Code: Zustände als Funktionen oder Klassen
Wie Sie implementieren, hängt vom Stil ab. Für viele Projekte reicht ein Enum für Zustände und ein switch-case, der pro Zustand eine kleine Funktion aufruft. Wichtig ist, dass die Zustandslogik nicht in einem riesigen Block endet. Alternativ können Sie Zustände als Klassen modellieren (State Pattern). Das ist umfangreicher, kann aber bei großen Projekten sehr übersichtlich werden.
- Enum + Funktionen: leicht, schnell, gut für Arduino-Projekte
- Tabellengetrieben: Übergänge als Tabelle, sehr sauber bei vielen Zuständen
- State Pattern: objektorientiert, gut skalierbar, aber mehr Boilerplate
Eine Einordnung zum Zustandsmuster (State Pattern) kann helfen, wenn Sie objektorientiert arbeiten möchten.
Typische State-Machine-Muster für Maker-Projekte
Einige Muster tauchen immer wieder auf und haben sich bewährt. Sie bieten Ihnen „Baupläne“ für häufige Probleme, ohne dass Sie jedes Mal neu erfinden müssen.
- Setup-Portal-Muster: CONFIG_MODE → CONNECTING → RUNNING
- Retry-Backoff-Muster: CONNECTING → RETRY_WAIT → CONNECTING
- UI-Menü-Muster: IDLE → MENU → EDIT → SAVE
- Deep-Sleep-Muster: WAKE → MEASURE → SEND → SLEEP
- Safety-Muster: RUNNING → EMERGENCY_STOP → SAFE_IDLE
Backoff: Warum „sofort wieder verbinden“ oft schlecht ist
Wenn WLAN oder ein Server gerade ausfällt, ist sofortiges Reconnecten energieintensiv und belastet das Netz. Ein Backoff-Zustand mit wachsender Wartezeit macht Systeme stabiler und schont Ressourcen.
Häufige Fehler bei Zustandsautomaten – und wie Sie sie vermeiden
Auch State Machines können unübersichtlich werden, wenn man Zustände zu fein aufteilt oder Übergänge „überallhin“ zulässt. Ein guter Zustandsautomat bleibt begrenzt, konsistent und dokumentiert. Die folgenden Fehler treten besonders oft auf.
- Zu viele Zustände: unnötige Komplexität, ohne Mehrwert
- Unklare Zustandsnamen: „STATE1“, „RUN2“ – später nicht mehr verständlich
- Übergänge ohne Regeln: jeder Zustand kann in jeden wechseln
- Seiteneffekte: Zustandswechsel lösen versteckte Aktionen in fremden Modulen aus
- Blockierender Code: delay() oder Warteschleifen untergraben die Reaktivität
Qualitätskriterium: Sie können das Verhalten als Diagramm erklären
Wenn Sie Ihre Logik nicht als Zustandsdiagramm skizzieren können, ist sie wahrscheinlich nicht sauber genug strukturiert. Ein kurzes Diagramm zwingt zur Klarheit: Zustände, Events, Übergänge, Timeouts.
Praxis-Checkliste: State Machines für sauberen Code
- Zustände klar benennen: was bedeutet der Zustand konkret?
- Events definieren: Button, Timer, Netzwerk, Sensor, Fehler
- Entry/Do/Exit trennen: Initialisierung nicht in jedem Loop-Durchlauf
- Nicht-blockierend arbeiten: millis()-Timer statt delay()
- ISR nur als Event-Quelle: keine Logik in Interrupts
- Timeouts festlegen: keine endlosen „warte bis…“ Zustände
- Fehlerstrategie: Retries begrenzen, Fallback/Safe Mode planen
- Dokumentation: kleines Diagramm oder Tabelle der Übergänge pflegen
Weiterführende Ressourcen
- Endlicher Automat: Grundbegriffe und Modell
- Zustandsmuster (State Pattern): objektorientierte Umsetzung
- Arduino Dokumentation: Strukturierung von Projekten und Grundlagen
- Espressif Dokumentation: ESP32, Events, Tasks und Systemverhalten
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.

