Cinque modi per far lampeggiare un diodo LED (lezione 12)

Introduzione

Far lampeggiare un diodo LED può essere considerato dalla stragrande maggioranza delle persone un’operazione semplice, ma ci sono diversi modi per implementare questa funzione, in quest’articolo descriviamo i cinque metodi più utilizzati.

Il primo approccio  che di solito un neofita ha  con un sistema embedded è sicuramente quello di far lampeggiare un diodo LED,  molti lo  paragonano al famoso “Hello World” della programmazione.

Ma questa semplice implementazione, può essere realizzata in molteplici modi e ognuno di essi può  essere un ottimo esercizio didattico che può insegnarci diverse caratteristiche di un microcontrollore, per esempio come gestire il timer.

Di seguito riporto l’elenco dei modi descritti in questo articolo per far lampeggiare un diodo LED:

  •  Delays
  •  Contatore
  •  Funzione Millis
  •  Watchdog Timer Interrupt
  •  Timer Interrupt

Delays

Il più semplice modo per far lampeggiare un diodo LED è quello di usare la funzione “delay”, ed è il primo esempio che normalmente  viene proposto a chi si sta avvicinando al mondo della programmazione dei sistemi a microcontrollore.

/****************************************************************
	Data  21-05-22
	scritto da Gennaro Vitiello
	codice per far lampeggiare diodo LED usando la funzione delay 
    Maggiori informazioni sul sito www.quattrodispositivi.it
******************************************************************/

#define LED 13 // Associamo un nome mnemonico al numero 13
void setup() {
  pinMode(LED, OUTPUT); //il pin dove e' connesso il dido LED impostato come uscita
}

void loop() {
  digitalWrite(LED, HIGH);    // Accendiamo il diodo LED con un valore di tensione 5V sull'uscita 
  delay(1000);               // aspettiamo per un secondo
  digitalWrite(LED, LOW);   // spegniamo il diodo LED con un valore di 0V sull'uscita 
  delay(1000);             // aspettiamo per un secondo
}

Di seguito descriviamo i dettagli del codice.

#define LED 13 // Associamo un nome mnemonico al numero 13

Definiamo il pin dove è connesso il diodo LED.

void setup() {
  pinMode(LED, OUTPUT); //il pin dove e' connesso il dido LED impostato come uscita
}

Impostiamo  il pin 13 come un output, poichè esso deve pilotare il diodo LED.

void loop() {
  digitalWrite(LED, HIGH);    // Accendiamo il diodo LED con un valore di tensione 5V sull'uscita 
  delay(1000);               // aspettiamo per un secondo
  digitalWrite(LED, LOW);   // spegniamo il diodo LED con un valore di 0V sull'uscita 
  delay(1000);             // aspettiamo per un secondo
}

Questa è la parte di codice che si ripete  a ciclo continuo.

Tra le parentesi tonde della funzione “delay” è espresso il tempo in millisecondi del ritardo introdotto(1000mS = 1S) .

Il diodo LED alterna lo stato di accensione e spegnimento con un periodo di 1 secondo.

Questo tipo di approccio ha un grosso vantaggio, l’implementazione è molto semplice, basta usare la funzione “delay()” aggiungere tra le parentesi il ritardo desiderato ed il gioco è fatto!

La funzione “delay()” è poco efficiente, poiché quando essa entra in esecuzione, il microcontrollore non esegue nessuna istruzione e attende che trascorra l’intervallo temporale impostato.

Quando il codice è molto complesso e ci sono processi che devono controllare in real-time delle periferiche, lasciare il microcontrollore in una sorta di stallo funzionale è inaccettabile!

Contatore

Questo metodo è altrettanto semplice come quello descritto nel paragrafo precedente, ma il microcontrollore non verra’ mai bloccato, analizziamo i dettagli.

/*
	Data  21-05-22
	scritto da Gennaro Vitiello
	codice per far lampeggiare diodo LED usando un contatore 
    Maggiori informazioni sul sito www.quattrodispositivi.it
*/

