Arduino millis() statt delay(): Zeitsteuerung ohne Blockieren
Arduino millis() erklärt: Warum delay() den Sketch anhält, Blink ohne delay, mehrere Aufgaben gleichzeitig, sicherer 49-Tage-Überlauf, micros(), ESP32 und Pico.
Die Arduino-Funktion millis() liefert die Zeit seit dem Start des Boards in Millisekunden und ist der Standardweg, um ohne delay() zu takten. Schreibst du delay(1000), macht der Arduino eine ganze Sekunde lang nichts anderes: Ein Tastendruck wird nicht bemerkt, Sensoren werden nicht gelesen, das Display wird nicht aktualisiert. Mit millis() fragst du stattdessen "Wie viel Zeit ist seit der letzten Aufgabe vergangen?"; du erledigst, was fällig ist, überspringst den Rest, und loop() läuft ohne Pause weiter. So kann ein einziger Arduino gleichzeitig LEDs blinken lassen, Taster abfragen, Sensoren messen und Berichte an den seriellen Monitor schicken.
In dieser Anleitung lernst du, warum delay() dein Programm blockiert, das bekannte Beispiel Blink without delay (Blinken ohne delay), wie du mehrere Aufgaben gleichzeitig ausführst, wie du den Überlauf nach 49,7 Tagen sicher überstehst, die Funktion micros() für Mikrosekunden und wiederverwendbare Timer-Muster. In den letzten Abschnitten siehst du, wie dieselbe Logik auf dem ESP32 und auf dem Raspberry Pi Pico (MicroPython) aussieht.

