
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().
5 | unsigned long startMillis; |
6 | unsigned long millisCorrente; |
7 | const unsigned long periodo = 1000; |
14 | pinMode(ledPin, OUTPUT); |
16 | startMillis = millis(); |
21 | millisCorrente = millis(); |
24 | if (millisCorrente - startMillis >= periodo) { |
28 | startMillis = millisCorrente; |
36 | if (digitalRead(ledPin) == HIGH) { |
37 | digitalWrite(ledPin, LOW); |
39 | digitalWrite(ledPin, HIGH); |
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.
4 | unsigned long startMillis; |
5 | unsigned long millisCorrente; |
6 | const unsigned long periodo = 1000; |
10 | pinMode(ledPin, OUTPUT); |
11 | startMillis = millis(); |
15 | millisCorrente = millis(); |
16 | if (millisCorrente - startMillis >= periodo) { |
17 | digitalWrite(ledPin, !digitalRead(ledPin)); |
18 | startMillis = millisCorrente; |
Esempio 3 – Controllo di Due LED con periodi diversi
Faremo lampeggiare con periodi diversi due LED connessi al pin 11 e 12.
6 | unsigned long millisCorrente; |
7 | const byte ledPin1 = 11; |
8 | const byte ledPin2 = 12; |
9 | unsigned long millisPrecedente1 = 0; |
10 | unsigned long millisPrecedente2 = 0; |
11 | const long intervalo1 = 1000; |
12 | const long intervalo2 = 250; |
15 | pinMode(ledPin1, OUTPUT); |
16 | pinMode(ledPin2, OUTPUT); |
20 | millisCorrente = millis(); |
22 | if (millisCorrente - millisPrecedente1 >= intervalo1) { |
23 | millisPrecedente1 = millisCorrente; |
24 | digitalWrite(ledPin1, !digitalRead(ledPin1)); |
27 | if (millisCorrente - millisPrecedente2 >= intervalo2) { |
28 | millisPrecedente2 = millisCorrente; |
29 | digitalWrite(ledPin2, !digitalRead(ledPin2)); |
Esempio 4 – Fading in di un LED senza l’utilizzo del delay
Utilizzeremo per questo esempio un LED connesso al pin 9.
5 | unsigned long millisPrecedente = 0; |
6 | const long intervallo = 20; |
8 | int livelloLuminosita = 5; |
11 | pinMode(ledPin, OUTPUT); |
15 | unsigned long millisCorrente = millis(); |
18 | if (millisCorrente - millisPrecedente >= intervallo) { |
19 | millisPrecedente = millisCorrente; |
20 | analogWrite(ledPin, luminosita); |
21 | luminosita = luminosita + livelloLuminosita; |
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.
5 | unsigned long millisPrecedente = 0; |
6 | const long intervallo = 20; |
8 | int livelloLuminosita = 5; |
11 | pinMode(ledPin, OUTPUT); |
15 | unsigned long millisCorrente = millis(); |
18 | if (millisCorrente - millisPrecedente >= intervallo) { |
19 | millisPrecedente = millisCorrente; |
20 | analogWrite(ledPin, luminosita); |
21 | luminosita = luminosita + livelloLuminosita; |
24 | if (luminosita <= 0 || luminosita >= 255) { |
25 | livelloLuminosita = -livelloLuminosita; |
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.
8 | #define DHTTYPE DHT11 // tipo di sensore DHT |
10 | const byte pinLED = 13; |
12 | const unsigned long intervalloLettura = 10000; |
13 | const unsigned long durataLED = 3000; |
16 | unsigned long tempoPrecedenteLettura = 0; |
17 | unsigned long tempoInizioLED = 0; |
18 | bool ledAcceso = false ; |
20 | DHT dht(pinDHT, DHTTYPE); |
26 | pinMode(pinLED, OUTPUT); |
27 | digitalWrite(pinLED, LOW); |
31 | unsigned long tempoCorrente = millis(); |
34 | if (tempoCorrente - tempoPrecedenteLettura >= intervalloLettura) { |
36 | tempoPrecedenteLettura = tempoCorrente; |
39 | float umidita = dht.readHumidity(); |
40 | float temperatura = dht.readTemperature(); |
43 | if (isnan(umidita) || isnan(temperatura)) { |
44 | Serial.println( "Errore di lettura dal sensore DHT11" ); |
47 | Serial.print( "Umidità: " ); |
49 | Serial.print( "% Temperatura: " ); |
50 | Serial.print(temperatura); |
54 | digitalWrite(pinLED, HIGH); |
56 | tempoInizioLED = tempoCorrente; |
61 | if (ledAcceso && (tempoCorrente - tempoInizioLED >= durataLED)) { |
62 | digitalWrite(pinLED, LOW); |
Se avete necessità di ulteriori dettagli chiedete.
Buon Coding a tutti 