#define LED 13    // Associamo un nome mnemonico al numero 13
long contatore = 0; // definamo la variabile contatore e azzerata 

void setup() {
  pinMode(LED, OUTPUT); // il pin dove e' connesso il dido LED impostato come uscita
}

void loop() {

  contatore++;                 // incrementiamo di uno la variabile contattore
  if (contatore == 100000)     // effettuiamo un controllo sulla variabile contatore se e' inferiore a 100000
  {
    digitalWrite(LED, HIGH); // Accendiamo il diodo LED con un valore di tensione 5V sull'uscita 
  }
  else if (contatore == 200000) // effettuiamo un controllo sulla variabile contatore se e' inferiore a 200000
  {
    contatore = 0;             //azzeriamo la variabile contatore
    digitalWrite(LED, LOW);  // spegniamo il diodo LED con un valore di 0V sull'uscita 
  }
  
}

Di seguito descriviamo i dettagli del codice.

#define LED 13    // Associamo un nome mnemonico al numero 13
long contatore = 0; // definamo la variabile contatore e azzerata 

Associamo il numero 13 con la “define” LED.

La variabile del contatore è stata azzerata.

void setup() {
  pinMode(LED, OUTPUT); // il pin dove e' connesso il dido LED impostato come uscita
}

Il pin 13, definito LED nell’istruzione precedente, è associato come una uscita per pilotare il diodo LED.

void loop() {

  contatore++;                 // incrementiamo di uno la variabile contattore
  if (contatore == 100000)     // effettuiamo un controllo sulla variabile contatore se e' inferiore a 100000
  {
    digitalWrite(LED, HIGH); // Accendiamo il diodo LED con un valore di tensione 5V sull'uscita 
  }
  else if (contatore == 200000) // effettuiamo un controllo sulla variabile contatore se e' inferiore a 200000
  {
    contatore = 0;             //azzeriamo la variabile contatore
    digitalWrite(LED, LOW);  // spegniamo il diodo LED con un valore di 0V sull'uscita 
  }
  
}

Ad ogni esecuzione delle istruzioni contenute nel loop,  la variabile “contatore” sarà incrementata di una unità , al raggiungimento del valore 100.000, il diodo LED  verrà acceso, e quando il contatore raggiunge 200.000 il diodo LED verrà spento e azzerata la variabile “contatore” per avviare un nuovo ciclo di esecuzione

Tra un istruzione di controllo e l’altra può essere inserito del codice che il microcontrollore potra’ eseguire.

Il maggiore svantaggio di questo metodo è quello di essere poco preciso, poiché  la velocità con cui è incrementato il contatore  dipende dalla complessità del programma e da quante istruzioni condizionali sono presenti e quindi il conteggio durante l’esecuzione può essere variabile.

In modo empirico si può selezionare il valore che più si avvicina all’intervallo temporale desiderato, ma il problema della scarsa costanza è difficilmente risolvibile, soprattutto quando il codice è molto complesso. 

Funzione Millis

Anche questo esempio non blocca l’esecuzione delle istruzione del  microcontrollore, è più precisa del contatore.

Si usa la funzione “millis()” integrata nella libreria di Arduino.

Questa funzione usa il contatore interno del microcontrollore e misura il tempo trascorso dall’accensione della board, in ms.

Il contatore va in overflow dopo circa 50 giorni.

/****************************************************************
	Data  21-05-22
	scritto da Gennaro Vitiello
	codice per far lampeggiare diodo LED usando la funzione mills()
    Maggiori informazioni sul sito www.quattrodispositivi.it
******************************************************************/

#define LED 13                  // Associamo un nome mnemonico al numero 13
unsigned long millsPrecedente;  // variabile per il confronto con il dato riportato dalla funzione millis()
bool statoLED = false;       // Stato del diodo LED
void setup() {
  pinMode(LED, OUTPUT);         // il pin dove e' connesso il dido LED impostato come uscita
}