Was ist delay() und warum hält es dein Programm an?
delay(ms) lässt den Prozessor in einer Schleife warten, bis die angegebene Zeit abgelaufen ist. In dieser Zeit wird die nächste Zeile in loop() nicht erreicht; man spricht von einem blockierenden Warten. Für eine einzelne blinkende LED ist das kein Problem, doch sobald du einen Taster hinzufügst, beginnt der Ärger. Der folgende Code lässt eine LED im Sekundentakt blinken und prüft dabei auch, ob ein Taster gedrückt ist:
1const int LED_PIN = 13; // dahili LED / built-in LED
2const int BUTON_PIN = 2; // buton, diğer ucu GND / button, other leg to GND
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 pinMode(BUTON_PIN, INPUT_PULLUP);
7 Serial.begin(9600);
8}
9
10void loop() {
11 digitalWrite(LED_PIN, HIGH);
12 delay(1000); // 1 sn bekle, buton okunmaz / wait 1 s, button is not read
13 digitalWrite(LED_PIN, LOW);
14 delay(1000);
15 if (digitalRead(BUTON_PIN) == LOW) { // 2 saniyede bir bakılır / checked once every 2 s
16 Serial.println(F("Butona basildi / Button pressed"));
17 }
18}Hier wird der Taster nur alle zwei Sekunden bemerkt, und auch nur, wenn er genau in diesem Moment gedrückt ist; ein kurzer Druck geht zwischen den beiden delay()-Aufrufen verloren. Interrupts laufen während delay() weiter: millis() zählt weiter, und Bytes vom seriellen Port landen im 64-Byte-Puffer. Aber dein Code wartet. Kommen Display, Sensoren und Buzzer dazu, bremst jedes delay() alle anderen Aufgaben aus, und das Gerät wirkt träge. Die Lösung: nicht mehr warten, sondern Zeit messen.
Was ist millis() und wie funktioniert es?
millis() gibt die Millisekunden seit dem Einschalten oder Reset als unsigned long (32 Bit, vorzeichenlos) zurück. Beim Arduino Uno wird dieser Zähler im Hintergrund vom Überlauf-Interrupt des Hardware-Timers Timer0 aktualisiert. Bei 16 MHz läuft Timer0 alle 1,024 ms über; der Core sammelt die überzähligen 0,024 ms und erhöht den Zähler etwa alle 43 ms um 2 statt um 1. millis() überspringt also einzelne Werte, deshalb sind Gleichheitsprüfungen wie millis() == 5000 unzuverlässig; nutze immer >=. Außerdem zählt millis() nicht weiter, solange Interrupts gesperrt sind (zum Beispiel innerhalb einer Interrupt-Routine).
| Funktion | Was sie tut | Auflösung (Uno, 16 MHz) | Überlauf nach | Blockiert das Programm? |
|---|---|---|---|---|
| delay(ms) | Wartet die angegebenen Millisekunden | 1 ms | - | Ja |
| delayMicroseconds(us) | Kurzes Warten (genau von 3 bis 16383 µs) | Etwa 1 µs | - | Ja |
| millis() | Millisekunden seit dem Start | 1 ms (springt gelegentlich um 2) | Etwa 49,7 Tage | Nein |
| micros() | Mikrosekunden seit dem Start | 4 µs | Etwa 71,6 Minuten | Nein |
Blink without delay: LED mit millis() blinken lassen
- Lege eine Variable vom Typ unsigned long an, die sich merkt, wann die Aufgabe zuletzt erledigt wurde (oncekiZaman).
- Lies bei jedem Durchlauf von loop() mit millis() die aktuelle Zeit.
- Prüfe mit der Bedingung simdi - oncekiZaman >= ARALIK, ob genug Zeit vergangen ist.
- Ist die Zeit um, erledige die Aufgabe (LED umschalten) und aktualisiere oncekiZaman.
- Ist sie noch nicht um, tu nichts und lass loop() weiterlaufen; genau dann kommen deine anderen Aufgaben zum Zug.
1const int LED_PIN = LED_BUILTIN; // Uno'da 13 / pin 13 on the Uno
2const unsigned long ARALIK = 500; // yanıp sönme aralığı (ms) / blink interval (ms)
3
4unsigned long oncekiZaman = 0; // son değişim anı / time of the last toggle
5bool ledDurumu = false;
6
7void setup() {
8 pinMode(LED_PIN, OUTPUT);
9}
10
11void loop() {
12 unsigned long simdi = millis(); // şu anki zaman / current time
13 if (simdi - oncekiZaman >= ARALIK) { // taşmaya karşı güvenli / overflow-safe
14 oncekiZaman += ARALIK; // kaymayı önler / prevents drift
15 ledDurumu = !ledDurumu;
16 digitalWrite(LED_PIN, ledDurumu);
17 }
18 // burada başka işler beklemeden çalışır / other work runs here without waiting
19}Im Code steht oncekiZaman += ARALIK statt oncekiZaman = simdi. Der Unterschied ist klein, aber wichtig: Kommt loop() manchmal ein paar Millisekunden zu spät, addiert die erste Variante diese Verzögerung jedes Mal, und die LED driftet über Stunden; die zweite schiebt die Zielzeit in festen Schritten weiter, sodass die mittlere Periode genau 500 ms bleibt. Nimm += für Uhren, Metronome und regelmäßige Messungen. Kann dein Programm länger blockiert werden (etwa beim Warten auf WLAN), versucht += die verpassten Schritte schnell hintereinander nachzuholen; dann ist = simdi sicherer. Wenn du auch die Helligkeit der LED ohne Blockieren ändern willst, lies den Artikel über PWM und analogWrite.
Mehrere Dinge gleichzeitig: Multitasking mit millis()
Der Arduino Uno hat einen Einkernprozessor und kann nicht wirklich zwei Dinge gleichzeitig tun. Weil loop() aber zehntausende Male pro Sekunde laufen kann, wirkt es von außen "gleichzeitig", wenn jede Aufgabe ein paar Mikrosekunden bekommt, sobald sie an der Reihe ist. Das nennt man kooperatives Multitasking. Die Regel ist einfach: Jede Aufgabe hat ihre eigene Zeitvariable und ihr eigenes Intervall, und keine Aufgabe wartet lange. Im folgenden Beispiel laufen vier Aufgaben nebeneinander:
1const int KIRMIZI_LED = 8; // 300 ms'de bir yanıp söner / blinks every 300 ms
2const int YESIL_LED = 9; // 1000 ms'de bir yanıp söner / blinks every 1000 ms
3const int SARI_LED = 10; // butonla açılıp kapanır / toggled by the button
4const int BUTON = 2; // diğer ucu GND / other leg to GND
5
6unsigned long kirmiziZaman = 0, yesilZaman = 0, butonZaman = 0, seriZaman = 0;
7bool kirmizi = false, yesil = false, sari = false;
8int sonOkuma = HIGH; // son ham okuma / last raw reading
9int kararliDurum = HIGH; // titreşimsiz durum / debounced state
10
11void setup() {
12 pinMode(KIRMIZI_LED, OUTPUT);
13 pinMode(YESIL_LED, OUTPUT);
14 pinMode(SARI_LED, OUTPUT);
15 pinMode(BUTON, INPUT_PULLUP);
16 Serial.begin(9600);
17}
18
19void loop() {
20 unsigned long simdi = millis();
21
22 // Görev 1: kırmızı LED / Task 1: red LED
23 if (simdi - kirmiziZaman >= 300) {
24 kirmiziZaman = simdi;
25 kirmizi = !kirmizi;
26 digitalWrite(KIRMIZI_LED, kirmizi);
27 }
28
29 // Görev 2: yeşil LED / Task 2: green LED
30 if (simdi - yesilZaman >= 1000) {
31 yesilZaman = simdi;
32 yesil = !yesil;
33 digitalWrite(YESIL_LED, yesil);
34 }
35
36 // Görev 3: 50 ms debounce ile buton / Task 3: button with 50 ms debounce
37 int okuma = digitalRead(BUTON);
38 if (okuma != sonOkuma) { // değişti, sayacı yeniden başlat / changed, restart the timer
39 butonZaman = simdi;
40 sonOkuma = okuma;
41 }
42 if (simdi - butonZaman >= 50 && okuma != kararliDurum) {
43 kararliDurum = okuma;
44 if (kararliDurum == LOW) { // basıldı / pressed
45 sari = !sari;
46 digitalWrite(SARI_LED, sari);
47 }
48 }
49
50 // Görev 4: saniyede bir seri rapor / Task 4: serial report once per second
51 if (simdi - seriZaman >= 1000) {
52 seriZaman = simdi;
53 Serial.print(F("Calisma suresi (s) / Uptime (s): "));
54 Serial.println(simdi / 1000);
55 }
56}| Aufgabe | Pin | Intervall | Was sie tut |
|---|---|---|---|
| Rote LED | 8 | 300 ms | Blinkt schnell |
| Grüne LED | 9 | 1000 ms | Blinkt langsam |
| Taster + gelbe LED | 2 und 10 | 50 ms Entprellung | Schaltet die gelbe LED bei jedem Druck um |
| Serieller Bericht | - | 1000 ms | Gibt die Laufzeit in Sekunden aus |
Auch die Tasterlogik basiert auf millis(): Ändert sich der Rohwert, startet der Timer neu, und bleibt der Wert 50 ms lang stabil, gilt der Druck als echt. So wird aus dem mechanischen Prellen kein Dutzend Tastendrücke, und der Code wartet nie. Wie du einen Taster anschließt und den internen Pull-up-Widerstand nutzt, steht im Artikel Taster am Arduino anschließen. Für die LEDs reicht ein Vorwiderstand von 220 Ω; für andere Farben und Ströme hilft der LED-Vorwiderstandsrechner.
Der Überlauf nach 49,7 Tagen und sichere Vergleiche
Ein unsigned long fasst höchstens 4.294.967.295. Erreicht millis() diese Zahl, also nach etwa 49 Tagen, 17 Stunden und 2 Minuten, beginnt es wieder bei 0. Läuft dein Board wochenlang (Gewächshaussteuerung, Aquarium, Alarmanlage), kommt dieser Moment bestimmt. Die gute Nachricht: Code, der mit Subtraktion in der Form simdi - onceki >= aralik geschrieben ist, ist gegen den Überlauf immun. Vorzeichenlose Subtraktion wird modulo 2 hoch 32 gerechnet. Ist zum Beispiel onceki = 4.294.967.000 und nach dem Überlauf simdi = 200, ergibt simdi - onceki keine negative Zahl, sondern das richtige Ergebnis 496. Solange dein Intervall kürzer als 49,7 Tage ist, funktioniert der Vergleich also immer.
| Schreibweise | Ergebnis | Warum? |
|---|---|---|
| if (millis() - onceki >= aralik) | Richtig | Vorzeichenlose Subtraktion liefert auch über den Überlauf hinweg die richtige Differenz |
| if (millis() >= onceki + aralik) | Falsch | Läuft onceki + aralik über, wird daraus eine kleine Zahl und die Bedingung greift zu früh |
| if (millis() == hedef) | Falsch | millis() springt manchmal um 2, der genaue Wert taucht eventuell nie auf |
| int onceki = millis(); | Falsch | Auf dem Uno hat int 16 Bit und speichert nach 32,7 Sekunden falsche Werte |
| long onceki = millis(); | Riskant | Ein vorzeichenbehafteter Typ wird nach 24,8 Tagen negativ; nimm unsigned long, um Verwirrung zu vermeiden |
Mikrosekunden messen mit micros()
micros() funktioniert genauso, liefert aber die Mikrosekunden seit dem Start. Auf 16-MHz-Boards wie Arduino Uno und Nano beträgt die Auflösung 4 µs (auf 8-MHz-Boards 8 µs), und der Zähler läuft etwa alle 71,6 Minuten über; die Subtraktionsregel gilt auch hier. micros() eignet sich für präzise Aufgaben: messen, wie lange ein Codeabschnitt dauert, kurze Impulse timen oder Töne erzeugen. Das folgende Beispiel misst zweimal pro Sekunde, wie lange ein analogRead()-Aufruf dauert; auf dem Uno siehst du etwa 110–120 µs:
1unsigned long raporZaman = 0;
2
3void setup() {
4 Serial.begin(9600);
5}
6
7void loop() {
8 if (millis() - raporZaman >= 500) { // yarım saniyede bir ölç / measure every half second
9 raporZaman = millis();
10 unsigned long basla = micros(); // başlangıç (µs) / start (µs)
11 int deger = analogRead(A0);
12 unsigned long sure = micros() - basla; // geçen süre (µs) / elapsed time (µs)
13 Serial.print(F("A0 = "));
14 Serial.print(deger);
15 Serial.print(F(" sure / time: "));
16 Serial.print(sure);
17 Serial.println(F(" us"));
18 }
19}Für längere Intervalle nimm lieber millis() statt micros(); 71 Minuten bis zum Überlauf sind für lange Wartezeiten zu knapp. delayMicroseconds() ist nur für kurze, unvermeidbare Wartezeiten von wenigen Mikrosekunden gedacht, etwa für den 10-µs-Triggerimpuls am TRIG-Pin des Ultraschallsensors HC-SR04. So kurze Pausen bremsen deine anderen Aufgaben nicht spürbar.
Timer-Muster: periodisch, einmalig und Zustandsautomat
- Periodische Aufgabe: eine Aufgabe, die sich in festen Abständen wiederholt, etwa eine blinkende LED, eine Sensormessung alle 2 Sekunden oder eine Displayaktualisierung pro Sekunde. Das Muster kennst du schon von oben.
- Einmal-Timer (Timeout): läuft einmal, eine bestimmte Zeit nach einem Ereignis. Treppenhauslicht ist ein gutes Beispiel: Bei Bewegung Licht an, Zeit merken, und wenn 30 Sekunden lang keine neue Bewegung kommt, Licht aus. Jede neue Bewegung setzt die Zeit zurück und verlängert die Dauer.
- Warten mit Zeitlimit: Wenn du auf eine Antwort oder ein Signal wartest, setzt du eine Obergrenze, damit du nie ewig hängen bleibst; kommen zum Beispiel innerhalb von 2 Sekunden keine Daten, zeigst du einen Fehler an.
- Zustandsautomat (State Machine): Du speicherst den aktuellen Schritt des Geräts in einer Variablen und gehst zum nächsten Schritt, sobald die Zeit des aktuellen abgelaufen ist. Ampeln, Waschmaschinenprogramme und Spiellevels werden so geschrieben.
- Langen Tastendruck erkennen: Misst du mit millis(), wie lange ein Taster gehalten wird, kannst du kurzen und langen Drücken unterschiedliche Aufgaben geben.
1// Yeniden kullanılabilir zamanlayıcı / Reusable timer
2struct Zamanlayici {
3 unsigned long baslangic = 0;
4 unsigned long sure = 0;
5
6 void kur(unsigned long ms) { // süreyi şimdiden başlat / start the period from now
7 baslangic = millis();
8 sure = ms;
9 }
10 bool doldu() const { // süre doldu mu? / has it expired?
11 return millis() - baslangic >= sure;
12 }
13};
14
15const int KIRMIZI = 11, SARI = 12, YESIL = 13;
16enum Durum { DUR, HAZIRLAN, GEC, YAVASLA }; // trafik lambası adımları / traffic light states
17Durum durum = DUR;
18Zamanlayici isik;
19
20void lambalar(bool k, bool s, bool y) {
21 digitalWrite(KIRMIZI, k);
22 digitalWrite(SARI, s);
23 digitalWrite(YESIL, y);
24}
25
26void setup() {
27 pinMode(KIRMIZI, OUTPUT);
28 pinMode(SARI, OUTPUT);
29 pinMode(YESIL, OUTPUT);
30 lambalar(true, false, false);
31 isik.kur(5000); // kırmızı 5 sn / red for 5 s
32}
33
34void loop() {
35 if (isik.doldu()) {
36 switch (durum) {
37 case DUR: durum = HAZIRLAN; lambalar(true, true, false); isik.kur(1500); break;
38 case HAZIRLAN: durum = GEC; lambalar(false, false, true); isik.kur(5000); break;
39 case GEC: durum = YAVASLA; lambalar(false, true, false); isik.kur(2000); break;
40 case YAVASLA: durum = DUR; lambalar(true, false, false); isik.kur(5000); break;
41 }
42 }
43 // loop serbest: buton, sensör, ekran... / loop stays free: buttons, sensors, display...
44}In diesem Code verkürzt eine kleine Struktur (struct) namens Zamanlayici das Starten einer Zeitspanne und die Frage, ob sie abgelaufen ist, auf jeweils eine Zeile. Die Ampel ist ein Zustandsautomat mit vier Schritten: In jedem Schritt werden die Lampen gesetzt und die Zeit bis zum nächsten Wechsel gestartet. Weil loop() nie wartet, kannst du problemlos einen Fußgängertaster, einen LDR-Lichtsensor für einen Nachtmodus oder ein Display mit der Restzeit am selben Board ergänzen. Brauchst du viele Timer, arbeiten die fertigen Timer-Bibliotheken aus dem Bibliotheksverwalter nach demselben Prinzip.
millis(), delay() und Ticker auf dem ESP32
Im Arduino-Core für den ESP32 heißen millis() und micros() genauso und liefern denselben 32-Bit-Typ unsigned long; ihre Werte werden aus dem 64-Bit-Zähler esp_timer_get_time() berechnet. Die Überlaufzeiten von 49,7 Tagen und 71,6 Minuten gelten also auch hier, und du verwendest dieselbe Subtraktionsregel. Der wichtige Unterschied liegt bei delay(): Auf dem ESP32 ruft delay() die FreeRTOS-Funktion vTaskDelay() auf. Der Prozessor dreht nicht leer, während der Wartezeit laufen andere Tasks wie WLAN und Bluetooth weiter. Deine loop() wartet aber trotzdem, sodass Webserver, MQTT oder Tasterabfragen verzögert werden. Für periodische Aufgaben ist auch die Ticker-Bibliothek praktisch, die mit dem ESP32-Core geliefert wird:
1#include <Ticker.h>
2
3const int LED1 = 2; // çoğu DevKit'te dahili LED / built-in LED on most DevKits
4const int LED2 = 4;
5
6Ticker ticker;
7bool led1Durum = false;
8bool led2Durum = false;
9unsigned long oncekiZaman = 0;
10
11void led1Degistir() { // kısa tut, uzun iş yapma / keep it short, no long work
12 led1Durum = !led1Durum;
13 digitalWrite(LED1, led1Durum);
14}
15
16void setup() {
17 Serial.begin(115200);
18 pinMode(LED1, OUTPUT);
19 pinMode(LED2, OUTPUT);
20 ticker.attach_ms(250, led1Degistir); // her 250 ms'de çağır / call every 250 ms
21}
22
23void loop() {
24 unsigned long simdi = millis();
25 if (simdi - oncekiZaman >= 1000) { // Arduino ile aynı desen / same pattern as on Arduino
26 oncekiZaman = simdi;
27 led2Durum = !led2Durum;
28 digitalWrite(LED2, led2Durum);
29 int64_t us = esp_timer_get_time(); // 64 bit, pratikte taşmaz / 64-bit, practically never overflows
30 Serial.printf("millis=%lu esp_timer=%lld us\n", simdi, us);
31 }
32}Die an Ticker übergebene Funktion wird von einem Timer-Task im Hintergrund aufgerufen; erledige darin nichts Langsames (HTTP-Anfragen, lange serielle Ausgaben, delay), sondern setze nur ein Flag oder schalte einen Pin. Das 64-Bit-esp_timer_get_time() läuft erst nach Hunderttausenden von Jahren über; du kannst es für mikrosekundengenaue Messungen auf Geräten nutzen, die monatelang laufen. Der Pin der eingebauten LED kann je nach Board abweichen; die Pinbelegung deines Boards findest du im Tool Pinout. Bist du neu beim ESP32, ist der Artikel Was ist der ESP32? ein guter Einstieg für Installation und Pinwahl.
Raspberry Pi Pico (MicroPython): ticks_ms() und ticks_diff()
In MicroPython nutzt du time.sleep(), time.sleep_ms() und time.sleep_us() statt delay() und time.ticks_ms() statt millis(). Aber Vorsicht: Die Tick-Werte laufen nicht wie beim Arduino bei 2 hoch 32 über, sondern bei einer kleineren, vom Port abhängigen Grenze. Auf dem Pico liegt sie bei 2 hoch 30 ms, also etwa 12,4 Tagen (auf deinem Board prüfst du das mit time.ticks_add(0, -1)). Subtrahiere oder vergleiche zwei Tick-Werte deshalb nie direkt; bilde die Differenz mit time.ticks_diff(neu, alt) und berechne künftige Zeitpunkte mit time.ticks_add(). Der folgende Code lässt die eingebaute LED des Pico ohne Blockieren blinken, während ein Taster an GP14 eine LED an GP15 umschaltet:
1from machine import Pin
2import time
3
4led = Pin("LED", Pin.OUT) # dahili LED (Pico ve Pico W) / onboard LED (Pico and Pico W)
5dis_led = Pin(15, Pin.OUT) # GP15'e harici LED / external LED on GP15
6buton = Pin(14, Pin.IN, Pin.PULL_UP) # GP14 buton, diğer ucu GND / GP14 button, other leg to GND
7
8ARALIK = 500 # ms
9onceki = time.ticks_ms()
10son_okuma = 1 # son ham okuma / last raw reading
11kararli = 1 # titreşimsiz durum / debounced state
12degisim = time.ticks_ms() # son değişim anı / time of the last change
13
14while True:
15 simdi = time.ticks_ms()
16 if time.ticks_diff(simdi, onceki) >= ARALIK: # taşmaya karşı güvenli / wrap-safe
17 onceki = time.ticks_add(onceki, ARALIK) # kaymasız / drift-free
18 led.toggle()
19 okuma = buton.value()
20 if okuma != son_okuma: # değişti, sayacı yeniden başlat / changed, restart the timer
21 degisim = simdi
22 son_okuma = okuma
23 if time.ticks_diff(simdi, degisim) >= 50 and okuma != kararli: # 50 ms debounce
24 kararli = okuma
25 if kararli == 0: # basıldı / pressed
26 dis_led.toggle()
27Für periodische Aufgaben gibt es in MicroPython außerdem machine.Timer: Die Zeile Timer(period=500, mode=Timer.PERIODIC, callback=funktion) ruft deine Funktion alle 500 ms auf. Halte diese Callbacks genauso kurz wie bei Ticker. Programmierst du den Pico mit der Arduino IDE (arduino-pico-Core), funktionieren millis() und micros() genau wie beim Arduino. Wie du MicroPython auf den Pico bringst und seine Pins kennenlernst, erfährst du im Artikel Was ist der Raspberry Pi Pico?.
Projektideen mit millis()
- Reaktionstest: Lass eine LED zu einem zufälligen Zeitpunkt aufleuchten, miss mit millis() die Zeit bis zum Tastendruck und zeige das Ergebnis in Millisekunden an.
- Treppenhauslicht: Schalte das Licht ein, wenn ein PIR-Sensor Bewegung erkennt, und 30 Sekunden nach der letzten Bewegung wieder aus, während der Code andere Aufgaben weiter erledigt.
- Schneller werdender Parksensor: Verkürze den Piepabstand des Buzzers, je kürzer die mit dem HC-SR04 gemessene Entfernung wird.
- Nicht blockierende Melodie: Spiele die Noten auf einem Buzzer mit millis() statt delay() ab, damit die Taster auch während der Melodie funktionieren.
- Sensor und Display zusammen: Lies den DHT11-Sensor alle 2 Sekunden, aktualisiere das Display aber alle 250 ms.
- Stoppuhr: Baue eine Stoppuhr mit Start-, Stopp- und Reset-Taster, die die Zeit auf einer 7-Segment-Anzeige zeigt.
Häufige Fehler und Lösungen
| Fehler | Symptom | Lösung |
|---|---|---|
| Zeitvariable als int deklariert | Das Programm spielt nach etwa 33 Sekunden verrückt | Mach alle Zeitvariablen zu unsigned long |
| millis() >= onceki + aralik geschrieben | Die Zeitsteuerung versagt nach 49,7 Tagen | Wechsle zur Form millis() - onceki >= aralik |
| Prüfung auf millis() == hedef | Das Ereignis wird manchmal nie ausgelöst | Nutze >= statt Gleichheit |
| onceki nicht aktualisiert | Nach Ablauf wird die Aufgabe bei jedem Durchlauf wiederholt | Schreibe onceki = simdi oder onceki += aralik in die Bedingung |
| Ein delay() steckt noch im Timer-Code | Andere Aufgaben werden weiterhin verzögert | Teile lange Wartezeiten in einen Zustandsautomaten auf |
| Mit millis() in einer Interrupt-Routine warten | Das Board hängt sich auf | In einer ISR nie warten, nur ein Flag setzen |
| int-Produkt wie 60 * 1000 | Das Intervall wird negativ oder viel zu kurz | Schreibe 60000UL oder 60UL * 1000 |
Hast du das millis()-Prinzip einmal verstanden, verändern sich deine Projekte komplett: LEDs, Taster, Sensoren und Displays arbeiten zusammen, ohne aufeinander zu warten. Als nächsten Schritt kannst du dir aus den Arduino-Projektideen ein Projekt mit Zeitsteuerung aussuchen. In der BlueGrays-App findest du Projekte wie Ampel, Polizei-Blinklicht, intelligente Treppen- und Flurbeleuchtung und den Reaktionstest mit Tonsignal Schritt für Schritt mit Schaltplan und fertigem Code, sodass du sie direkt auf deinem eigenen Board ausprobieren kannst.
Häufige Fragen
Was macht millis() beim Arduino?
millis() ist eine Funktion, die die Zeit seit dem Einschalten oder Reset des Arduino in Millisekunden liefert. Der Wert ist ein unsigned long und beginnt nach etwa 49,7 Tagen wieder bei null. Man nutzt sie, um Abläufe zu takten, ohne das Programm anzuhalten.
Was ist der Unterschied zwischen millis() und delay()?
delay() hält das Programm für die angegebene Zeit an, sodass in der Zwischenzeit nichts anderes passiert, etwa Taster lesen oder das Display aktualisieren. millis() liefert nur die verstrichene Zeit und wartet nie. Indem du die verstrichene Zeit prüfst und fällige Aufgaben ausführst, kannst du mehrere Aufgaben gleichzeitig erledigen.
Stürzt mein Programm ab, wenn millis() nach 49 Tagen überläuft?
Nein, wenn du die Zeit per Subtraktion in der Form millis() - onceki >= aralik prüfst; vorzeichenlose Subtraktion liefert auch über den Überlauf hinweg die richtige Differenz. Probleme gibt es nur bei Vergleichen mit Addition wie millis() >= onceki + aralik und bei int-Variablen.
Kann man millis() beim Arduino zurücksetzen?
Eine offizielle Funktion zum Zurücksetzen von millis() gibt es nicht, und die interne Variable des Cores zu ändern ist nicht empfehlenswert. Das ist auch unnötig: Speichere den gewünschten Startzeitpunkt in einer Variablen und rechne die verstrichene Zeit ab dort. Ein Reset des Boards startet millis() ohnehin wieder bei 0.
Kann ein Arduino zwei Dinge gleichzeitig tun?
Da der Uno nur einen Kern hat, kann er zwei Befehle nicht physisch gleichzeitig ausführen. Teilst du aber jede Aufgabe mit millis() in kurze Schritte, durchläuft loop() tausende Male pro Sekunde und jede Aufgabe kommt an die Reihe. Von außen scheinen LED, Taster, Sensor und Display gleichzeitig zu arbeiten. Der ESP32 bietet zusätzlich zwei Kerne und FreeRTOS-Tasks.
Ist millis() genau genug für eine Uhr?
millis() hängt vom Taktgeber des Boards ab; wegen des Keramikresonators auf dem Arduino Uno kann die Abweichung bis zu einige Minuten pro Tag betragen. Für kurze Intervalle reicht das, aber für eine Uhr, die tagelang stimmen soll, ist ein RTC-Modul wie DS1307 oder DS3231 oder die Zeit per NTP auf einem ESP32 genauer.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
LED Blinken
Einfaches Eingabeprojekt, das eine einzelne LED in bestimmten Intervallen mit Arduino ein- und ausschaltet. Lehrt die Verwendung von digitalWrite und Verzögerungsfunktionen.

