Basi · 11 min di lettura

Arduino millis() invece di delay(): temporizzare senza bloccare

Arduino millis(): perché delay() blocca lo sketch, blink senza delay, più attività insieme, overflow dei 49 giorni senza errori, micros(), ESP32 e Pico.

La funzione Arduino millis() restituisce il tempo trascorso dall'avvio della scheda, in millisecondi, ed è il modo standard per temporizzare senza usare delay(). Quando scrivi delay(1000), Arduino non fa nient'altro per un intero secondo: se premi un pulsante non se ne accorge, i sensori non vengono letti e il display non si aggiorna. Con millis(), invece, ti chiedi "quanto tempo è passato dall'ultima attività?"; esegui le attività in scadenza, salti le altre e loop() non si ferma mai. Così un solo Arduino può far lampeggiare LED, leggere pulsanti, misurare sensori e inviare report sulla porta seriale tutto insieme.

In questa guida imparerai perché delay() blocca il programma, il famoso esempio blink without delay (lampeggio senza delay), come eseguire più attività contemporaneamente, come superare senza problemi l'overflow che avviene dopo 49,7 giorni, la funzione micros() che conta i microsecondi e alcuni schemi di temporizzazione riutilizzabili. Nelle ultime sezioni vedrai come si scrive la stessa logica su ESP32 e su Raspberry Pi Pico (MicroPython).

Scheda Arduino Uno con il LED L integrato
Arduino Uno: grazie al LED "L" integrato sul pin 13 non servono componenti aggiuntivi per provare millis().

Che cos'è delay() e perché ferma il programma?

delay(ms) tiene il processore occupato in un ciclo finché non è trascorso il tempo indicato. In quel periodo la riga successiva di loop() non viene raggiunta; si parla di attesa bloccante. Per far lampeggiare un solo LED non è un problema, ma appena aggiungi un pulsante al progetto iniziano i guai. Il codice seguente fa lampeggiare un LED ogni secondo e controlla anche se il pulsante è premuto:

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}

In questo codice il pulsante viene rilevato solo una volta ogni due secondi, e solo se è premuto proprio in quell'istante; una pressione breve si perde tra le due chiamate a delay(). Gli interrupt continuano a funzionare durante delay(): millis() continua a contare e i byte in arrivo sulla porta seriale vengono salvati nel buffer da 64 byte. Ma il tuo codice aspetta. Man mano che aggiungi display, sensori e buzzer, ogni delay() rallenta tutte le altre attività e il dispositivo sembra lento. La soluzione è smettere di aspettare e iniziare a misurare il tempo.

Che cos'è millis() e come funziona?

millis() restituisce i millisecondi trascorsi dall'accensione o dal reset della scheda, come unsigned long (32 bit, senza segno). Su Arduino Uno questo contatore viene aggiornato in background dall'interrupt di overflow del timer hardware Timer0. A 16 MHz Timer0 va in overflow ogni 1,024 ms; il core accumula gli 0,024 ms in eccesso e, circa ogni 43 ms, incrementa il contatore di 2 invece che di 1. Quindi millis() salta alcuni valori, ed è per questo che i controlli di uguaglianza come millis() == 5000 non sono affidabili; usa sempre >=. Inoltre millis() non avanza mentre gli interrupt sono disabilitati (per esempio dentro una routine di interrupt).

FunzioneCosa faRisoluzione (Uno, 16 MHz)Overflow dopoBlocca il programma?
delay(ms)Attende i millisecondi indicati1 ms-Sì
delayMicroseconds(us)Attesa breve (precisa da 3 a 16383 µs)Circa 1 µs-Sì
millis()Millisecondi dall'avvio1 ms (a volte salta di 2)Circa 49,7 giorniNo
micros()Microsecondi dall'avvio4 µsCirca 71,6 minutiNo
  1. Crea una variabile di tipo unsigned long per ricordare quando l'attività è stata eseguita l'ultima volta (oncekiZaman).
  2. A ogni giro di loop(), leggi il tempo attuale con millis().
  3. Controlla se è passato abbastanza tempo con la condizione simdi - oncekiZaman >= ARALIK.
  4. Se il tempo è scaduto, esegui l'attività (inverti il LED) e aggiorna oncekiZaman.
  5. Se non è scaduto, non fare nulla e lascia proseguire loop(); è proprio in quel momento che girano le altre attività.
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}

