Abbonati ora!

Libreria per la gestione dei timers software

Realizzare una libreria per la gestione dei timers sotfware. L'articolo inizia con la descrizione della procedura di basic timer e dei timer software sincroni per poi descrivere i timers asincroni e le procedure di gestione.

In ogni applicazione embedded ci sono delle funzioni che devono essere eseguite periodicamente. Ad esempio: l’acquisizione dello stato di una tastiera, l’aggiornamento del display o la lettura di una periferica analogica. Di regola un timer hardware del microcontrollore viene dedicato alla sincronizzazione di questi eventi periodici. Normalmente questo timer è configurato per generare un interrupt con frequenza compresa tra 1ms e 10ms. Al vettore di interrupt si associa una procedura, denominata basic timer, che richiama tutte le funzioni da eseguire periodicamente. La frequenza del basic timer fornisce la base tempi per tutti i timers software. Questi timers si possono suddividere
in due categorie: sincroni e asincroni. Un timer sincrono è sempre attivo e viene aggiornato ad ogni interrupt di basic timer. I timer asincroni, al contrario, si possono attivare in un istante qualsiasi e restano attivi solo per un determinato periodo di tempo. Un esempio di timer asincrono è il controllo di timeout di ricezione nella comunicazione  seriale. Il timer si attiva alla ricezione di un carattere (evento asincrono) e al timeout ripristina lo stato iniziale del driver di comunicazione.

Il basic timer e i timers  software sincroni

La procedura di basic timer è associata all’interrupt periodico di un timer hardware del microcontrollore. La scelta del periodo di interrupt dipende dal tipo di applicazione e in genere varia tra 1ms e 10ms ma può assumere anche valori più elevati. Ad esempio in un sistema a basso consumo che esce dallo stato di power down solo per brevi istanti il periodo del basic timer può essere anche di centinaia di millisecondi. Anzi, in questo caso, si può sfruttare proprio questo interrupt per risvegliare periodicamente  il microcontrollore. La procedura di basic timer richiama tutte le funzioni che devono essere eseguite con una temporizzazione prestabilita come indicato nella prima versione della funzione BTimer del listato 1.

// Basic timer frequency = F
void BTimer()
{
      FuncA ();
      FuncB ();
      FuncC ();
      FuncD ();
      FuncE ();
}

// Basic timer frequency = 2F
unsigned char switch = 0;

void BTimer()
{
       if (switch)
              {
              FuncA ();
              FuncB ();
              }
       else
       {
              FuncC ();
              FuncD ();
              FuncE ();
        }
        switch ^= 1;
}
Listato 1

Queste funzioni dovrebbero essere brevi per ridurre al minimo la durata dell’interrupt. Talvolta però si devono eseguire molte funzioni oppure le funzioni sono complesse e l’interrupt di basic timer bloccherebbe periodicamente l’attività del microcontrollore per un tempo elevato. In questo caso si possono sfruttare  i registri di priorità degli interrupt presenti in alcuni microcontrollori. Si può assegnare al basic timer una priorità bassa permettendo la sovrapposizione di interrupt a priorità più elevata. Non tutti i  microcontrollori però permettono di configurare la priorità degli interrupt. In questo caso si può ricorrere ad un semplice accorgimento: Si raddoppia la frequenza del basic timer e di conseguenza la periodicità dell’interrupt e si suddividono le funzioni da eseguire in due gruppi. Tramite un bit di controllo, ad ogni interrupt si esegue un gruppo o l’altro di funzioni. Lo schema della procedura è riportato nella seconda versione della BTimer nel listato 1. La periodicità di esecuzione delle funzioni non è cambiata così come la durata complessiva dell’interrupt. In questo caso però il  ritardo massimo con cui saranno gestiti gli altri interrupt si dimezza. All’inter no della procedura BTimer verrà sempre richiamata la procedura di aggior namento dei timers software sincroni.

I timers  software sincroni

Con questi timers si possono realizzare temporizzazioni lente di 1secondo, 1minuto o anche superiori sincrone con la frequenza del basic timer. Il listato 2 descrive la procedura di aggiornamento dei timers sincroni.

#define PSET100MS 20
#define PSET1SEC 10
#define PSET1MIN 60

unsigned char SWT100ms;
unsigned char SWT1sec;
unsigned char SWT1min;

int Synctmrsupdate()
{
         int tmrtype = 1;
         if (—SWT100ms == 0)
                 {
                 SWT100ms = PSET100ms;
                 tmrtype++;
                 if (—SWT1sec == 0)
                         {
                         SWT1sec = PSET1SEC;
                         tmrtype++;
                         if (—SWT1min == 0)
                                {
                                SWT1min = PSET1MIN;
                                tmrtype++;
                                }
                        }
                }
        return (tmrtype);
}
Listato 2

