Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

9
voti

Una semplice libreria Timer per Arduino

Questo articolo nasce per fornire un minimo di documentazione alla libreria che ho allegato in questo post, dove i file erano stati buttati lì velocemente, corredati solo da un breve esempio relativo all'argomento del thread. Ora, non so se c'è un errore, ma dalla mia dashboard risulta addirittura che sono stati superati gli 80 downloads (possibile?) per cui ho pensato potesse essere utile qualche info in più.

Indice

1. Premessa

La necessità di inserire ritardi nelle valutazioni e nelle sequenze di comandi è vecchia come l'automazione stessa ed è uno dei primi "problemi" con cui ci si scontra quando si inizia a programmare, anche con Arduino.
La libreria in questione può essere considerata per un utilizzo generale, dove non serva una precisione al microsecondo e con ritardi impostabili fino a 49 giorni ( \scriptstyle 2^{32}-1\ ms ). Il suo utilizzo non blocca l'esecuzione del micro-controllore: non si fa uso dell'istruzione delay().

2. Principali tipologie di temporizzatori

Il codice è ispirato al funzionamento dei temporizzatori industriali: la libreria può essere configurata per emularne una delle principali tipologie, facciamo un breve ripasso.

2.1 Temporizzatore ritardato all'eccitazione (o all'inserzione)

E' considerata la tipologia fondamentale, il funzionamento di questo temporizzatore, come diceva il mio professore di impianti quasi trent'anni fa, si capisce meglio guardandone il simbolo: sul contatto si nota un piccolo "paracadute", che ne ritarda la chiusura e non la riapertura (nel caso di un contatto normalmente aperto):

Quando il contatto S alimenta la bobina, "abilita" il temporizzatore e, dopo un certo ritardo D, il contatto commuta accendendo in questo caso la lampada H. Se S si apre prima del tempo impostato, H non si accende.

2.2 Temporizzatore ritardato alla diseccitazione (o alla disinserzione)

Con questi temporizzatori la commutazione del contatto avviene in concomitanza dell'alimentazione della bobina, mentre viene ritardato il ritorno del contatto nella posizione di riposo. In genere viene rappresentato con una delle due schematizzazioni seguenti:

La lampada H quindi si accende quando viene chiuso il contatto S e, quando quest'ultimo si apre, rimane accesa per il tempo D spegnendosi in ritardo.

Lo stesso mio professore di impianti diceva che questo tipo di temporizzatore non è fondamentale e può essere ottenuto anche con un ritardo all'eccitazione ed un relè ausiliario:

Appartiene a questa tipologia anche il temporizzatore "luce scale", uno dei relè più usati nell'impiantistica civile, che viene armato con un impulso anche se in genere è considerato una categoria a sé, visto che i moderni moduli elettronici lo hanno arricchito di svariate funzioni ("pulizia scale", "reset", ..).

2.3 Temporizzatore ciclico ad intermittenza

Da bambino rimanevo affascinato quando si inserivano nelle prese quei vecchi dispositivi per rendere le luci dell'albero di Natale intermittenti, con una caratteristica di accensione/spegnimento assolutamente non regolare che al giorno d'oggi fa sorridere, o quando mio nonno, appassionato lettore di Nuova Elettronica, faceva lampeggiare dei led con un NE555, non per niente una delle prime esercitazioni con i micro-controllori consiste proprio in questo.

L'esigenza di far lampeggiare un led o una spia luminosa è legata ad attirare l'attenzione dell'operatore, in genere maggiore è la frequenza di lampeggio e più "urgente" è la segnalazione (anomalia, errore, guasto, allarme).
Per le segnalazioni sui PLC è frequente l'uso del "byte di cadenza": un byte che viene incrementato ad ogni ciclo, a seconda del bit utilizzato si creano frequenze di lampeggio diverse, con tempistiche non perfette in quanto legate al tempo ciclo, ma trascurabili per questi scopi.