Nel codice abbiamo scritto oncekiZaman += ARALIK invece di oncekiZaman = simdi. La differenza è piccola ma importante: se loop() arriva a volte con qualche millisecondo di ritardo, il primo metodo accumula quel ritardo ogni volta e il LED deriva nel corso delle ore; il secondo fa avanzare l'orario di riferimento a passi fissi, così il periodo medio resta esattamente di 500 ms. Usa += per orologi, metronomi e misure periodiche. Se il programma può restare bloccato a lungo (per esempio in attesa del Wi-Fi), += cercherà di recuperare i passi persi uno dopo l'altro; in quel caso = simdi è più sicuro. Se vuoi cambiare anche la luminosità del LED senza bloccare il codice, dai un'occhiata all'articolo su PWM e analogWrite.

Più cose insieme: il multitasking con millis()

Arduino Uno ha un processore single-core e non può davvero fare due cose nello stesso momento. Ma siccome loop() può girare decine di migliaia di volte al secondo, dedicare qualche microsecondo a ogni attività quando arriva il suo turno sembra "simultaneo" visto dall'esterno. Questo si chiama multitasking cooperativo. La regola è semplice: ogni attività ha la propria variabile di tempo e il proprio intervallo, e nessuna attività aspetta a lungo. Nell'esempio seguente quattro attività girano fianco a fianco:

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}
AttivitàPinIntervalloCosa fa
LED rosso8300 msLampeggia veloce
LED verde91000 msLampeggia lento
Pulsante + LED giallo2 e 10Debounce di 50 msAccende o spegne il LED giallo a ogni pressione
Report seriale-1000 msStampa il tempo di funzionamento in secondi

Anche la logica del pulsante si basa su millis(): ogni volta che la lettura grezza cambia il timer riparte, e se la lettura resta stabile per 50 ms la pressione viene considerata reale. Così i rimbalzi meccanici del pulsante non trasformano una pressione in decine di pressioni, e il codice non aspetta mai. Per collegare un pulsante e usare la resistenza di pull-up interna, leggi Come collegare un pulsante ad Arduino. Per i LED basta una resistenza da 220 Ω; per altri colori e correnti usa il calcolatore di resistenza per LED.

L'overflow dei 49,7 giorni e i confronti sicuri

Un unsigned long può contenere al massimo 4.294.967.295. Quando millis() raggiunge quel numero, dopo circa 49 giorni, 17 ore e 2 minuti, ricomincia a contare da 0. Se la tua scheda resta accesa per settimane (controllo di una serra, acquario, sistema d'allarme), quel momento arriverà di sicuro. La buona notizia: il codice scritto con la sottrazione, nella forma simdi - onceki >= aralik, non risente dell'overflow. La sottrazione senza segno si calcola modulo 2 alla 32. Per esempio, se onceki = 4.294.967.000 e dopo l'overflow simdi = 200, l'operazione simdi - onceki non dà un numero negativo ma il risultato corretto, 496. Quindi, finché l'intervallo che aspetti è più corto di 49,7 giorni, il confronto funziona sempre.

ScritturaRisultatoPerché?
if (millis() - onceki >= aralik)CorrettoLa sottrazione senza segno dà la differenza giusta anche attraverso l'overflow
if (millis() >= onceki + aralik)SbagliatoQuando onceki + aralik va in overflow diventa un numero piccolo e la condizione scatta troppo presto
if (millis() == hedef)Sbagliatomillis() a volte salta di 2, il valore esatto potrebbe non comparire mai
int onceki = millis();SbagliatoSu Uno un int è a 16 bit e contiene valori errati dopo 32,7 secondi
long onceki = millis();RischiosoUn tipo con segno diventa negativo dopo 24,8 giorni; usa unsigned long per evitare confusione
Non dichiarare mai le variabili di tempo come int. Su Arduino Uno un int è a 16 bit e contiene al massimo 32.767, quindi il programma inizia a comportarsi in modo strano dal 33° secondo. Ogni variabile che lavora con millis() o micros() deve essere unsigned long (o uint32_t). Quando scrivi intervalli grandi usa anche la forma 60000UL: su Uno un prodotto come 60 * 1000 supera il limite di un int e diventa un numero negativo.

Misurare i microsecondi con micros()

micros() funziona allo stesso modo ma restituisce i microsecondi dall'avvio. Sulle schede a 16 MHz come Arduino Uno e Nano la risoluzione è di 4 µs (8 µs sulle schede a 8 MHz) e va in overflow circa ogni 71,6 minuti; anche qui vale la regola della sottrazione. Si usa per lavori di precisione come misurare quanto dura un pezzo di codice, cronometrare impulsi brevi o generare suoni. L'esempio seguente misura due volte al secondo quanto dura una chiamata ad analogRead(); su Uno vedrai circa 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}