Ampel
Projekt, das eine echte Ampel mit roten, gelben und grünen LEDs simuliert. Lehrt das Timing und die sequentielle Steuerlogik.

LED-Polizeilicht
Projekt zur Erzeugung eines Polizeistrobe-Lichteffekts durch Blinken zweier LEDs in schnellen Intervallen. Lehrt für Schleifen- und Zeitsteuerung.

LED-Lauflicht mit einstellbarer Geschwindigkeit
Das Projekt, das die LED-Laufgeschwindigkeit in Echtzeit mit dem Potentiometer einstellen kann. Es lehrt die dynamische Steuerung der Verzögerungszeit durch das Lesen des Potentiometerwerts mit analog

LED mit Taster ein- und ausschalten
Ein Toggle-Projekt, das den Zustand der LED (ein/aus) bei jedem Tastendruck ändert. Lehrt Zustandsvariable und Entprellungslogik.

Intelligente Treppen- und Korridorbeleuchtung
Ein elegantes Automatisierungsprojekt mit einem bidirektionalen Abstandssensor HC-SR04, der in Bewegungsrichtung abwechselnd leuchtet und erlischt.

Arduino Reaktionstest mit Tonsignal
Arduino-Reaktionsteststation Bei diesem Projekt handelt es sich um einen digitalen Testaufbau, der entwickelt wurde, um die Reflexgeschwindigkeit eines Benutzers auf visuelle und auditive Reize mit einer Genauigkeit von Millisekunden (ms) zu messen. Das System besteht aus einem Arduino-Controller, einer dreistufigen LED-Benachrichtigungsgruppe, einem Summer für akustische Warnungen und einem I2C-LCD-Display, das die Ergebnisse sofort anzeigt. Arbeitsprinzip: Wenn der Test beginnt, bereitet das

Digitaluhr mit 4-stelliger 7-Segment-Anzeige
Digitales Uhrenprojekt mit Stunden- und Minutenanzeige mit DS1302 RTC-Modul und 4-stelliger 7-Segment-Anzeige. Es kombiniert Echtzeituhrdaten und Segmentanzeige.
Weitere Artikel
Erfahre, warum ein Taster mit delay() verzögert reagiert, wie du mit millis() LEDs, Taster und Sensoren gleichzeitig steuerst, wie dein Code den Überlauf nach 49,7 Tagen übersteht und was ESP32 und Pico stattdessen nutzen.