void loop() {
  if (millis() - millsPrecedente >= 1000)  // controlliamo se sono passati 1000 ms
  {
    if (statoLED == false) {              // facciamo un toggle della variabile di stato del diodo LED 
      statoLED = true;
    }
    else {
      statoLED = false;
    }
    digitalWrite(LED, statoLED);          // impostiamo l'uscita in funzione dello stato del diodo LED
    millsPrecedente = millis();            // impostiamo la variabile millsPrecedente con il valore corrente riportato dalla funzione millis()
  }
}
#define LED 13                  // Associamo un nome mnemonico al numero 13
unsigned long millsPrecedente;  // variabile per il confronto la funzione millis()
bool statoLED = false;       // Stato del diodo LED

In un determinato istante, nella variabile millisPrecedente verrà memorizzato il tempo riportato dalla funzione “millis()”, in questo modo abbiamo un riferimento con il quale monitorare il tempo trascorso da quando essa è stata aggiornata.

Il valore riportato dalla funzione “millis()” è incrementato con il trascorrere del tempo, esso verrà confrontato con il valore di riferimento memorizzato nella variabile millisPrecedente.

void loop() {
  if (millis() - millsPrecedente >= 1000)  // controlliamo se sono passati 1000 ms
  {
    if (statoLED == false) {              // facciamo un toggle della variabile di stato del diodo LED 
      statoLED = true;
    }
    else {
      statoLED = false;
    }
    digitalWrite(LED, statoLED);// impostiamo l'uscita in funzione dello stato del diodo LED
    millsPrecedente = millis();// impostiamo la variabile millsPrecedente la funzione millis()
  }
}

Nella prima istruzione,  facciamo la differenza tra il valore riportato dalla funzione “millis ()” e la variabile “millisPrecedente”, se il risultato è maggiore o uguale a 1000 significa che  è trascorso un tempo maggiore o uguale ad un secondo.

Se la condizione è verificata si esegue il toggle del valore della variabile booleana “statoLED” e si riaggiorna la variabile “millisPrecedente” e questo ciclo si ripete a loop.

Questo metodo è molto più accurato rispetto all’esempio del contatore ma esso richiede una maggiore quantità di memoria.

Watchdog Timer Interrupt

In questo esempio usiamo il  Watchdog Timer Interrupt, integrato all’interno di quasi tutti i microcontrollori, questa periferica hardware, non è generalmente usata per questo  tipo di applicazioni, ma per un uso didattico può essere molto istruttivo.

/****************************************************************
	Data  21-05-22
	scritto da Gennaro Vitiello
	codice per far lampeggiare diodo LED usando il watchdog
    Maggiori informazioni sul sito www.quattrodispositivi.it
******************************************************************/


#include <avr/wdt.h> //includiamo la libreria wdt AVR Watchdog 
#define LED 13  //Associamo un nome mnemonico al numero 13
boolean ledState = false; //Stato del diodo LED

void setup() {
  pinMode(LED, OUTPUT); //il pin dove e' connesso il dido LED impostato come uscita
  cli();               //Disabilitiamo tutti gli interrupt
  WDTCSR = (1 << WDCE) | (1 << WDE);  //abilitiamo  Watchdog interrupt

  /********************************************************************************************/
  //

  //WDTCSR = (1 << WDIE);                                                                   //16ms
  //WDTCSR = (1 << WDIE) | (1 << WDP0);                                          //32ms
  //WDTCSR = (1 << WDIE) | (1 << WDP1);                                          //64ms
  //WDTCSR = (1 << WDIE) | (1 << WDP1) | (1 << WDP0);                  //0.125s
  //WDTCSR = (1 << WDIE) | (1 << WDP2);                                          //0.25s
  //WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP0);                  //0.5s
  WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP1);                     //1s
  //WDTCSR = (1<<WDIE)| (1<<WDP2) | (1<<WDP1) | (1 << WDP0);  // 2s
  //WDTCSR = (1<<WDIE)| (1<<WDP3);                                               //4s
  //WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0);                  //8s

  /*******************************************************************************************/

  sei();                                                      //abilitiamo gli interrupts
}