Per intervalli più lunghi preferisci millis() a micros(); 71 minuti prima dell'overflow sono pochi per attese lunghe. delayMicroseconds() serve solo per attese brevi e inevitabili di pochi microsecondi, come inviare l'impulso di trigger da 10 µs al pin TRIG del sensore di distanza HC-SR04. Attese così brevi non rallentano in modo percepibile le altre attività.

Schemi di temporizzazione: periodico, singolo e macchina a stati

  • Attività periodica: un lavoro che si ripete a intervalli fissi, come un LED che lampeggia, la lettura di un sensore ogni 2 secondi o l'aggiornamento del display ogni secondo. Lo schema l'hai già visto sopra.
  • Timer singolo (timeout): si esegue una sola volta, a una certa distanza di tempo da un evento. La luce delle scale è un buon esempio: accendi la luce quando c'è movimento, salva l'orario e spegnila se per 30 secondi non arriva nuovo movimento. Aggiornare l'orario a ogni movimento prolunga la durata.
  • Attesa con timeout: quando aspetti una risposta o un segnale imposti un limite massimo per non restare bloccato per sempre; per esempio mostri un errore se i dati non arrivano entro 2 secondi.
  • Macchina a stati (state machine): tieni in una variabile il passo in cui si trova il dispositivo e passi al successivo quando scade il tempo di ogni passo. Semafori, programmi della lavatrice o livelli di un gioco si scrivono così.
  • Rilevare la pressione lunga: misurando con millis() quanto a lungo viene tenuto premuto un pulsante, puoi assegnare compiti diversi alle pressioni brevi e lunghe.
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 questo codice una piccola struttura (struct) chiamata Zamanlayici riduce a una sola riga l'avvio di un intervallo e la domanda se è scaduto. Il semaforo è una macchina a stati con quattro passi: a ogni passo si impostano le luci e si avvia il tempo fino al cambio successivo. Dato che loop() non aspetta mai, puoi aggiungere facilmente alla stessa scheda un pulsante pedonale, un sensore di luce LDR per la modalità notturna o un display con il tempo rimanente. Se ti servono molti timer, le librerie di temporizzazione del Gestore librerie funzionano con lo stesso principio.

millis(), delay() e Ticker su ESP32

Nel core Arduino per ESP32, millis() e micros() hanno gli stessi nomi e lo stesso tipo unsigned long a 32 bit; i loro valori vengono calcolati dal contatore a 64 bit esp_timer_get_time(). Per questo gli overflow di 49,7 giorni e 71,6 minuti valgono anche su ESP32 e usi la stessa regola della sottrazione. La differenza importante riguarda delay(): su ESP32 delay() chiama la funzione FreeRTOS vTaskDelay(). Il processore non gira a vuoto: durante l'attesa continuano a funzionare altri task come Wi-Fi e Bluetooth. Il tuo loop() però aspetta comunque, quindi un web server, MQTT o la lettura dei pulsanti vengono ritardati. Per le attività periodiche è comoda anche la libreria Ticker, inclusa nel core ESP32:

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}

La funzione passata a Ticker viene chiamata in background da un task del timer; non metterci nulla di lento (richieste HTTP, lunghi output seriali, delay), limitati ad alzare un flag o a cambiare un pin. Il contatore a 64 bit esp_timer_get_time() non va in overflow prima di centinaia di migliaia di anni, quindi puoi usarlo per misure con precisione al microsecondo su un dispositivo che resta acceso per mesi. Il pin del LED integrato può cambiare a seconda della scheda; puoi vedere la piedinatura della tua nello strumento Pinout. Se sei alle prime armi con ESP32, l'articolo Che cos'è l'ESP32? è un buon punto di partenza per l'installazione e la scelta dei pin.

Raspberry Pi Pico (MicroPython): ticks_ms() e ticks_diff()

In MicroPython si usano time.sleep(), time.sleep_ms() e time.sleep_us() al posto di delay(), e time.ticks_ms() al posto di millis(). Ma attenzione: i valori dei ticks non ricominciano da zero a 2 alla 32 come su Arduino, ma a un limite più piccolo che dipende dal port. Sul Pico questo limite è 2 alla 30 ms, cioè circa 12,4 giorni (puoi verificarlo sulla tua scheda con time.ticks_add(0, -1)). Per questo non sottrarre né confrontare mai direttamente due valori di ticks; calcola la differenza con time.ticks_diff(nuovo, vecchio) e usa time.ticks_add() per calcolare un istante futuro. Il codice seguente fa lampeggiare il LED integrato del Pico senza bloccare, mentre un pulsante su GP14 comanda un LED su GP15:

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