Nell’esempio si è ipotizzato di utilizzare un basic timer con periodicità di 5ms (PSET100MS = 20). La procedura rappresenta la struttura base della procedura Synctmrs, all’interno dei diversi if si possono inserire le chiamate a quelle procedure che richiedono una periodicità più lenta rispetto al periodo del basic timer.

I timers  software asincroni

I timers asincroni sono i più utilizzati all'interno di un'applicazione. Si consideri ad esempio il driver di gestione di un lettore chipcard. Le operazioni da eseguire quando viene inserita una carta nel lettore sono:

➤ riconoscere l’evento inserzione di una carta;

➤ attendere tempo per evitare false commutazioni;

➤ fornire alimentazione alla chip card;

➤ attendere tempo;

➤ iniziare la comunicazione con la chipcard.

Oppure un driver di gestione della comunicazione seriale quando riceve un carattere deve:

➤ attivare un timer di timeout;

➤ al prossimo carattere ricevuto ricaricare il timer;

➤ al timeout azzerare lo stato del driver di comunicazione.

Questi timers devono essere attivati in modo asincrono quando viene riconosciuto un determinato evento. Il  timer deve sospendere l’esecuzione della procedura per un certo tempo per poi riprenderla al timeout oppure eseguire al timeout una determinata procedura. La soluzione più semplice ed intuitiva è quella di realizzare all’interno del software dei loop che contengono determinate istruzioni da eseguire un certo numero di volte in modo da ottenere il ritardo desiderato. Questa soluzione però dovrebbe essere adottata solo se i ritardi  da realizzare non superano  i 10/20microsecondi. In tutti gli altri casi si deve utilizzare un timer software asincrono. Con questi timers si potrà facilmente realizzare le azioni menzionate prima: dopo questo evento attendere tempo prima di eseguire una certa funzione oppure al timeout eseguire una certa funzione. Come i timers sincroni anche la procedura di aggior namento dei timers asincroni viene richiamata all’interno della procedura di basic timer. Perciò la massima risoluzione, cioè la massima frequenza di conteggio, dei timers asincroni sarà quella del basic timer. Per ogni timer asincrono si deve definire la risoluzione del timer o, in altre parole, la frequenza con cui viene decrementato e il  valore di preset cioè il valore iniziale di conteggio. La struttura T_TIMER di un timer software asincrono è definita nel listato 3.

#define TMRTYPE1MS 0
#define TMRTYPE100MS 1
#define TMRTYPE1SEC 2
#define TMRTYPE1MIN 3
#define TMRTYPEIDLE 255

// definzione dei flags
#define FTMRBUSY 0x01
#define FTMREXPIRED 0x02
#define FTMRRELOAD 0x04

typedef struct
{
        unsigned char type;
        unsigned char flags;
        unsigned pset;
        unsigned count;
        void (*pTimerf)(void);
} T_TIMER;
Listato 3

Nella struttura la variabile type definisce il tipo di timer cioè la sua frequenza di conteggio. Il preset è il valore iniziale assegnato al timer. Il valore 255 indica che il timer è inattivo e non dev’essere decrementato. Per definizione ogni timer asincrono viene attivato in un istante qualsiasi senza nessuna correlazione con l’interrupt di basic timer che ne decrementa il valore. Può accadere che l’interrupt di aggiornamento del timer intervenga immediatamente
dopo la configurazione del timer. Per esempio dopo aver configurato un timer con tic al secondo il primo decremento avviene entro un secondo e non dopo un secondo.
Questo introduce un incertezza di un conteggio sul valore del timer. Ad esempio un timer con preset 10sec e con tic 100ms resterà attivo per un tempo compreso tra 9.9sec e 10sec. Lo stesso timer con tic 1sec resterà attivo per un tempo compreso tra 9sec e 10sec. Le altre variabili che sono state incluse nella struttura T_TIMER sono:

count che indica il conteggio attuale del timer;
flags che contiene i bit di stato del timer.

Nell’esempio sono stati definiti tre flags di stato:

FTMRBUSY a 1 quando il timer è attivo
FTMREXPIRED a 1 quando il timer ha raggiunto il valore zero di timeout
FTMRRELOAD a 1 specifica che al timeout il timer deve essere ricaricato con il valore di preset.
pTimerf che è un puntatore ad una funzione.
Quando il timer viene attivato si può assegnare a questo parametro l’indirizzo di una funzione che sarà richiamata automaticamente al timeout del timer. Si consideri ad esempio la funzione di blink di un led. Per attivare il blink si deve semplicemente configurare il timer con il tempo di blink desiderato e associare al puntatore pTimerf l’indirizzo della funzione che commuta lo stato dell’uscita del microcontrollore collegata al led. Se si attiva anche il flag di reload durante la configurazione del timer, il sistema gestirà in automatico il blink del led senza ulteriori interventi da parte dell’applicativo.

