February 8, 2026

Multitasking ohne Betriebssystem: Millis() statt Delay()

Multitasking ohne Betriebssystem: Millis() statt Delay() ist eines der wichtigsten Konzepte, wenn Ihre Mikrocontroller-Projekte von „Spielerei“ zu stabilen Anwendungen wachsen. Viele Einsteiger starten mit delay(), weil es einfach ist: LED an, warten, LED aus, warten. Das funktioniert bei einem Blink-Sketch perfekt – aber sobald mehrere Dinge parallel passieren sollen, wird delay() zum Problem. Während der Mikrocontroller „wartet“, macht er nichts anderes: keine Buttons abfragen, keine Sensorwerte sauber erfassen, keine Netzwerkverbindung pflegen, keine Zustandswechsel ausführen. Genau dann entstehen typische Symptome: Taster reagieren verzögert, Displays ruckeln, Datenpakete gehen verloren, Motoren laufen unruhig, WLAN-Verbindungen brechen ab oder das System fühlt sich „träge“ an. Die Lösung ist nicht zwingend ein echtes Betriebssystem (RTOS), sondern oft ein einfacher, robuster Ansatz: zeitgesteuerte Abläufe mit millis() (oder micros()) und nicht-blockierenden Zustandslogiken. Damit entsteht eine Art kooperatives Multitasking: Jede Aufgabe erledigt nur kleine Schritte, gibt sofort wieder die Kontrolle ab und wird beim nächsten Durchlauf fortgesetzt. In diesem Artikel lernen Sie, warum delay() bremst, wie Millis() funktioniert, wie Sie mehrere Aufgaben sauber takten, wie Sie typische Fallen (Overflow, Drift, Timing-Fehler) vermeiden und wie Sie aus Ihrem Sketch ein reaktionsschnelles System machen – ohne Betriebssystem, aber mit professioneller Struktur.

Warum delay() in echten Projekten fast immer schadet

delay(ms) blockiert den Programmlauf für eine feste Zeit. Währenddessen kann Ihr Code keine anderen Aufgaben ausführen. Auf einem PC würde man „warten“ und gleichzeitig andere Threads laufen lassen – auf vielen Mikrocontrollern ohne RTOS gibt es jedoch keinen Scheduler, der nebenbei andere Aufgaben ausführt. Das führt zu Problemen, sobald Sie mehr als eine Sache zuverlässig tun müssen.

  • Verpasste Ereignisse: Buttons, Encoder oder Sensor-Interrupts werden zu spät verarbeitet
  • Unruhige Abläufe: Displays aktualisieren ruckelig, Motorsteuerung schwankt
  • Kommunikationsprobleme: UART/I2C/WLAN benötigen regelmäßige Pflege
  • Fehlerdiagnose schwierig: „manchmal“ Bugs durch Timing und Blockaden

Delay ist nicht „falsch“ – nur für sehr einfache Abläufe geeignet

Für kurze Tests, Blink-Beispiele oder einfache Sequenzen ist delay() okay. Sobald jedoch Interaktion, Kommunikation oder mehrere Perioden ins Spiel kommen, lohnt sich der Umstieg auf nicht-blockierende Logik praktisch immer.

Was macht millis() eigentlich?

millis() liefert die Anzahl der Millisekunden seit dem Start des Mikrocontrollers (bzw. seit Reset). Dieser Wert steigt kontinuierlich an. Sie nutzen millis() nicht, um „zu warten“, sondern um zu prüfen, ob eine bestimmte Zeitspanne vergangen ist. Der entscheidende Unterschied: Ihr Programm läuft weiter und kann in jedem Durchlauf andere Aufgaben erledigen. Wenn die Zeit reif ist, führen Sie die nächste Aktion aus.

  • Zeitbasis: „Uhr“ seit Systemstart in Millisekunden
  • Nicht-blockierend: keine Wartezeit, sondern Zeitprüfung
  • Skalierbar: mehrere unabhängige Aufgaben mit eigenen Timern
  • Standard in Arduino: verfügbar auf vielen Boards und Cores

Eine gute Referenz für Zeitfunktionen finden Sie in der Arduino Referenz zu millis().

Das Grundmuster: „Wenn Zeit vergangen ist, tue etwas“

Das klassische Millis()-Muster besteht aus zwei Variablen: einem gespeicherten Zeitstempel (letzter Ausführungszeitpunkt) und einem Intervall. In der loop() prüfen Sie regelmäßig, ob jetzt – zuletzt größer oder gleich dem Intervall ist. Dann führen Sie die Aktion aus und setzen den Zeitstempel neu. Das System „arbeitet“ in kleinen Schritten, statt zu blockieren.

  • Zeitstempel: wann lief die Aufgabe zuletzt?
  • Intervall: wie oft soll sie laufen?
  • Prüfung: if (now – last >= interval) …
  • Update: last = now (oder last += interval für weniger Drift)