Per le attività periodiche MicroPython offre anche machine.Timer: la riga Timer(period=500, mode=Timer.PERIODIC, callback=funzione) chiama la tua funzione ogni 500 ms. Tieni brevi queste callback, proprio come con Ticker. Se programmi il Pico con l'Arduino IDE (core arduino-pico), millis() e micros() funzionano esattamente come su Arduino. Per installare MicroPython sul Pico e conoscerne i pin, leggi l'articolo Che cos'è il Raspberry Pi Pico?.

Idee di progetti con millis()

  • Test dei riflessi: accendi un LED in un momento casuale, misura con millis() il tempo fino alla pressione del pulsante e mostra il risultato in millisecondi.
  • Luce delle scale: accendi la luce quando un sensore PIR rileva movimento e spegnila 30 secondi dopo l'ultimo movimento, mentre il codice continua con le altre attività.
  • Sensore di parcheggio che accelera: accorcia l'intervallo dei bip del buzzer man mano che si riduce la distanza misurata con l'HC-SR04.
  • Melodia non bloccante: suona le note su un buzzer con millis() invece di delay(), così i pulsanti funzionano anche mentre suona la melodia.
  • Sensore e display insieme: leggi il sensore DHT11 ogni 2 secondi, ma aggiorna il display ogni 250 ms.
  • Cronometro: costruisci un cronometro con pulsanti di avvio, stop e azzeramento che mostri il tempo su un display a 7 segmenti.

Errori comuni e soluzioni

ErroreSintomoSoluzione
Variabile di tempo dichiarata come intIl programma impazzisce dopo circa 33 secondiRendi unsigned long tutte le variabili di tempo
Scrivere millis() >= onceki + aralikLa temporizzazione si guasta dopo 49,7 giorniPassa alla forma millis() - onceki >= aralik
Controllare millis() == hedefA volte l'evento non scatta maiUsa >= invece dell'uguaglianza
Dimenticare di aggiornare oncekiScaduto il tempo, l'attività si ripete a ogni giroScrivi onceki = simdi oppure onceki += aralik dentro la condizione
Un delay() rimasto nel codice del timerLe altre attività restano in ritardoSuddividi le attese lunghe in una macchina a stati
Attendere con millis() dentro un interruptLa scheda si bloccaNon aspettare mai in una ISR, alza solo un flag
Prodotto int come 60 * 1000L'intervallo risulta negativo o troppo cortoScrivi 60000UL o 60UL * 1000
Un modo semplice per verificare che il codice non blocchi davvero è contare quante volte al secondo gira loop(): incrementa un contatore a ogni giro, stampalo sulla porta seriale una volta al secondo e azzeralo. Se il numero crolla all'improvviso da migliaia a una sola cifra, da qualche parte c'è codice che aspetta (delay, pulseIn o un lungo ciclo while).

Una volta capita la logica di millis(), i tuoi progetti cambiano completamente: LED, pulsanti, sensori e display lavorano insieme senza aspettarsi a vicenda. Come passo successivo puoi scegliere un progetto che richiede temporizzazione dall'elenco di idee di progetti Arduino. Nell'app BlueGrays trovi passo passo progetti come il Semaforo, il Lampeggiante della polizia, l'Illuminazione intelligente per scale e corridoi e il Test dei riflessi con avviso sonoro, con schema del circuito e codice pronto, da provare sulla tua scheda.

Domande frequenti

Che cos'è millis() in Arduino?

millis() è una funzione che restituisce il tempo trascorso dall'accensione o dal reset di Arduino, in millisecondi. Il valore è di tipo unsigned long e ricomincia da zero dopo circa 49,7 giorni. Si usa per temporizzare senza fermare il programma.

Qual è la differenza tra millis() e delay()?

delay() mette in pausa il programma per il tempo indicato, quindi nel frattempo non può succedere nient'altro, come leggere pulsanti o aggiornare un display. millis() ti dice solo quanto tempo è passato e non aspetta mai. Controllando il tempo trascorso ed eseguendo l'attività in scadenza, puoi portare avanti più attività insieme.

