Corso programmazione PICMicro in C – Appunti di utilizzo del Timer1 : spunti utili per la realizzazione di orologi e timers

Molti nel momento in cui devono realizzare un orologio o un timer, pensano subito ad utilizzare un RTCC (Real Time Clock Calendar, come il DS1307) nel primo caso o il semplice oscillatore di sistema nel secondo. Nel caso in cui si voglia realizzare un orologio che mostra anche la data, allora la scelta di un RTCC è sicuramente valida in quanto ci svincola dal dover fare numerosi calcoli e acrobazie (soprattutto nel caso degli anni bisestili!). Se invece dobbiamo unicamente mostrare un’orario, senza data, allora l’uso di un RTCC è forse troppo esagerato.

Tra l’altro chi lavora con i pic a 16 bit (pic24 e dsPic) non usa nemmeno più l’RTCC esterno in quanto è già integrato a bordo come una normale periferica

Utilizzare l’oscillatore di sistema (mi riferisco al quarzo o all’oscillatore che fanno funzionare la CPU) per realizzare un orologio è invece una scelta non consigliata in quanto può portare a grandi imprecisioni che si accumulano in maniera esponenziale con il passare del tempo: in pochi giorni possiamo ritrovarci l’orologio che va già un’ora avanti. Questo perchè, nel caso in cui si utilizza l’oscillatore di sistema, è molto difficile riuscire a sincronizzare le istruzioni con il calcolo del trascorrere del tempo. Nulla vieta che ci si possa riuscire, per carità, ma che fatica! Nel caso della costruzione di un semplice timer che deve conteggiare tempi brevi magari questa soluzione potrebbe anche andar bene… Ma per un orologio… ne dubito!

Per questi scopi sui PIC16 e PIC18 (e sui nuovi PIC12) esiste il Timer1. Il Timer1 è un timer a 16bit: ha un registro alto e un registro basso in cui contenere il conteggio. Quando il valore passa da 0xFFFF a 0x0000 (overflow) scatta un interrupt come abbiamo già visto che accade per il Timer0. La particolarità del Timer1 sta nel fatto di poter operare come Timer o come Counter.

Il funzionamento come Timer non ci interessa: in questa modalità il conteggio viene incrementato dall’oscillatore di sistema (come accade per il Timer0), ovvero il conteggio incrementa ad ogni ciclo istruzioni (FOSC/4) e anche qui abbiamo un prescaler che ci permette di rallentare la velocità di incremento.

La modalità di funzionamento che a noi interessa per realizzare un orologio o un timer preciso è quella come Counter. In modalità counter l’incremento di Timer1 non è regolato dal clock di sistema e difatti il Timer1 ha la possibilità di incrementare il conteggio sfruttando un oscillatore secondario, diverso da quello di sistema. E’ qui che sta il trucco, anche se di trucco in realtà non si tratta in quanto il Timer1 è stato appositamente studiato per eseguire queste operazioni relative al calcolo del trascorrere del tempo “umano”.

L’oscillatore secondario è comunemente un altro quarzo che va collegato allo stesso modo con cui si collega il quarzo di sistema (cioè con i due condensatori per permettere l’oscillazione), ai due pin del picmicro che hanno i contrassegni T1OSO e T1OSI (Timer1 OScillator Output e Timer1 OScillator Input)

Sui pic16 a 40 pin in genere i pin T1OSO e T1OSI si trovano posizionati su RC0 e RC1, sui pic più piccoli, come il 16F628, si trovano su RB6 e RB7 (ovvero gli stessi pin usati per la programmazione, per cui non alimentate il circuito dal pickit durante le prove). In ogni caso si trovano subito dopo ai pin destinati al quarzo principale. Nei nuovi PIC12, invece l’oscillatore per il Timer1 si collega sugli stessi pin dell’oscillatore primario (è ovvio che si usa o l’uno o l’altro).

Avete capito bene: se vi accingete a realizzare un orologio sfruttando il Timer1 monterete due quarzi: quello classico di sistema e quello secondario che andrà ad alimentare unicamente il Timer1. In realtà quando si realizza un orologio generalmente si monta unicamente il quarzo secondario mentre la CPU, qualora ve ne sia la possibilità, viene fatta funzionare con l’oscillatore interno o anche con una rete RC esterna: in questi casi non serve la precisione per la CPU, la precisione serve per il Timer1 che deve mantenere l’orario, e con il suo quarzo è assicurata.

Bene, per afferrare pienamente il funzionamento diamo uno sguardo al diagramma del Timer1 relativo al PIC16F877A, che è uno dei più semplici:

Partiamo da sinistra in basso: abbiamo i due pin ai quali si collega il quarzo secondario che alimenta l’oscillatore del Timer1 e che ho detto si chiamano T1OSO e T1OSI. Incontriamo un primo bit chiamato T1OSCEN, l’acronimo avrete già capito che sta per Timer1 OSCillator Enable (bit 3 del registro di controllo del Timer1 – T1CON). Quando tale bit viene posto a 1, il circuito di oscillazione che serve a sfruttare il quarzo esterno per il Timer1 viene abilitato e in automatico i pin del picmicro che hanno i contrassegni T1OSO e T1OSI vengono impostati come ingressi. Quindi: non è necessario impostare tali pin come ingressi nel registro tristato: viene fatto in automatico ponendo T1OSCEN=1.

E’ anche possibile utilizzare, invece del quarzo, un oscillatore esterno, ovvero un circuito che produce un’onda quadra. In questo caso si utilizza ovviamente un solo ingresso per il timer1 e cioè quello che ha il contrassegno T1CKI (Timer1 Clock Input – che è posizionato su T1OSO). Se si sfrutta questa terza opzione, T1OSCEN va posto a zero in maniera da disattivare la circuiteria di oscillazione e utilizzare quindi l’onda quadra già fornita dall’esterno.

Abbiamo quindi un deviatore che permette di alimentare il Timer1 con l’oscillatore/quarzo esterno (modalità counter) o con l’oscillatore di sistema (FOSC/4 – modalità timer). Il bit che si occupa di questo è TMR1CS (Timer1 Clock Source – bit 1 di T1CON); vediamo che per sfruttare l’oscillatore esterno si deve impostare a 1. Segue quindi il prescaler (il divisore di frequenza) da impostare con i due bit T1CKPS0-T1CKPS1 (Timer1 ClocK PreScaler bit 0 e 1 – bits 4 e 5 di T1CON). Due bit permettono solo 4 valori possibili di divisione : 1 (frequenza oscillatore tal quale),2,4 e 8. In realtà vedremo che a noi della divisione, per questo scopo, non importa nulla e ci basta il valore di prescaler pari a 1.

Segue quindi un selettore che permette di far funzionare il Timer1 in modalità sincrona o asincrona. Cosa vuol dire? In modalità sincrona il Timer1 è sincronizzato con l’oscillatore di sistema il che vuol dire che è in grado di rilevare la condizione di SLEEP della CPU. Questo significa che se la CPU va in “letargo” allora si addormenta anche il Timer1. In modalità asincrona, invece, il Timer1 continua a funzionare nonostante tutto il resto si sia “addormentato”. Questa modalità di funzionamento è sicuramente vantaggiosa e consente, in alcune applicazioni, di continuare a mantenere il tempo risparmiando corrente. La modalità asincrona si abilita ponendo il bit T1SYNC (bit 2 di T1CON) a 1.

Quando il Timer1 opera in modalità Timer, viene cioè incrementato dal clock di sistema, il settaggio di T1SYNC è ininfluente in quanto è implicito che per operare in questa modalità deve per forza essere sincronizzato.

Abbiamo quindi un flag di abilitazione del Timer1, TMR1ON (bit 0 di T1CON) che permette di avviare/fermare il conteggio e infine i due registri di conteggio basso (TMR1L) e alto (TMR1H) che insieme ci permettono di avere un conteggio con valori a 16bit. Quando il registro di conteggio va in overflow, ovvero passa da 0xFFFF (65535) a 0, viene settato il flag di interrupt overflow su Timer1 che prende il nome di TMR1IF (Timer1 Interrupt Flag).

Se abbiamo abilitato la notifica di interrupt su overflow del Timer1 (TMR1IE) e quindi abbiamo anche abilitato la notifica generale interrupt (GIE) e gli interrupt di periferica (PEIE), possiamo intercettare nella nostra Interrupt Service Routine anche questo interrupt testando il flag TMR1IF.

I pic della serie Enhanced (quelli che hanno la sigla nel formato PIC16F1xxx, es.: il PIC16F1827, PIC16F1939 ecc) hanno la possibilità di sfruttare più sorgenti di clock per il Timer1. Su questi pic, oltre all’oscillatore esterno e al ciclo istruzioni è possibile anche utilizzare il quarzo di sistema tal quale (FOSC, senza la divisione per 4) o l’oscillatore destinato al modulo capacitive sensing. Per tale motivo il diagramma di funzionamento del Timer1 su tali pic è un po’ più complesso e i bit del registro T1CON sono arrangiati in maniera differente, ma se avete capito quello che ho detto fin’ora non avrete difficoltà.

Bene, abbiamo capito come funziona il Timer1 ma ora… Come impostarlo per realizzare un orologio?