Per quanto riguarda i relè ad intermittenza non esiste un vero e proprio standard, ad esempio alcuni partono con l'impulso di off, altri con quello on, anche come simbolo non ne ho trovato uno ufficiale, per cui lo ho rappresentato così (con tempi di accensione e spegnimento uguali):

Anche in questo caso questo tipo di comando può essere ottenuto con dei temporizzatori ritardati all'eccitazione:

dove con KT2 si regola il tempo di accensione e con KT1 il tempo di spegnimento.

2.4 Altre tipologie

L'avvento dei temporizzatori elettronici, spesso addirittura multifunzione, ed anche l'evoluzione nei vari linguaggi PLC, ha portato la nascita di nuove tipologie (temporizzatore ad impulso, ad impulso prolungato, con memoria, con comando di reset, ed altro ancora), ma sono funzioni ottenibili anche con le tipologie viste in precedenza.

3. La libreria

Come anticipato, la libreria emula le tre principali tipologie di temporizzatori, al suo interno utilizza la funzione millis() legata al Timer0, quindi non interrompe il flusso di esecuzione, ci sono certamente approcci che garantiscono maggiore precisione, questa è da considerare una libreria "general purpose".

3.1 Installazione ed inclusione

Se usate un IDE di Arduino recente, potete installare la libreria direttamente dal menu Sketch:

Figura 6 - Installazione

Figura 6 - Installazione

altrimenti basta scomprimere i due file, individuare la vostra cartella libraries, creare una sotto-cartella Timer ed inserire i due file.
Non uso molto l'IDE di Arduino, se non vedete la libreria nell'elenco dovrebbe essere sufficiente riavviare il programma.

Per utilizzare la libreria nel vostro sketch è necessario usare la direttiva include, inserendo questa riga all'inizio:

#include <Timer.h>

operazione che viene fatta anche automaticamente dal menu visto in figura 6, selezionando la libreria nella lista.

3.2 Utilizzo

La libreria gestisce due proprietà fondamentali per ogni timer istanziato, osservando i temporizzatori visti al capitolo 2 ci sono queste analogie:

  • abilitazione del timer: rappresenta la condizione di alimentazione della bobina o della chiusura del contatto S
  • stato del timer: rappresenta la condizione di contatti commutati o dell'accensione della lampada H


3.2.1 Costruttore

Per ogni timer che si desidera usare, va creata un'istanza della libreria, il costruttore prevede i seguenti parametri:

Timer ( type , delay , [enablePtr] , [timerChgFct] )


type: è un enumerativo che dichiara la tipologia di timer, i valori possibili sono:
  • OnDelay: all'eccitazione, il funzionamento è quello descritto dal diagramma di figura 1
  • OffDelay: alla diseccitazione, il funzionamento è quello descritto dal diagramma di figura 2
  • Cycle: ad intermittenza, il funzionamento è quello descritto dal diagramma di figura 4, tranne per il fatto che si inizia con l'impulso di ON, ho preferito così per attivare subito la segnalazione nel caso di ritardi lunghi
delay: valore del ritardo in millisecondi, il tipo è unsigned long
enablePtr: è un parametro opzionale, rappresenta il puntatore ad una variabile di tipo bool a cui agganciare l'abilitazione del timer
timerChgFct: è un parametro opzionale, rappresenta il puntatore alla funzione da richiamare all'evento di cambio stato del timer, la funzione deve avere una firma di questo tipo: void timerChanged(bool state), quindi senza valori di ritorno, il parametro passato è lo stato del timer


3.2.2 Metodi pubblici

I metodi pubblici previsti sono:

  • bool update(): aggiornamento istanza del timer, da richiamare nella funzione loop(), ritorna true al verificarsi di un cambio di stato
  • void restart(): fa ripartire il conteggio del timer, va successivamente richiamata la funzione update()
  • unsigned long remainingTime(): restituisce il tempo rimanente in millisecondi, con conteggio non è in corso ritorna 0
  • unsigned long elapsedTime(): restituisce il tempo trascorso in millisecondi, con conteggio non è in corso ritorna 0