Il programma si blocca quando millis() va in overflow dopo 49 giorni?

No, se controlli il tempo con una sottrazione nella forma millis() - onceki >= aralik; la sottrazione senza segno dà la differenza giusta anche attraverso l'overflow. I problemi compaiono solo con confronti scritti con l'addizione, come millis() >= onceki + aralik, e con variabili int.

Si può azzerare millis() su Arduino?

Non esiste una funzione ufficiale per azzerare millis() e modificare la variabile interna del core è sconsigliato. Non serve nemmeno: salva in una variabile l'istante da cui vuoi partire e calcola il tempo trascorso da lì. Resettare la scheda fa comunque ripartire millis() da 0.

Arduino può fare due cose contemporaneamente?

Poiché Uno ha un solo core non può eseguire fisicamente due istruzioni insieme, ma se dividi ogni attività in piccoli passi con millis(), loop() gira migliaia di volte al secondo e dà il turno a tutte. Dall'esterno LED, pulsante, sensore e display sembrano funzionare insieme. ESP32, in più, ha due core e i task di FreeRTOS.

millis() è abbastanza preciso per costruire un orologio?

millis() dipende dall'oscillatore della scheda; a causa del risonatore ceramico di Arduino Uno può derivare fino a qualche minuto al giorno. Va bene per misurare intervalli brevi, ma per un orologio che deve restare preciso per giorni è meglio un modulo RTC come DS1307 o DS3231, oppure l'ora presa via NTP con un ESP32.

Vedi tutti i progetti
LED lampeggiante
#1Principiante

LED lampeggiante

Progetto di input di base che accende e spegne un singolo LED a determinati intervalli con Arduino. Insegna l'uso delle funzioni digitalWrite e delay.

4.7(163)
784
Semaforo
#3Principiante

Semaforo

Progetto che simula un vero semaforo con LED rossi, gialli e verdi. Insegna la temporizzazione e la logica di controllo sequenziale.

4.8(123)
306
Lampeggiante della polizia a LED
#4Principiante

Lampeggiante della polizia a LED

Progetto che crea un effetto di luce stroboscopica della polizia facendo lampeggiare due LED a intervalli veloci. Insegna il controllo del loop e della temporizzazione.

4.7(95)
212
LED in sequenza a velocità regolabile
#7Intermedio

LED in sequenza a velocità regolabile

Il progetto consente di regolare in tempo reale la velocità di movimento del LED con il potenziometro e insegna a controllare dinamicamente il tempo di ritardo leggendo il valore del potenziometro con

4.6(71)
133
Accendere e spegnere un LED con un pulsante
#10Principiante

Accendere e spegnere un LED con un pulsante

Un progetto di commutazione che cambia lo stato del LED (acceso/spento) a ogni pressione del pulsante. Insegna la logica della variabile di stato e del debounce.

4.8(100)
158
Illuminazione intelligente di scale e corridoi
#82Intermedio
Premium

Illuminazione intelligente di scale e corridoi

Un elegante progetto di automazione con un sensore di distanza bidirezionale HC-SR04 che si accende e si spegne alternativamente nella direzione del movimento.

5.0(35)
51
Test di reazione con segnale acustico con Arduino
#97Intermedio
Premium

Test di reazione con segnale acustico con Arduino

Stazione di test di reazione Arduino Questo progetto è una stazione di test digitale sviluppata per misurare la velocità di reazione di un utente a stimoli visivi e uditivi con una precisione di millisecondi (ms). Il sistema è composto da un controller Arduino, un gruppo di LED di notifica a tre livelli, un cicalino per l'avviso acustico e un display LCD I2C che mostra i risultati istantaneamente. Principio di funzionamento: All'avvio del test, il sistema prepara l'utente accendendo rispettivam

4.6(33)
49
Orologio digitale con display a 7 segmenti a 4 cifre
#48Intermedio
Premium

Orologio digitale con display a 7 segmenti a 4 cifre

Progetto di orologio digitale con indicazione di ore e minuti con modulo RTC DS1302 e display a 7 segmenti a 4 cifre. Combina i dati dell'orologio in tempo reale e la visualizzazione dei segmenti.

4.8(56)
73

Scopri perché un pulsante risponde in ritardo con delay(), come gestire LED, pulsanti e sensori insieme con millis(), come superare l'overflow dei 49,7 giorni e quali sono gli equivalenti su ESP32 e Pico.

Arduino millis() invece di delay(): temporizzare senza bloccare | BlueGrays