void loop() {
 
}


ISR(WDT_vect) {                                    // funzione per la gestione dei watchdog interrupt
  if (ledState)                                    //
  {
    ledState = false;
  }
  else                                            
  {
    ledState = true;
  }
  digitalWrite(LED, ledState); //impostiamo l'uscita in funzione dello stato del diodo LED    
}
#include <avr/wdt.h> //includiamo la libreria wdt AVR Watchdog 

Aggiungiamo la libreria per gestire il Watchdog Timer, verranno associati gli indirizzi fisici dei registri a dei nomi mnemonici, riportati sul datasheet del microcontrollore.

cli();               //Disabilitiamo tutti gli interrupt
WDTCSR = (1 << WDCE) | (1 << WDE);  //abilitiamo  Watchdog interrupt

Cli() disabilita il Watchdog Timer, questa istruzione può anche essere omessa, ma è una buona regola usarla prima della configurazione del Watchdog Timer.

L’istruzione (wdtcsr = (1 << wdce) | (1 << wde),  imposta i bits WDCE e WDE ad “1” nel registro WDTCSR per abilitare il timer del watchdog in modalità di interrupt.

  /********************************************************************************************/
  //

  //WDTCSR = (1 << WDIE);                                                                   //16ms
  //WDTCSR = (1 << WDIE) | (1 << WDP0);                                          //32ms
  //WDTCSR = (1 << WDIE) | (1 << WDP1);                                          //64ms
  //WDTCSR = (1 << WDIE) | (1 << WDP1) | (1 << WDP0);                  //0.125s
  //WDTCSR = (1 << WDIE) | (1 << WDP2);                                          //0.25s
  //WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP0);                  //0.5s
  WDTCSR = (1 << WDIE) | (1 << WDP2) | (1 << WDP1);                     //1s
  //WDTCSR = (1<<WDIE)| (1<<WDP2) | (1<<WDP1) | (1 << WDP0);  // 2s
  //WDTCSR = (1<<WDIE)| (1<<WDP3);                                               //4s
  //WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0);                  //8s

  /*******************************************************************************************/

  sei();                                                      //abilitiamo gli interrupts

La prossima istruzione è quella di impostare  l’intervallo di tempo di attivazione dell’interrupt, in questo caso è stato selezionato 1 secondo, come da indicazione riportate nel datasheet del microcontrollore. 

Ed in fine è stato riattivato il watchdog.

Figura 2
ISR(WDT_vect) {                                    // funzione per la gestione dei watchdog interrupt
  if (ledState)                                    //
  {
    ledState = false;
  }
  else                                            
  {
    ledState = true;
  }
  digitalWrite(LED, ledState);                    //impostiamo l'uscita in funzione dello stato del diodo LED    
}

La funzione ISR (WDT_vect) verrà chiamata ogni volta che il timer del watchdog fa scattare l’interrupt.

Anche questo metodo non blocca  l’esecuzione del codice.

Timer Interrupt

Questa è  l’ultima soluzione che analizzeremo, Arduino Uno,  integra tre timer   timer0, timer1 e timer2, il timer0 è usato dalla funzione millis(), già analizzato  in uno dei paragrafi precedenti.

In questo esempio usiamo il timer1.

/****************************************************************
	Data  21-05-22
	scritto da Gennaro Vitiello
	codice per far lampeggiare diodo LED usando il Timer
    Maggiori informazioni sul sito www.quattrodispositivi.it
******************************************************************/

#define LED 13	// Associamo un nome mnemonico al numero 13
boolean statoLED = false;          //Stato del diodo LED

void setup() {
  pinMode(LED, OUTPUT);				//il pin dove e' connesso il dido LED impostato come uscita
  TCCR1A = 0;                      //Resettiamo il  Timer1 control del Registro A

  bitClear(TCCR1B, WGM13);         //impostiamo il contatore con il modo CTC 
  bitSet(TCCR1B, WGM12);


  bitSet(TCCR1B, CS12);            //impostiamo il prescale a 1024
  bitClear(TCCR1B, CS11);
  bitSet(TCCR1B, CS10);

  //Resettiamo il Timer1
  TCNT1 = 0;

  //impostiamo il valore di comparazione
  //massimo valore  (16bit Timer) = 65535
  /*******************************************
    per calcolare il valore di comparazione
    OCR1A = (time(s) * clock Freq.)/prescaler
    OCR1A = (1*16*10^6)/1024
  ********************************************/
  //OCR1A = 3906;     //per 0.25sec
  //OCR1A = 7812;    //per 0.5sec
  OCR1A = 15625;   //per 1sec
  //OCR1A = 31250;   //per 2sec

  bitSet(TIMSK1, OCIE1A); // abilitiamo l'interrupt di comparazione Timer1 
  sei();                  // abilitiamo global interrupts
}

void loop() {
  
}

ISR(TIMER1_COMPA_vect)				// funzione per la gestione degli interrupt
{
  if (statoLED)                
  {
    statoLED = false;
  }
  else                         
  {
    statoLED = true;
  }
  digitalWrite(LED, statoLED); //impostiamo l'uscita in funzione dello stato del diodo LED
}
bitClear(TCCR1B, WGM13);         //impostaimo il contatore con il modo CTC 
  bitSet(TCCR1B, WGM12);

Settiamo  i registri WGM12 e WGM13, per attivare la modalità CTC, come indicato nel datasheet del  microcontrollore  ATmega328P.

Figura 3

La modalità CTC (Clear Timer on Compare Match) resetta il contatore quando ha raggiunto il valore inserito nel registro OCR1A e il suo comportamento e indicato nella figura 4

Figura 4
 bitSet(TCCR1B, CS12);            //impostiamo il prescale a 1024
  bitClear(TCCR1B, CS11);
  bitSet(TCCR1B, CS10);

Impostiamo il valore del prescaler come indicato nel datasheet:

Figura 5

Abbiamo selezionato la configurazione evidenziata in rosso, quindi nel nostro caso abbiamo un prescaler di 1024, cioè il contatore usa un clock  con un  periodo di 1024 volte più grande del clock di sistema, su Arduino Uno è di 16 Mhz.

Per determinare il valore da caricare nel registro e  impostare un interrupt timer ogni secondo, dobbiamo usare la seguente equazione:

OCR1A = (1*16*10^6)/1024 = 15625

//impostiamo il valore di comparazione
  //massimo valore  (16bit Timer) = 65535
  /*******************************************
    per calcolare il valore di comparazione
    OCR1A = (time(s) * clock Freq.)/prescaler
    OCR1A = (1*16*10^6)/1024
  ********************************************/
  //OCR1A = 3906;     //per 0.25sec
  //OCR1A = 7812;    //per 0.5sec
  OCR1A = 15625;   //per 1sec
  //OCR1A = 31250;   //per 2sec

Infatti nel nostro codice abbiamo caricato il valore di 15625.

ISR(TIMER1_COMPA_vect)				// funzione per la gestione degli interrupt
{
  if (statoLED)                
  {
    statoLED = false;
  }
  else                         
  {
    statoLED = true;
  }
  digitalWrite(LED, statoLED); //impostiamo l'uscita in funzione dello stato del diodo LED
}

Questa è la parte di codice che si esegue a loop. Aggiungiamo l’ISR con la funzione IMER1_COMPA_vect,  scatta l’interrupt ogni volta che il timer  raggiunge il valore impostato nel registro OCR1A.

Spero che questo articolo l’abbiate trovato interessante e se aveste dei quesiti non esitate a scrivere nei commenti di seguito.

Lascia una risposta

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

Ti sei iscritto alla newsletter

There was an error while trying to send your request. Please try again.

Quattrodispositivi utilizzerà le informazioni fornite in questo modulo per restare in contatto con te e fornire aggiornamenti del sito