La gestione dei timers software asincroni

Tutti i timers asincroni sono raggruppati nella tabella dei timers:

#define NSWTIMERS 10
T_TIMER SWTimerTable[NSWTIMERS] ;

Ovviamente il numero di timer dipende dalle risorse disponibili e dalle necessità dell’applicazione. Si possono utilizzare due regole per determinare la dimensione della SWTimertable. La più semplice è quella di dimensionare la tabella per il numero massimo di timers utilizzati dall’applicazione. Ogni timer sarà identificato dall’offset all’interno della tabella. Un’altra scelta è quella di dimensionare la tabella in funzione del massimo numero di timers attivi contemporaneamente. In questo caso la tabella dei timers avrà dimensioni inferiori al caso precedente ma si dovrà realizzare una funzione di ricerca del primo timer libero all’interno della SWTimerTable. Inizialmente si descriveranno le funzioni con assegnazione statica dei timers nella tabella, successivamente si introdurranno le modifiche necessarie per realizzare l’assegnazione dinamica. La gestione dei timers software richiede almeno cinque procedure principali:

➤ la TimerSet che configura  il timer software;

➤ la TimerRst che interrompe il conteggio e azzera lo stato del timer;

➤ la TimerChk che controlla lo stato del timer;

➤ la TimerTask che gestisce l’aggiornamento dei timer attivi nella tabella dei timer;

➤ la TimerInit che configura tutti i timer nello stato di riposo…

Le procedure sono descritte nel listato 4.

unsigned char TimerSet (unsigned char tmrID, unsigned char type,
   unsigned value, unsigned char flags, void (*pf)())
{
   SWTimerTable[tmrID].type = type ;
   SWTimerTable[tmrID].value = SWTimerTable[tmrID].pset = value ;
   SWTimerTable[tmrID].pf = pf;
   SWTimerTable[tmrID].flags = flags | FTMRBUSY;
   return (tmrID);
}
unsigned char TimerRst (unsigned char tmrID)
{
   SWTimerTable[tmrID].type = TMRTYPEIDLE;
   SWTimerTable[tmrID].value = SWTimerTable[tmrID].pset = 0;
   SWTimerTable[tmrID].pf = 0;
   SWTimerTable[tmrID].flags = 0;
   return (tmrID) ;
}
unsigned char TimerChk (unsigned char tmrID)
{
   return (SWTimerTable[tmrID].flags);
}
void TimerTask(unsigned char timertype)
{
   int tmrID;
   for (tmrID=0; tmrID < NTIMERS; tmrID++)
   {
     // if timer need decremented
     if (SWTimerTable[tmrID].type <= timertype)
     {
     
       // active timer? (not expired)
       if (SWTimerTable[tmrID].flags & FTMRBUSY)
       {

           // countdown to zero
           if (—(SWTimerTable[tmrID].value) == 0)
           {

               if (SWTimerTable[tmrID].pf)
                   SWTimerTable[tmrID].pf();//call timeout f()
               if (SWTimerTable[tmrID].flags & FTMRRELOAD)
                   SWTimerTable[tmrID].value = SWTimerTable[tmrID].pset;
          else
             {
                   SWTimerTable[tmrID].flags &= ~FTMRBUSY;
                   SWTimerTable[tmrID].flags |= FTMREXPIRED;
              }
            }
          }
       }
    }
}
void TimerInit()
{
   unsigned char tmrID;
   for (tmrID=0; tmrID < NTIMERS; tmrID++) TimerRst(tmrID);
}
void BTimer()
{
   unsigned char tmrtype;
   tmrtype = Synctmrsupdate();
}
Listato 4