Per realizzare un orologio o un timer avremmo bisogno di un segnale che scandisca i secondi in maniera precisa: conto i secondi a partire da 0, quando arrivo a 59 il successivo incremento dei secondi me li riporta a zero e mi fa aumentare di una unità i minuti e così via… Il concetto alla base di un orologio è molto semplice.

Quindi la reale domanda è: come ottenere un segnale che scandisca i secondi? Sicuramente lo possiamo ottenere tramite un interrupt del Timer1 che scatti ogni secondo. Per fare questo si utilizza il Timer1 in modalità counter collegandovi il famoso quarzetto da 32768Hz (32.768KHz).

Quarzo da 32.768Khz. Formato TC26H

Io lo chiamo quarzetto perchè tale quarzo si trova solo nel formato cilindrico TC26H che è molto più piccolo del più comune HC49. E’ possibile trovare questo quarzo nei comuni orologi a lancetta che vanno appesi al muro. La cosa brutta di questo quarzo è che nella maggior parte dei casi sul corpo non riporta scritto nulla che possa far pensare si tratti di un quarzo da 32768Hz.

Un valore strano? Per niente. Non avete fatto caso che il valore 32768 vale giusto la metà (più 1) di 0xFFFF? 32768Hz vuol dire che il quarzo genera 32768 oscillazioni complete in un secondo, per cui quando ho contato 32768 oscillazioni è passato un secondo, fin qui ok? (Ho visto che qualcuno ha ancora parecchie difficoltà a capire il concetto di frequenza e periodo)

Supponiamo ora di far partire il Timer1 dal valore 32768 (0x8000 in esadecimale: valore  che io chiamo di preload) e di farlo incrementare sfruttando il quarzetto esterno da 32768Hz. Quando il Timer1 avrà raggiunto il valore 32768+32767 sarà giunto al suo massimo valore: 65535 (0xFFFF), un’altra oscillazione mi causerà l’overflow e quindi l’interrupt… e questo avverrà dopo giusto un secondo!

Il sistema sta quindi nell’utilizzare un quarzo esterno da 32.768KHz, settare il Timer1 per poterlo utilizzare, precaricare i registri con il valore 0x8000 e quindi intercettare l’interrupt. Sappiamo bene che una volta intercettato l’interrupt è passato un secondo e ci facciamo quindi tutti i nostri bravi calcoli per far funzionare l’orologio (aumentiamo i secondi, stiamo attenti che a 60 secondi aumentiamo i minuti e azzeriamo i secondi ecc). Non dobbiamo dimenticarci, inoltre, di azzerare il flag di interrupt e di ricaricare il Timer.

Qualcuno potrebbe obiettare che ricaricare il Timer possa influire in qualche modo sul conteggio. Sappiamo che questo è vero per il Timer0: ricaricare il Timer0 richiede 2 cicli macchina per cui il preload generalmente si aumenta di 2 unità in maniera da recuperare i 2 cicli macchina persi.

Prima che qualcuno si incastri in questo concetto faccio un esempio: se precarico, ad esempio, il Timer0 a 100, questo andrà in overflow dopo 155 + 1 conteggi. Se “perdo tempo”, ovvero 2 cicli macchina, per poterlo ricaricare, vuol dire che allora dovrò partire da 102 in maniera da “arrivare prima” all’interrupt dato che 2 cicli già li ho persi.

Qui invece stiamo parlando del Timer1 che, a parte il fatto che non sta funzionando sfruttando i cicli macchina interni, è anche a 16bit e ci basta quindi un piccolo accorgimento: nel momento in cui scatta l’interrupt entrambi i registri TMR1H e TMR1L si trovano a zero, ok? Dato che il valore di preload è 0x8000 perchè dobbiamo andare a disturbare TMR1L che già si trova a 0x00? Carichiamo soltanto TMR1H con il valore 0x80 (vedete a cosa serve scrivere i numeri in esadecimale?). Questa operazione non influirà sul conteggio in quanto TMR1L continua ad incrementare indisturbato mentre noi carichiamo TMR1H: difatti TMR1H entrerà in funzione solo quando TMR1L sarà arrivato a 255.

Ricapitolando: se vogliamo realizzare un orologio o un timer preciso dobbiamo realizzare il nostro circuito per poter sfruttare un quarzo esterno da 32768Hz collegato come ingresso del timer1. Se il nostro picmicro ha l’oscillatore interno possiamo sfruttarlo abilitandolo nella word di configurazione, altrimenti nulla vieta di montare due quarzi. Un circuito di esempio, su un PIC16F628A, può essere questo:

Il valore dei condensatori da usare per il quarzo secondario è di 33pF come consigliato dai datasheet:

Come vedete è possibile utilizzare anche condensatori di valore più elevato ma il circuito si stabilizzerà un po’ più tardi.

Non vi sognate nemmeno di realizzare questa configurazione circuitale su una breadboard: questo tipo di oscillatore consuma pochissima corrente e gira ad una frequenza abbastanza bassa per cui è relativamente sensibile ai disturbi esterni. Una breadboard inoltre, con tutte le capacità parassite che introduce, non vi consentirà mai di apprezzare la reale precisione di questo sistema, per cui vi prego già da ora: non fate che realizzate il circuito su breadboard e poi mi venite a dire che il sistema non è preciso. Noi ci abbiamo impazzito su un bel po’ di tempo per scoprire alla fine che i problemi di imprecisione erano unicamente dovuti all’utilizzo della Breadboard.

In aggiunta, il documento della Microchip DS01146B (Compiled Tips ‘N Tricks Guide) a pagina 29 (paragrafo intitolato: TIP #19 Low Power Timer1 Oscillator Layout) consiglia giustamente, quando si fa uso di quarzi con basso valore di frequenza, di mettere il quarzo ed i condensatori quanto più vicino possibile ai pin e addirittura di circondarli con un anello di massa nel caso in cui sulla scheda siano presenti forti disturbi:

Non a caso i pin dell’oscillatore primario in genere si trovano accanto a quelli dell’oscillatore secondario.

Nel main, prima di entrare nel ciclo infinito, andremo precaricare il Timer1 con il valore 0x8000 (anche se non è strettamente necessario partire all’avvio con questi valori):

TMR1H=0x80;
TMR1L=0x00;

Abilitiamo quindi gli interrupt:

GIE=1; // gestione globale interrupt attiva
PEIE=1; // interrupt di periferica abilitati
TMR1IE=1; // interrupt su overflow timer1 abilitato

Impostiamo il funzionamento del Timer1:

T1CON=0b00001111;
/*
bit 7 e 6 : non usati
bit 5 e 4 (T1CKPS1:T1CKPS0): 00 - Prescaler impostato a 1:1
bit 3 (T1OSCEN): 1 - circuito di oscillazione abilitato
bit 2 (T1SYNC): 1 - timer1 in modalità asincrona
bit 1 (TMR1CS): 1 - timer1 impostato per usare oscillatore esterno
bit 0 (TMR1ON): 1 - timer1 abilitato
*/

Attenzione! Il registro T1CON del vostro PIC potrebbe avere un diverso arrangiamento dei bit, per cui non copiate e incollate questo codice ma verificate prima che la posizione dei bit sia quella corretta. In particolare sui  PIC12, PIC16F1xxx e PIC18 questo codice non è corretto.

Fatto questo il Timer1 incomincerà a conteggiare i fronti di salita del clock fornito dal quarzetto esterno. Non dovremo far altro, nell’ISR, che intercettare l’interrupt su overflow del Timer1 e fare le nostre operazioni:

void ISR(void)
   {
   if(TMR1IF) // interrupt su overflow timer1: è passato un secondo
      {
      TMR1H=0x80; // ricarico il timer dal valore 0x8000, non vado a scomodare anche TMR1L che già si trova a zero
 
      // qui eseguo le mie operazioni di conteggio secondi, minuti, ore
      // cercando di farle nella maniera più sbrigativa possibile
 
      TMR1IF=0; // azzero il flag di interrupt
      }
   }

supponendo di avere le variabili secondi, minuti e ore, dichiarate come unsigned char, un’idea di come agire potrebbe essere la seguente:

void ISR(void)
   {
   if(TMR1IF) // interrupt su overflow timer1: è passato un secondo
      {
      TMR1H=0x80; // ricarico il timer dal valore 0x8000, non vado a scomodare anche TMR1L che già si trova a zero
      secondi++; // incremento i secondi
 
      if (secondi==60)
         {
         secondi=0;
         minuti++;
         }
      if (minuti==60)
         {
         minuti=0;
         ore++;
         }
      if (ore==24)
         {
         ore=0;
         }
 
      TMR1IF=0; // azzero il flag di interrupt
      }
   }

Nel main, poi, ci occuperemo di mostrare i valori delle variabili ore, minuti e secondi sull’LCD, sui display a 7 segmenti usando il multiplex ecc.

Adesso mi raccomando: fate un orologio sfruttando questi appunti e andate a pubblicare il progetto da un’altra parte?

Ringrazio Luca Gallucci per la collaborazione.

Bibliografia

AN580
Compiled Tips ‘N Tricks Guide
Video Microchip – Emulating an I2C Real Time Clock Calendar on a PIC16F886

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)