3.2.3 Accesso ai membri (proprietà)

I metodi per leggere ed impostare le proprietà sono:

  • bool getEnable(): lettura abilitazione del timer
  • void setEnable(bool): impostazione abilitazione del timer, da utilizzare se nella dichiarazione non si è specificato il parametro enablePtr
  • bool getState(): lettura stato attuale del timer
  • unsigned long getDelay(): lettura del ritardo impostato in millisecondi
  • void setDelay(unsigned long): impostazione del ritardo in millisecondi, da utilizzare se si desidera variare dinamicamente il ritardo impostato


3.3 Esempi

Proseguo con qualche esempio, per tentare di facilitare la comprensione.

3.3.1 Esempio di timer all'eccitazione

Questo esempio accende il led integrato (H) connesso al pin 13, con un ritardo di 3 secondi da quando viene portato a massa il pin 2 tramite il contatto S, il diagramma di funzionamento è quello visto in figura 1:

#include <Timer.h>

//----------------------- Dichiarazioni -----------------------
void timerChg(bool state);                        //Metodo gestione evento cambio stato timer
bool enbLed = false;                              //Abilitazione timer
Timer timLed(OnDelay, 3000UL, &enbLed, timerChg); //Istanza timer con ritardo all'eccitazione
const uint8_t INPUT_PIN = 2;                      //Pin di connessione dell'ingresso
//-------------------------------------------------------------

void setup()
{
   pinMode(INPUT_PIN, INPUT_PULLUP);              //Impostazione pin ingresso
   pinMode(LED_BUILTIN, OUTPUT);                  //Impostazione pin uscita
}
void loop()
{
   enbLed = !digitalRead(INPUT_PIN);              //Lettura stato ingresso (attivo basso)
   timLed.update();                               //Aggiornamento istanza timer
}
void timerChg(bool state)                         //Evento di cambio stato del timer
{
   digitalWrite(LED_BUILTIN, state);              //Comando del led
}

L'esempio che segue è equivalente al precedente, nella dichiarazione non si è specificato il puntatore all'abilitazione ed il funtore per gestire l'evento, quindi si sono dovuti utilizzare i metodi per accedere ai membri interni della classe:

#include <Timer.h>

//----------------------- Dichiarazioni -----------------------
Timer timLed(OnDelay, 3000UL);                    //Istanza timer con ritardo all'eccitazione
const uint8_t INPUT_PIN = 2;                      //Pin di connessione dell'ingresso
//-------------------------------------------------------------

void setup()
{
   pinMode(INPUT_PIN, INPUT_PULLUP);              //Impostazione pin ingresso
   pinMode(LED_BUILTIN, OUTPUT);                  //Impostazione pin uscita
}
void loop()
{
   timLed.setEnable(!digitalRead(INPUT_PIN));     //Abilitazione timer con stato ingresso
   if (timLed.update())                           //Aggiornamento istanza timer
      digitalWrite(LED_BUILTIN,timLed.getState());//Comando del led
}

Da un esempio così semplice non si colgono bene le differenze dell'una o dell'altra metodologia, la scelta dipende dal giro che fa l'abilitazione del timer, dalle azioni da eseguire al cambio stato, dalla complessità di codice già presente nella funzione loop().

3.3.2 Esempio di timer alla diseccitazione

In genere i timer alla diseccitazione li uso per creare interblocchi software con una pausa garantita sulle inversioni di marcia, quindi abilitazione del timer legata allo stato dell'uscita, ad esempio di marcia avanti, e stato negato del timer in serie alle abilitazioni dell'uscita di marcia indietro e viceversa.

Qui ho riportato un esempio più generale, ovvero un temporizzatore luce scale con ritardo impostato a 5 minuti, al pin 2 è connesso il pulsante di comando S, al pin 12 il led H che emula l'illuminazione ed al pin 5 l'indicatore luminoso W del pulsante, acceso non solo a luce spenta per facilitare l'individuazione del pulsante stesso, ma anche quando il ritardo è quasi terminato (inferiore al 25%). Ad ogni pressione del pulsante il ritardo riparte, il diagramma di funzionamento è quello visto in figura 2:

#include <Timer.h>

//----------------------- Dichiarazioni -----------------------
Timer timLgt(OffDelay, 300000UL);                 //Istanza timer con ritardo alla diseccitazione
const uint8_t BTN_PIN = 2;                        //Pin di connessione del pulsante
const uint8_t WRN_PIN = 5;                        //Pin di connessione dell'indicatore
const uint8_t LGT_PIN = 12;                       //Pin di connessione dell'illuminazione
//-------------------------------------------------------------

void setup()
{
   pinMode(BTN_PIN, INPUT_PULLUP);                //Impostazione pin ingresso
   pinMode(WRN_PIN, OUTPUT);                      //Impostazione pin uscita
   pinMode(LGT_PIN, OUTPUT);                      //Impostazione pin uscita
}
void loop()
{
   timLgt.setEnable(!digitalRead(BTN_PIN));       //Abilitazione timer con pulsante
   if (timLgt.update())                           //Aggiornamento istanza timer
      digitalWrite(LGT_PIN, timLgt.getState());   //Comando luce scale

   //Comando dell'indicatore luminoso del pulsante
   digitalWrite(WRN_PIN, timLgt.remainingTime()<(timLgt.getDelay()/4));
}


3.3.3 Esempio di timer ad intermittenza

Anche qui porto un semplice esempio, simile se vogliamo allo sketch blink di Arduino, ovvero il lampeggio del led integrato (H) connesso al pin 13, con tempi di accensione e spegnimento di un secondo, portando a massa il pin 2 tramite il contatto S si "stoppa" l'intermittenza, riaprendo il contatto l'intermittenza riprende (riparte il conteggio):

#include <Timer.h>

//----------------------- Dichiarazioni -----------------------
void timerChg(bool state);                        //Metodo gestione evento cambio stato timer
Timer timLed(Cycle, 1000UL, NULL, timerChg);      //Istanza timer con ritardo all'eccitazione
const uint8_t INPUT_PIN = 2;                      //Pin di connessione dell'ingresso
//-------------------------------------------------------------

void setup()
{
   pinMode(INPUT_PIN, INPUT_PULLUP);              //Impostazione pin ingresso
   pinMode(LED_BUILTIN, OUTPUT);                  //Impostazione pin uscita
   timLed.setEnable(true);                        //Timer sempre abilitato
}
void loop()
{
   if (!digitalRead(INPUT_PIN))                   //Lettura stato pin 2 (attivo basso)
      timLed.restart();                           //Ripartenza conteggio

   timLed.update();                               //Aggiornamento istanza timer
}
void timerChg(bool state)                         //Evento di cambio stato del timer
{
   digitalWrite(LED_BUILTIN, state);              //Comando del led
}

Il diagramma di funzionamento è il seguente:

Il timer ciclico ovviamente può essere utilizzato anche per eseguire dei compiti ad intervalli regolari, è sufficiente richiamarne l'esecuzione all'interno dell'evento di cambio stato, ignorando il parametro state.

4. Conclusione

Il linguaggio C++ lo conosco ancora poco, per cui mi scuso sin d'ora se ho usato termini impropri o non corretti, segnalazioni e proposte di miglioria sono benvenute: ne farò tesoro.

5. Codice

Il codice illustrato in questo articolo è scaricabile qui.


3

Commenti e note

Inserisci un commento

di ,

Molto interessante!

Rispondi

di ,

Grazie a te, richiurci: buon divertimento (quando sarà)

Rispondi

di ,

Grazie, quando troverò il tempo per dedicarmi ad Arduino mi sarà utile ;-)

Rispondi

Inserisci un commento

Per inserire commenti è necessario iscriversi ad ElectroYou. Se sei già iscritto, effettua il login.