Warum „now – last“ besser ist als „now > last + interval“

Weil millis() irgendwann überläuft. Mit der Subtraktion und dem Vergleich in unsigned-Arithmetik bleibt das Muster auch beim Überlauf korrekt. Das ist ein Standardtrick in Embedded-Systemen.

Millis statt Delay: Mehrere Aufgaben parallel planen

Der große Gewinn zeigt sich, wenn Sie mehrere Aufgaben gleichzeitig brauchen: LED blinken, Sensor auslesen, Button abfragen, Display aktualisieren, Netzwerkverbindung prüfen – jeweils mit unterschiedlichen Intervallen. Mit millis() erhält jede Aufgabe ihren eigenen Timer. Da jede Aufgabe nur kurz arbeitet, fühlt sich das System wie Multitasking an, obwohl alles in einem Thread läuft.

  • LED-Heartbeat: z. B. alle 500 ms umschalten
  • Sensor-Sampling: z. B. alle 200 ms messen
  • Display-Refresh: z. B. alle 250 ms aktualisieren
  • Netzwerk-Check: z. B. alle 5 Sekunden Reconnect prüfen
  • Button-Scan: z. B. bei jedem Loop-Durchlauf oder alle 5–10 ms

Kooperatives Multitasking: Jede Aufgabe muss „fair“ sein

Weil es keinen Scheduler gibt, ist Fairness Ihre Aufgabe: Jede Routine sollte schnell zurückkehren. Wenn eine Aufgabe plötzlich 200 ms blockiert (z. B. durch lange Serial-Ausgaben oder Warte-Schleifen), leidet wieder alles andere.

Delay ersetzen: Typische Praxisbeispiele

Viele Delay-Anwendungen lassen sich direkt mit millis() umsetzen. Dazu gehört nicht nur Blinken, sondern auch zeitgesteuerte Sequenzen: „Nach 2 Sekunden Motor starten“, „Nach 10 Sekunden wieder stoppen“, „Wenn Button gedrückt, 3 Sekunden lang aktiv“. Statt in einer delay-Kette zu hängen, modellieren Sie diese Abläufe über Zeitstempel oder Zustände.

  • Blink ohne Blockade: LED toggeln, wenn Intervall abgelaufen
  • Timeouts: statt „warte bis verbunden“ eine Maximalzeit prüfen
  • Debouncing: Tasterprellen über Zeitfenster filtern
  • Sequenzen: Schrittfolgen über Zustände und Zeitstempel steuern

Millis und Zustandsautomaten: Das Dream-Team für sauberen Code

Wenn Abläufe mehrstufig werden, reicht ein einzelner Timer nicht mehr aus. Dann helfen Zustandsautomaten (State Machines): Jeder Zustand beschreibt, was jetzt gilt und welche Bedingungen (Zeit, Events, Sensorwerte) zum nächsten Zustand führen. millis() liefert dabei die Zeitbedingungen. So entsteht sauberer Code, der nicht in verschachtelten if-else-Ketten und Delay-Sequenzen endet.

  • Zustand definieren: z. B. IDLE, HEATING, STABILIZE, ERROR
  • Entry-Zeit merken: timestamp beim Eintritt speichern
  • Timeouts prüfen: now – stateStart > timeout
  • Events verarbeiten: Button, Sensor, Netzwerkstatus

Warum das skalierbar ist

Ein Zustandsautomat zwingt Sie, Logik zu strukturieren: „Was darf in diesem Zustand passieren?“ Das reduziert Nebenwirkungen und macht Erweiterungen leichter, weil neue Funktionen meist nur neue Zustände oder Übergänge hinzufügen.

Typische Fallstricke: Overflow, Drift und ungenaue Intervalle

Millis() läuft nicht unendlich. Je nach Plattform überläuft der Zähler nach einer gewissen Zeit (bei 32-bit Millisekunden typischerweise nach einigen Wochen). Das ist kein Problem, wenn Sie das richtige Vergleichsmuster nutzen. Zusätzlich gibt es Drift: Wenn Sie nach jeder Ausführung last = now setzen, verschiebt sich der Rhythmus leicht, wenn der Loop-Durchlauf variiert. Für gleichmäßige Takte nutzen Sie oft last += interval (mit Schleife, falls mehrere Intervalle verpasst wurden).

  • Overflow-Sicherheit: unsigned Subtraktion verwenden
  • Drift reduzieren: last += interval statt last = now
  • Nachholen: bei großen Verzögerungen mehrere Schritte abarbeiten (vorsichtig)
  • Intervall realistisch wählen: zu kurze Intervalle überlasten die loop()

