Credo che una delle problematiche più ostiche da gestire soprattutto per i neofiti è l’utilizzo degli intervalli di tempo in cui eseguire operazioni con Arduino, mi riferisco all’uso e all’abuso improprio del delay(). Infatti gli studenti scoprono che, sebbene la funzione delay() sia facile da usare, ha degli effetti collaterali importanti; il principale è che ferma l’esecuzione dello sketch Arduino fino a quando non è trascorso il periodo di delay. Quando ciò accade, di solito chi spiega indirizza lo studente sull’esempio di defaut sulla temporizzazione non bloccante che troviamo nell’IDE di Arduino: BlinkWithoutDelay.
Molto spesso però questo confonde ancora di più le idee perché in realtà non si vuole solo far lampeggiare un LED ma si vogliono eseguire più operazioni contemporaneamente, quindi è bene comprendere a fondo il principio di funzionamento del BlinkWithoutDelay prima di poterlo applicare alla propria situazione.
Ho pensato quindi di realizzare qualche post tematico sull’uso di millis(), prendendo spunto dalle spiegazioni che realizzo per gli studenti.
Per usare millis() per la temporizzazione è necessario registrare il momento in cui un’azione si è verificata (ad esempio accensione di un LED ritardata alla pressione di un pulsante) affinché possiate iniziare a contare il tempo trascorso da tale evento, dovrete quindi controllare ad intervalli regolari se il periodo richiesto è trascorso.
Se tale intervallo di tempo non è trascorso allora il vostro programma potrà fare altro fino al prossimo controllo.
Nei programmi che seguono userò i commenti all’interno dello sketch per spiegare l’utilizzo delle varie parti del programma.
Ma cos’è millis()?
La funzione millis() restituisce il numero di millisecondi trascorsi dall’avvio del programma corrente su una scheda Arduino. Questo valore è restituito come un numero di tipo unsigned long.
Come Funziona
- Incremento Automatico: il conteggio inizia automaticamente quando il microcontrollore sulla scheda Arduino viene alimentato o resettato. Il conteggio continua ad aumentare fino a che la scheda rimane alimentata.
- Overflow: poiché millis() utilizza una variabile di tipo unsigned long, che ha una dimensione di 32 bit su Arduino, il valore massimo che può raggiungere è 4,294,967,295, dopo aver raggiunto questo valore, si andrà in overflow (ovvero Arduino non è in grado di memorizzare un numero più grande) e il valore restituito da millis() ripartirà da zero. Questo avviene dopo circa 49 giorni e 17 ore dall’ultimo reset della scheda.
Utilizzi di millis()
Di seguito una lista non esaustiva di alcuni utilizzi della funzione millis():
- Temporizzazione non bloccante: a differenza di delay(), che ferma l’esecuzione del programma per un periodo specificato, millis() può essere utilizzato per realizzare pause o attese senza bloccare altre operazioni. Questo è particolarmente utile in applicazioni multitasking dove altre attività devono continuare ad essere eseguite.
- Debounce: viene spesso usata per implementare il debounce di pulsanti o switch, riducendo gli effetti dei rimbalzi meccanici che possono causare letture multiple per una singola pressione.
- Esecuzione di azioni a intervalli regolari: può essere utilizzata per eseguire specifiche azioni a intervalli regolari, come leggere sensori, aggiornare display, o inviare dati.
Pratica di utilizzo
Per utilizzare millis() per la temporizzazione, il vostro sketch deve conoscere il valore attuale del tempo (valore restituito da millis()) e questa rilevazione può essere fatto quando volete, in più punti dello sketch ovviamente dovrebbe essere fatta quando serve, ma vediamo cosa vuol dire.
Tipicamente il valore di millis() conviene registrarlo in una variabile ad inizio loop() o all’interno del setup() in questo modo:
millisCorrente = millis();
Regola di utilizzo:
- La variabile millisCorrente deve essere stata precedentemente dichiarata.
- Deve essere di tipo unsigned long perché millis() restituisce un intero long senza segno.
Regole generali che valgono per tutti i programmi che realizzerete, lo scrivo perchè puntualmente durante le correzioni qualcuno dimentica questa due regolette:
- Il nome della variabile, così come accade per tutte le variabili dovrebbe essere autoesplicativa, quindi il suo nome deve dare informazioni: “a cosa serve”
- Inoltre è buona regola utilizzare la notazione Camel Case per gestire nomi di variabili composte da più parole, ciò vale anche per le funzioni.
Come spesso accade durante le attività di laboratorio, lascio come semplicissimo esercizio per lo studente il desumere i componenti utilizzati e connessioni dagli sketch di esempio, quindi fate riferimento a quanto indicato in ogni singolo programma, si tratterà semplicemente di connettere dei LED con in serie un resistore da 220 Ohm. Per quanto riguarda l’ultimo esempio fate riferimento al link indicato che rimanda ad un altro post su questo sito.
Siete ora pronti per seguire le spiegazioni successive.
Esempio 1 – blink senza delay versione 1
Per poter introdurre il primo esempio dell’uso di millis() avremo bisogno di altre due variabili per sapere se il periodo impostato è trascorso.
Per comprendere come procedere chiedetevi:
Conosciamo il valore corrente di millis(), ma quando è iniziato il periodo di temporizzazione e quanto è lungo il periodo?
Quindi per farlo, possiamo definire due variabili aggiuntive all’inizio del nostro sketch:
- Una variabile per memorizzare il momento in cui l’ultimo evento (o azione) è iniziato. Potremmo chiamarla: millisPrecedente.
- Una variabile per definire la durata del periodo di temporizzazione, cioè quanto tempo vogliamo aspettare prima di eseguire di nuovo l’azione. La chiameremo: intervallo.
La chiave per utilizzare millis() per la temporizzazione non bloccante sta nel confrontare continuamente il tempo corrente, millisCorrente con il tempo dall’ultimo evento, millisPrecedente con l’intervallo di tempo desiderato, intervallo. Se questa differenza è maggiore o uguale all’intervallo, allora è il momento di eseguire nuovamente l’azione.
Utilizzando quanto detto il codice che segue permette di far lampeggiare ogni secondo il LED connesso al pin 13 utilizzando millis().
// Prof. Maffucci Michele // Uso della funzione millis() // Dichiarazione delle variabili globali unsigned long startMillis; // memorizza l'inizio del periodo di conteggio unsigned long millisCorrente; // valore attuale del millis const unsigned long periodo = 1000; // durata del periodo in millisecondi, cioè 1 secondo // Pin a cui è collegato il LED const int ledPin = 13; void setup() { // Inizializza il pin del LED come OUTPUT pinMode(ledPin, OUTPUT); // Memorizza il momento in cui inizia il programma startMillis = millis(); } void loop() { // Aggiorna il valore di millisCorrente ad ogni ciclo millisCorrente = millis(); // Controlla se è passato il periodo di tempo if (millisCorrente - startMillis >= periodo) { // Cambiare lo stato del LED statoLED(); // Reimposta startMillis per il prossimo periodo startMillis = millisCorrente; } // in questo punto puoi aggiungere atre attività che devono essere svolte } void statoLED() { // Controlla lo stato attuale del LED e lo cambia if (digitalRead(ledPin) == HIGH) { digitalWrite(ledPin, LOW); // spegni il LED } else { digitalWrite(ledPin, HIGH); // accendi il LED } }
Utilizzando millis(), il programma memorizza il momento in cui l’ultimo cambiamento di stato (accensione/spegnimento) del LED è avvenuto. Ogni volta che il loop() viene eseguito, il programma controlla se è trascorso un secondo dall’ultima azione, se un secondo è passato, cambia lo stato del LED e aggiorna startMillis
con il tempo corrente per iniziare un nuovo periodo.
Esempio 2 – blink senza delay versione 2
Vediamo un altro modo più conciso per riscrivere il programma dell’esempio 1, anche in questo caso utilizziamo il pin 13 a cui colleghiamo un LED.
// Prof. Maffucci Michele // Uso della funzione millis() unsigned long startMillis; // memorizza l'inizio del periodo di conteggio unsigned long millisCorrente; // valore attuale del millis const unsigned long periodo = 1000; // valore in millisecondi, ovvero 1 secondo const byte ledPin = 13; // utilizzo del LED sulla sched Arduino void setup() { pinMode(ledPin, OUTPUT); // imposta il pin del LED come OUTPUT startMillis = millis(); // tempo iniziale } void loop() { millisCorrente = millis(); // ottiene il "tempo" attuale if (millisCorrente - startMillis >= periodo) { // verifica se il periodo è trascorso digitalWrite(ledPin, !digitalRead(ledPin)); // cambia lo stato del LED startMillis = millisCorrente; // salva l'inizio del periodo corrente } }
Esempio 3 – Controllo di Due LED con periodi diversi
Faremo lampeggiare con periodi diversi due LED connessi al pin 11 e 12.
// Prof. Maffucci Michele // Uso della funzione millis() // Lampeggio di due LED con periodi diversi // Dichiarazione delle variabili globali unsigned long millisCorrente; // valore attuale del millis const byte ledPin1 = 11; // pin a cui è connesso il LED 1 const byte ledPin2 = 12; // pin a cui è connesso il LED 2 unsigned long millisPrecedente1 = 0; // valore del millis precedente per il LED 1 unsigned long millisPrecedente2 = 0; // valore del millis precedente per il LED 2 const long intervalo1 = 1000; // 1 secondo - periodo LED 1 const long intervalo2 = 250; // 250 millisecondi - periodo LED 2 void setup() { pinMode(ledPin1, OUTPUT); // Imposta il pin del LED 1 come OUTPUT pinMode(ledPin2, OUTPUT); // Imposta il pin del LED 2 come OUTPUT } void loop() { millisCorrente = millis(); // Assegna tempo attuale a millisCorrente if (millisCorrente - millisPrecedente1 >= intervalo1) { // verifica se l'intervallo 1 è trascorso millisPrecedente1 = millisCorrente; // salva l'inizio del periodo corrente per il LED 1 digitalWrite(ledPin1, !digitalRead(ledPin1)); // cambia lo stato del LED 1 } if (millisCorrente - millisPrecedente2 >= intervalo2) { // verifica se l'intervallo 2 è trascorso millisPrecedente2 = millisCorrente; // salva l'inizio del periodo corrente per il LED 2 digitalWrite(ledPin2, !digitalRead(ledPin2)); // cambia lo stato del LED 2 } }
Esempio 4 – Fading in di un LED senza l’utilizzo del delay
Utilizzeremo per questo esempio un LED connesso al pin 9.
// Prof. Maffucci Michele // Uso della funzione millis() per controllare il feading in di un LED int ledPin = 9; // pin PWM per il controllo della luminosità unsigned long millisPrecedente = 0; // valore del millis precedente per il LED const long intervallo = 20; // intervallo di aggiornamento della luminosità int luminosita = 0; // intensità luminosa corrente del LED int livelloLuminosita = 5; // incremento minimo della luminosità void setup() { pinMode(ledPin, OUTPUT); // imposta il pin del LED come OUTPUT } void loop() { unsigned long millisCorrente = millis(); // assegna a millisCorrente il valore attuale // Ogni 20 millisecondi l'intensità luminosa del LED aumenta di 5 unità if(millisCorrente - millisPrecedente >= intervallo) { // verifica se l'intervallo è trascorso millisPrecedente = millisCorrente; // salva l'inizio del periodo corrente per il LED analogWrite(ledPin, luminosita); // imposta la luminosità del LED luminosita = luminosita + livelloLuminosita; // incremento della luminosità } }
Esempio 5 – Fading in e fading out di un LED senza l’utilizzo del delay
Utilizzeremo per questo esempio un LED connesso al pin 9.
// Prof. Maffucci Michele // Uso della funzione millis() per controllare il feading in e out di un LED int ledPin = 9; // pin PWM per il controllo della luminosità unsigned long millisPrecedente = 0; // alore del millis precedente per il LED const long intervallo = 20; // intervallo di aggiornamento della luminosità int luminosita = 0; // intensità luminosa corrente del LED int livelloLuminosita = 5; // incremento minimo della luminosità void setup() { pinMode(ledPin, OUTPUT); // imposta il pin del LED come OUTPUT } void loop() { unsigned long millisCorrente = millis(); // assegna a millisCorrente il valore attuale // Ogni 20 millisecondi l'intensità luminosa del LED aumenta di 5 unità if(millisCorrente - millisPrecedente >= intervallo) { // Verifica se l'intervallo è trascorso millisPrecedente = millisCorrente; // salva l'inizio del periodo corrente per il LED analogWrite(ledPin, luminosita); // imposta la luminosità del LED luminosita = luminosita + livelloLuminosita; // incremento della luminosità // Cambia direzione del fade quando raggiunge i limiti di luminosità if (luminosita <= 0 || luminosita >= 255) { livelloLuminosita = -livelloLuminosita; // Inversione di segno per ridurre l'intensità luminosa } } }
Esempio 6 – Utilizzo della funzione millis() per temporizzare le letture di un sensore DHT11 e l’accensione di un LED
Realizziamo ora uno sketch che utilizza millis() per eseguire letture a intervalli regolari e l’accensione di un LED per alcuni secondi durante la lettura in modo che una persona abbia percezione dell’avvenuta rilevazione. Il valore della temperatura e dell’umidità verranno stampati sulla Serial Monitor. Per una spiegazione approfondita sull’uso del sensore DHT 11 si faccia riferimento alla mia spiegazione: “Arduino: utilizzo del sensore di umidità e temperatura DHT11” di seguito utilizzerò parte degli sketch già spiegati e i medesimi schemi di collegamento.
Componenti
- DHT 11 su breakout board
- LED
- Resistore da 220 Ohm da inserire in serie al LED
Utilizzeremo il pin 2 di Arduino per collegare il pin data del sensore ed il pin 13 a cui collegheremo il LED.
// Prof. Maffucci Michele // Utilizzo della funzione millis() per temporizzare // le letture di un sensore DHT11 e l'accensione di un LED #include "DHT.h" // Definizione dei pin e costanti #define DHTTYPE DHT11 // tipo di sensore DHT const byte pinDHT = 2; // pin a cui è collegato il sensore DHT11 const byte pinLED = 13; // pin a cui è collegato il LED const unsigned long intervalloLettura = 10000; // intervallo tra le letture (10 secondi) const unsigned long durataLED = 3000; // durata accensione LED (3 secondi) // Variabili globali unsigned long tempoPrecedenteLettura = 0; // tempo dell'ultima lettura unsigned long tempoInizioLED = 0; // tempo di inizio accensione LED bool ledAcceso = false; // stato del LED DHT dht(pinDHT, DHTTYPE); void setup() { // Inizializzazione Serial.begin(9600); dht.begin(); pinMode(pinLED, OUTPUT); digitalWrite(pinLED, LOW); } void loop() { unsigned long tempoCorrente = millis(); // Controlliamo se è ora di fare una nuova lettura if (tempoCorrente - tempoPrecedenteLettura >= intervalloLettura) { // Aggiorna il tempo dell'ultima lettura tempoPrecedenteLettura = tempoCorrente; // Esegue la lettura del sensore float umidita = dht.readHumidity(); float temperatura = dht.readTemperature(); // Controlla eventuali errori di lettura if (isnan(umidita) || isnan(temperatura)) { Serial.println("Errore di lettura dal sensore DHT11"); } else { // Stampa i valori sulla Serial Monitor Serial.print("Umidità: "); Serial.print(umidita); Serial.print("% Temperatura: "); Serial.print(temperatura); Serial.println("°C"); // Accende il LED per indicare la lettura digitalWrite(pinLED, HIGH); ledAcceso = true; tempoInizioLED = tempoCorrente; } } // Controllo se il LED deve essere spento if (ledAcceso && (tempoCorrente - tempoInizioLED >= durataLED)) { digitalWrite(pinLED, LOW); ledAcceso = false; } }
Se avete necessità di ulteriori dettagli chiedete.
Buon Coding a tutti 🙂