Grundlagen · 10 Min. Lesezeit

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.

Arduino Uno mit eingebauter L-LED
Arduino Uno: Dank der eingebauten "L"-LED an Pin 13 brauchst du für die ersten millis()-Versuche keine zusätzlichen Bauteile.

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:

Arduino (C++)
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).

FunktionWas sie tutAuflösung (Uno, 16 MHz)Überlauf nachBlockiert das Programm?
delay(ms)Wartet die angegebenen Millisekunden1 ms-Ja
delayMicroseconds(us)Kurzes Warten (genau von 3 bis 16383 µs)Etwa 1 µs-Ja
millis()Millisekunden seit dem Start1 ms (springt gelegentlich um 2)Etwa 49,7 TageNein
micros()Mikrosekunden seit dem Start4 µsEtwa 71,6 MinutenNein
  1. Lege eine Variable vom Typ unsigned long an, die sich merkt, wann die Aufgabe zuletzt erledigt wurde (oncekiZaman).
  2. Lies bei jedem Durchlauf von loop() mit millis() die aktuelle Zeit.
  3. Prüfe mit der Bedingung simdi - oncekiZaman >= ARALIK, ob genug Zeit vergangen ist.
  4. Ist die Zeit um, erledige die Aufgabe (LED umschalten) und aktualisiere oncekiZaman.
  5. Ist sie noch nicht um, tu nichts und lass loop() weiterlaufen; genau dann kommen deine anderen Aufgaben zum Zug.
Arduino (C++)
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:

Arduino (C++)
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}
AufgabePinIntervallWas sie tut
Rote LED8300 msBlinkt schnell
Grüne LED91000 msBlinkt langsam
Taster + gelbe LED2 und 1050 ms EntprellungSchaltet die gelbe LED bei jedem Druck um
Serieller Bericht-1000 msGibt 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.

SchreibweiseErgebnisWarum?
if (millis() - onceki >= aralik)RichtigVorzeichenlose Subtraktion liefert auch über den Überlauf hinweg die richtige Differenz
if (millis() >= onceki + aralik)FalschLäuft onceki + aralik über, wird daraus eine kleine Zahl und die Bedingung greift zu früh
if (millis() == hedef)Falschmillis() springt manchmal um 2, der genaue Wert taucht eventuell nie auf
int onceki = millis();FalschAuf dem Uno hat int 16 Bit und speichert nach 32,7 Sekunden falsche Werte
long onceki = millis();RiskantEin vorzeichenbehafteter Typ wird nach 24,8 Tagen negativ; nimm unsigned long, um Verwirrung zu vermeiden
Deklariere Zeitvariablen niemals als int. Auf dem Arduino Uno hat int 16 Bit und fasst höchstens 32.767, dein Programm verhält sich also ab der 33. Sekunde seltsam. Jede Variable, die mit millis() oder micros() arbeitet, sollte unsigned long (oder uint32_t) sein. Schreibe große Intervalle außerdem als 60000UL: Auf dem Uno sprengt ein Produkt wie 60 * 1000 die int-Grenze und wird negativ.

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:

Arduino (C++)
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.
Arduino (C++)
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:

Arduino (C++)
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:

MicroPython
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()
27

Fü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

FehlerSymptomLösung
Zeitvariable als int deklariertDas Programm spielt nach etwa 33 Sekunden verrücktMach alle Zeitvariablen zu unsigned long
millis() >= onceki + aralik geschriebenDie Zeitsteuerung versagt nach 49,7 TagenWechsle zur Form millis() - onceki >= aralik
Prüfung auf millis() == hedefDas Ereignis wird manchmal nie ausgelöstNutze >= statt Gleichheit
onceki nicht aktualisiertNach Ablauf wird die Aufgabe bei jedem Durchlauf wiederholtSchreibe onceki = simdi oder onceki += aralik in die Bedingung
Ein delay() steckt noch im Timer-CodeAndere Aufgaben werden weiterhin verzögertTeile lange Wartezeiten in einen Zustandsautomaten auf
Mit millis() in einer Interrupt-Routine wartenDas Board hängt sich aufIn einer ISR nie warten, nur ein Flag setzen
int-Produkt wie 60 * 1000Das Intervall wird negativ oder viel zu kurzSchreibe 60000UL oder 60UL * 1000
So prüfst du einfach, ob dein Code wirklich nicht blockiert: Zähle, wie oft loop() pro Sekunde durchläuft. Erhöhe bei jedem Durchlauf einen Zähler, gib ihn einmal pro Sekunde seriell aus und setze ihn zurück. Fällt die Zahl plötzlich von Tausenden auf einstellige Werte, wartet irgendwo ein Codeteil (delay, pulseIn oder eine lange while-Schleife).

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.

Alle Projekte ansehen
LED Blinken
#1Anfänger

LED Blinken

Einfaches Eingabeprojekt, das eine einzelne LED in bestimmten Intervallen mit Arduino ein- und ausschaltet. Lehrt die Verwendung von digitalWrite und Verzögerungsfunktionen.

4.7(163)
784
Ampel
#3Anfänger

Ampel

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

4.8(123)
306
LED-Polizeilicht
#4Anfänger

LED-Polizeilicht

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

4.7(95)
212
LED-Lauflicht mit einstellbarer Geschwindigkeit
#7Mittel

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

4.6(71)
133
LED mit Taster ein- und ausschalten
#10Anfänger

LED mit Taster ein- und ausschalten

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

4.8(100)
158
Intelligente Treppen- und Korridorbeleuchtung
#82Mittel
Premium

Intelligente Treppen- und Korridorbeleuchtung

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

5.0(35)
51
Arduino Reaktionstest mit Tonsignal
#97Mittel
Premium

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

4.6(33)
49
Digitaluhr mit 4-stelliger 7-Segment-Anzeige
#48Mittel
Premium

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.

4.8(56)
73

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.