Überlauf in der Praxis: Keine Panik, wenn man korrekt vergleicht

Viele Systeme laufen wochenlang. Wenn Sie unsigned Zeitdifferenzen verwenden, bleibt die Logik auch nach dem Überlauf korrekt, weil die Arithmetik modulo arbeitet.

Prioritäten und „Task-Design“: Was wirklich oft laufen muss

Wenn Sie mehrere Aufgaben planen, ist die Frequenz entscheidend. Nicht alles muss 100-mal pro Sekunde laufen. Ein häufiger Fehler ist, zu viele Dinge zu oft zu aktualisieren (z. B. Display bei jedem Loop). Definieren Sie sinnvolle Intervalle: Buttons und Encoder eher schnell, Sensoren und Displays moderat, Netzwerkchecks eher langsam. So bleibt CPU-Zeit für das Wichtige.

  • Sehr häufig: Eingaben (Buttons, Encoder), Sicherheitschecks
  • Moderat: Sensoren, UI-Updates, Regelungen (je nach System)
  • Selten: Status-Logs, Netzwerk-Reconnect, Cloud-Sync
  • Event-basiert: wenn möglich, lieber Events statt Polling (Interrupts)

Millis und Interrupts: Echtzeit-Events ohne Chaos

Interrupts können Ereignisse präzise erfassen, millis() kann die zeitliche Verarbeitung steuern. Ein sauberes Muster ist: ISR setzt nur ein Flag oder speichert einen Zeitstempel, der Hauptcode verarbeitet das Ereignis später nicht-blockierend. So bleiben Ihre Abläufe stabil und „echtzeitnah“, ohne dass Sie Logik in Interrupts quetschen.

  • ISR: Flag setzen, Zähler erhöhen, Zeitstempel sichern
  • Loop: Flag auswerten und passende Aktion/State-Transition ausführen
  • Timing: millis() für Debounce, Cooldowns und Timeouts nutzen

Wann millis() nicht reicht: Grenzen und sinnvolle Alternativen

Millis()-Multitasking ist kooperativ: Es funktioniert nur, wenn Ihre Aufgaben kurz bleiben. Wenn Sie harte Echtzeit brauchen (sehr präzise, jitterarm), wenn viele Aufgaben komplex werden oder wenn Netzwerk-Stacks und Sensoren stark konkurrieren, kann ein RTOS sinnvoll sein (z. B. auf ESP32). Für extrem präzises Timing nutzen Sie Timer-Interrupts oder Hardware-PWM. Für sehr zeitkritische Messungen ist micros() oder dedizierte Peripherie (Capture/Compare) oft geeigneter.

  • Millis()-Stärken: einfach, robust, ideal für viele Maker-Projekte
  • Schwächen: blockierende Aufgaben ruinieren Fairness, Timing-Jitter möglich
  • Alternativen: Timer-Interrupts, RTOS-Tasks, Event-Queues, DMA (fortgeschritten)

ESP32-Hinweis: RTOS ist vorhanden, aber nicht zwingend nötig

Der ESP32 nutzt intern FreeRTOS. Dennoch ist millis()-basierte Struktur oft weiterhin sinnvoll, weil sie den Code übersichtlich hält. Ein RTOS ist dann hilfreich, wenn Aufgaben wirklich getrennt priorisiert oder parallelisiert werden müssen.

Best Practices: So wird Ihr Code „multitaskingfähig“ und wartbar

Der Umstieg auf millis() ist mehr als eine Technik – es ist ein Stil. Sie gestalten Aufgaben klein, definieren Intervalle, vermeiden Blockaden und strukturieren Abläufe über Zustände. Damit werden Projekte nicht nur reaktionsschnell, sondern auch leichter zu debuggen und zu erweitern.

  • Keine blockierenden Wartezeiten: delay() und wartende Schleifen vermeiden
  • Task-Intervalle planen: jede Aufgabe mit sinnvoller Frequenz
  • Kleine Funktionen: Aufgaben in kurze, wiederholbare Schritte zerlegen
  • Zustandsautomaten nutzen: für mehrstufige Abläufe und Timeouts
  • Debugging bewusst: Serial-Ausgaben dosieren, Timing nicht zerstören
  • Overflow-sicher: Zeitdifferenzen mit unsigned-Arithmetik vergleichen

LSI-Keywords für SEO und Weiterrecherche

Wenn Sie tiefer recherchieren, helfen Begriffe wie „nicht-blockierender Code“, „cooperative multitasking“, „Arduino Blink Without Delay“, „State Machine“, „Timer“, „event-driven programming“, „Scheduler“ und „loop timing“.

Weiterführende Ressourcen

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.

 

Related Articles