Guida all’uso di millis() – Lezione 1

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:

  1. La variabile millisCorrente deve essere stata precedentemente dichiarata.
  2. 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:

  1. Il nome della variabile, così come accade per tutte le variabili dovrebbe essere autoesplicativa, quindi il suo nome deve dare informazioni: “a cosa serve”
  2. 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:

  1. Una variabile per memorizzare il momento in cui l’ultimo evento (o azione) è iniziato. Potremmo chiamarla: millisPrecedente.
  2. 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 🙂

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.