I  parametri in ingresso alla funzione TimerSet sono: l’identificativo del timer che rappresenta l’offset all’interno della SWT imerTable. (assegnazione statica). Il  valore di preset del timer, la frequenza di conteggio del timer, la configurazione dei flags e infine il puntatore alla procedura da eseguire al timeout. Quest’ultimo parametro può essere anche NULL. La procedura TimerRst invece azzera la configurazione del timer selezionato e riporta il timer nella condizione di riposo. L’unico parametro in ingresso in questo caso è l’identificativo del timer che si vuole bloccare. La procedura TimerChk restituisce la variabile flags del timer selezionato. In questo modo è possibile verificare lo stato del timer: se è attivo oppure se ha raggiunto il timeout. La procedura più interessante però è la TimerTask che aggiorna periodicamente lo stato della tabella dei timers software asincroni. La chiamata a questa procedura viene inserita all’interno del basic timer descritto in precedenza. Nella procedura di aggiornamento dei timers sincroni (listato 2) era stato introdotta la variabile tmrtype, questa variabile serve alla TimerTask per stabilire quali timers software asincroni devono essere aggiornati. Se vale zero si devono aggiornare  i timers attivi con tic pari al basic timer, se vale 1 si aggiornano  i timers attivi con tic fino a 100ms, se vale 2 tutti i timers con tic fino a 1 sec e così via. In altre parole si aggiornano tutti i timer con tic, o meglio di tipo, minore o uguale al valore timertype. La procedura TimerTask pertanto includerà un ciclo for di scansione della tabella con confronto sul campo tipo del timer. Se il timer dev’essere aggiornato  il campo count viene decrementato di uno. Se è stato raggiunto il valore zero si aggiornano  i flags di stato del timer ed eventualmente viene richiamata la funzione associata al parametro pTimerf. Questa gestione dei timers asincroni richiede però alcuni accorgimenti La TimerTask viene richiamata all’interno di un interrupt, perciò la SWTimerTable non può avere dimensioni elevate altrimenti la sua scansione allungherebbe troppo la durata dell’interrupt di basic timer. Questo pone un limite al numero massimo di timers utilizzabili. Se il timer prevede che al timeout sia richiamata la funzione pTimerf, questa funzione viene a sua volta eseguita all’interno dell’interrupt di basic timer quindi anche questa funzione deve essere necessariamente breve. Si consideri che all’interno della stessa scansione ci possono essere più timers che raggiungono il timeout contemporaneamente e quindi più procedure che devono essere eseguite nello stesso interrupt. A questo proposito si osserva che non è necessario associare ad ogni timer una funzione da eseguire al timeout ma talvolta è sufficiente controllare lo stato del timer. Ad esempio volendo inserire un ritardo all’interno di un task si può agire in due modi come indicato nel listato 5. In entrambi i casi la procedura mytask viene richiamata continuamente nel loop principale del sistema. Quando il task si trova nello stato2 attende finché il timer non raggiunge  il timeout prima di portarsi nello stato3. La soluzione del listato 5.1 utilizza una funzione richiamata al timeout.

unsigned char taskstatus;
#define stato1 1
#define stato2 2
#define stato3 3
#define SWTMR1 1
void TSWtimeout()
{
   taskstatus = stato3 ;
}

void mytask()
{
   switch (taskstatus)
   case stato1:
   TimerSet(SWTMR1, 10, ,TMRTYPE1SEC, TSWTtimeout)
   taskstatus = stato2;
   break;
   case stato2: break;

   case stato3: ……
}
Listato 5.1

La procedura mytask rimane nello stato2 finché il  timer non raggiunge il  timeout e richiama automaticamente la TSWtimeout() che aggiorna lo stato della procedura mytask. La soluzione del listato 5.2 invece utilizza il controllo dei flags di stato del timer.

unsigned char taskstatus;
#define stato1 1
#define stato2 2
#define stato3 3
void mytask()
{
        switch (taskstatus)
        case stato1:
                TimerSet(SWTMR1,….)
                taskstatus = stato2;
                break;
        case stato2:
                if (TimerChk () & FTMREXPIRED)
                {
                          TimerRst (SWTMR1) ;
                          taskstatus = stato3;
                }
                break ;
        case stato3: ……
}
Listato 5.2

Nello stato2 la procedura controlla continuamente lo stato del timer. Quando il timer raggiunge il timeout lo stato della procedura mytask cambia nello stato3.

unsigned char TimerSet (unsigned char tmrID, unsigned char type, unsigned value, unsigned char flags, void (*pf)())
{
       int i;
       for (i=0; (i<MAXTIMERS) && (SWTimerTable[tmrID].type != TMRTYPEIDLE); i++) ;
       if (i<MAXTIMERS)
       {
               SWTimerTable[tmrID].type = type;
               SWTimerTable[tmrID].value = SWTimerTable[tmrID].pset = value;
               SWTimerTable[tmrID].pf = pf;
               SWTimerTable[tmrID].flags = flags | FTMRBUSY;
        return (tmrID);
        }
                return (0xFF);
}
Listato 6

Conclusioni

La gestione dei timers software è uno dei punti chiave della programmazione dei dispositivi embedded. La libreria descritta in questo articolo contiene le funzioni base per la gestione dei timers software, ad essa si possono aggiungere altre funzioni ad esempio per la gestione di un RTC software e la manipolazione di variabili data e ora.

 

 

Scrivi un commento

EOS-Academy