Corso 8051

CAPITOLO 7 - I Timer

L'8051 classico dispone di due timer, che possono essere configurati, controllati e letti in maniera indipendente. I timer dell'8051 hanno tre funzioni generali:

I tre modi di usare un timer verranno discussi separatamente. I primi due saranno affrontati in questo capitolo mentre l'uso del timer come generatore di baud rate verra' discusso nel capitolo relativo alla porta seriale.

Come fa un timer a contare?

Come fa un timer a contare? La risposta e' semplice. Esso conta sempre incrementando il suo valore. Non importra se viene usato come timer o come contatore o come generatore di baud rate. E' sempre incrementato dal microcontrollore.

USARE UN TIMER PER MISURARE UN TEMPO

Ovviamente uno degli usi primari dei timer e' quello di misurare il tempo. Parleremo prima di questo uso e poi di quello relativo al conteggio di eventi. Quando un timer e' usato per misurare il tempo e' anche chiamato "interval timer" poiche' esso sta misurando l'intervallo temporale tra due eventi.


Quanto tempo impiega un timer a contare?

Quando il timer e' nel modo "interval timer" ed e' correttamente configurato, esso viene incrementato di uno ogni ciclo macchina, ovvero 12 impulsi di clock.
Percio' un timer attivo sara incrementato:

921.583 volte al secondo. A differenza delle istruzioni che hanno durata variabile, il ciclo di un timer ha durata fissa; uno impulso per ciclo macchina. Se un timer ha contato da 0 a 50.000 possiamo calcolare: che sono trascorsi 0,0542 secondi .

Ovviamente non e' molto utilie sapere che sono trascorsi 0,0542 secondi. Se voi voleste far partire un evento ogni secondo dovreste aspettare il conteggio del timer da 0 a 50.000 per 18,45 volte. Come si puo' aspettare per un tempo non intero? Non e' possibile. Allora proviamo a fare un calcolo imporatnte.

Supponiamo di voler sapere quante volte il timer viene incrementato ogni 0,05 secondi. Possiamo fare questo semplice calcolo:

Il risultato ci dice che impieghiamo 0,05 secondi (un ventesimo di secondo) per contare da 0 a 46.079. Piu' precisamente il tempo trascorso e' di 0,49999837 secondi, cio' vuol dire che mancano 0,000000163 secondi all'appello, comunque e' abbastanza preciso anche per i lavori commissionati dal governo. Considerate che se costruite un orologio basato sull'8051, con tali ipotesi, ammesso che il quarzo sia perfetto, avrete un secondo in anticipo ogni due mesi. Beh, io credo che cio' sia accurato per la maggior parte delle applicazioni. Magari il mio orologio andasse avanti soltanto di un secondo ogni due mesi!

Una volta stabilito che esso impiega un ventesimo di secondo e vogliamo eseguire un evento ogni secondo sara' sufficiente che il timer conti da 0 a 46.079 per venti volte; a questo punto eseguite le azioni richieste dall'evento, resettate il timer e aspettate che il timer completi di nuovo il conteggio altre venti volte. In questa maniera eseguirete effettivamente il vostro evento una volta al secondo con una precisione al ventesimo di secondo.

Abbiamo ora un sistema capace di misurare il tempo. Quello che ci serve ora e' sapere come controllare il timer e inizializzarlo in maniera che possiamo dargli le informazioni corrette.

I registri Timer SFR

Come precedentemente affermato, l'8051 ha due timer che funzionano ambedue nella stessa maniera. Uno e' chiamato TIMER0 e l'altro TIMER1. I due timer condividono due registri SFR (TCON e TMOD) che servono a controllarli. Ciascun timer pero' dispone di due SFR per loro uso esclusivo (TH0/TL0 e TH1/TL1).

Ai registri SFR abbiamo dato un nome mnemonico per individuarli facilmente, in effetti essi hanno un valore numerico. E' spesso utile conoscere l'indirizzo corrispondente al nome mnemonico del registro SFR. Per quello che riguarda i timer essi sono:

Nome SFR Descrizione Indirizzo SFR
TH0 Timer 0 Byte Alto 8Ch
TL0 Timer 0 Byte Basso 8Ah
TH1 Timer 1 Byte Alto 8Dh
TL1 Timer 1 Byte Basso 8Bh
TCON Timer Control 88h
TMOD Timer Mode 89h

Quando inserite il nome di un registro SFR nell'assembler, esso viene internamente automaticamente convertito in un indirizzo, Per esempio:

carica il valore 25h nel registro TH0, tenuto conto che esso risiede nell'indirizzo 8Ch, sara' convertita in: Ora, torniamo a parlare del timer 0. Esso ha due registri TH0 e TL0, che senza complicarci la vita, possiamo ritenere essere il byte piu' e meno significativo del timer stesso. Se il timer 0 ha valore 0, entrambi i suddetti registri avranno valore 0. Quando il timer assume valore 1000 (decimale), TH0 conterra' il valore 3 (decimale) e TL0 il valore 232 (decimale).

Per ricordarvi come funziona la notazione byte basso/alto, ricordatevi di moltiplicare il byte alto per 256 e addizionare il byte basso al risultato ottenuto. Ovvero:

Il timer 1 funziona esattamente nella stessa maniera ma dispone dei registri TH1 e TL1.

Poiche' solo due byte sono usati per ogni timer risulta evidente che il massimo valore che un timer puo' assumere e' 65.535 (decimale). Nel momento in cui viene raggiunto tale valore un ulteriore impulso di clock lo resettera' o per meglio dire lo mandera' in overflow.

Il registro SFR TMOD


Il registro TMOD e' usato per controllare il modo operativo di ambedue i timer. Ogni bit del registro fornisce al microntrollore una specifica informazione che riguarda il funzionamento del timer. I 4 bit piu' significativi (da 4 a 7) sono relativi al timer 1 mentre i 4 bit meno significativi (da 0 a 3) hanno il medesimo significato ma sono relativi al timer 0.

I bit del registro TMOD hanno il seguente significato :

TMOD (89h) SFR
Bit Nome Spiegazione della funzione Timer
7 GATE1 Quando questo bit e' settato, il timer 1 e' attivo solo quando il pin P3.3 e' nello stato alto. Se tale bit e ' resettato il timer 1 sara' svincolato dallo stato del pin P3.3. 1
6 C/T1 Quando questo bit e' settato il timer 1 contera' il numero degli eventi sul pin T1 (P3.5). Se il bit e' resettato il timer 1 verra' incrementato ogni ciclo macchina. 1
5 T1M1 Bit 1 di modo del timer 1(vedi sotto) 1
4 T1M0 Bit 0 di modo del timer 1(vedi sotto) 1
3 GATE0 Quando questo bit e' settato, il timer 0 e' attivo solo quando il pin P3.2 e' nello stato alto. Se tale bit e ' resettato il timer 0 sara' svincolato dallo stato del pin P3.2. 0
2 C/T0 Quando questo bit e' settato il timer 0 contera' il numero degli eventi sul pin T1 (P3.5). Se il bit e' resettato il timer 0 verra' incrementato ogni ciclo macchina. 0
1 T0M1 Bit 1 di modo del timer 0(vedi sotto) 0
0 T0M0 Bit 0 di modo del timer 0(vedi sotto) 0

Come potete vedere dalla tabella di sopra, quattro bit, due per ciascun timer sono usati per specificare il modo operativo. I modi operativi sono:

TxM1 TxM0 Timer Mode Descrizione
0 0 0 Timer a 13-bit
0 1 1 Timer a 16-bit
1 0 2 Timer a 8-bit con auto-reload
1 1 3 Timer in splitted mode

Modo a 13-bit (modo 0)

Il modo "0" corrisponde al funzionamento del timer a 13 bit. Questo e' un retaggio del passato che e' stato mantenuto per avere la compatibilita' con il suo predecessore: l'8048.
Generalmente questo modo di funzionamento non viene utilizzato in nuovi sviluppi.

In questa modalita' di funzionamento, TLx contera' da 0 a 31. Quando TLx viene incrementato da 31, esso si resettera' e incrementera' THx. Percio' effettivamente, soltanto 13 bit del timer a 2 byte sono utlizzati: i bit 0-4 di TLx e i bit 0-7 di THx. Questo significa anche in definitiva che il timer puo' contare solo fino ad un valore pari a 8192. Se caricate il timer con il valore 0, esso andra' in overflow e quindi a 0 dopo 8192 cicli macchina.

Modo a 16-bit (modo 1)

Il modo "1" corrisponde al funzionamento del timer a 16 bit. Questo e' un modo molto utilizzato. TLx e' incrementato da 0 a 255. All'overflow di TLx, THx viene incrementato di 1. Tenuto conto che e' un timer a 16 bit, esso puo' assumere 65536 valori distinti. Se caricate il timer con il valore 0, esso andra' in overflow e quindi a 0, dopo 65536 cicli macchina.

Modo a 8-bit (modo 2)

Il modo "2" corrisponde al funzionamento del timer ad 8 bit con auto-reload. Quando il timer e' configurato in modo 2, THx contiene il valore che deve essere caricato in TLx quando va in overflow. TLx inizia a contatore in avanti. Quando raggiunge 255 e viene ulteriormente incrementato invece di tornare a 0 (come nei modi 0 e 1), assume il valore caricato in THx.

Se per esempio TH0 contiene il valore FDh e TL0 il valore FEh il conteggio procedera' nella seguente maniera:

Ciclo Macchina Valore di TH0 Valore di TL0
1 FDh FEh
2 FDh FFh
3 FDh FDh
4 FDh FEh
5 FDh FFh
6 FDh FDh
7 FDh FEh

Notate che il valore di TH0 non cambia mai e TL0 viene incrementato. Una volta raggiunto un valore pari a FFh, al successivo ciclo macchina TL0 assume lo stesso valore di TH0.

Ma qual'e' il vantaggio di usare tale modo di funzionamento? Ipotizziamo che vogliate che il timer assuma dei valori compresi tra 200 e 255. Nel modo 0 e 1 siete costretti a verificare da programma quando il timer va in overflow e quindi settarlo al valore 200. Questo lavoro richiede tempo prezioso e non vi garantisce un elevata accuratezza a meno che non vogliate rimanere a controllare solo lo stato del timer. Quando usate il modo 2, non dovete preoccuparvi di tutto questo, poiche' l'hardware del microcontrollore lo fara' automaticamente per voi.

Il modo 2 e' usato molto spesso per generare il baud rate. Ne parleremo piu' approfondidamente nel capitolo dedicato alla Comunicazione Seriale.

Modo Split (modo 3)

Il modo "3" e' il cosidetto modo a timer separati. Quando il timer 0 e' configurato per lavorare in questa modalita', si trasforma in due timer a 8 bit separati. Per capirci meglio, il timer 0 diventa TL0 e il timer 1 diventa TH0. Ambedue i timer contano da 0 a 255 e dopo l'overflow tornano a 0. Tutti i bit relativi al Timer 1 vengono assegnati a TH0.

Una volta che il Timer 0 e' configurato in modo split, il vero Timer 1 (cioe' TH1 e TL1) puo' essere configurato nei modi 0, 1, 2 come sempre, ma non puo' essere abilitato/disabilitato poiche' i suoi bit di controllo sono assegnati a TH0. Allora, il vero Timer 1 funzionera' in maniera libera e si incrementera' ad ogni ciclo macchina senza condizioni.

L'unico vero uso di questa modalita' e' quello per cui sia necessario avere due timer separati e, in aggiunta, un generatore di baud rate. In questo caso il Timer 1 viene utilizzato come baud generator e i due registri TH0 e TL0 come se fossero due timer separati.

Il registro TCON

Come ultimo c'e' un altro registro SFR che controlla i due timer e fornisce importanti informazioni sul loro funzionamento. Il registro TCON ha la seguente struttura:

TCON (88h) SFR
Bit Nome Indirizzo a Bit Spiegazione della funzione Timer
7 TF1 8Fh Timer 1 Overflow. Questo bit e'settato quando il timer 1 e' andato in overflow 1
6 TR1 8Eh Timer 1 Run. Quando questo bit viene settato il timer 1 e' abilitato, altrimenti e' fermo. 1
5 TF0 8Dh Timer 0 Overflow.Questo bit e'settato quando il timer 0 e' andato in overflow 0
4 TR0 8Ch Timer 0 Run. Quando questo bit viene settato il timer 0 e' abilitato, altrimenti e' fermo. 0

Come potete notare, abbiamo definito solo 4 degli 8 bit. Cio' e' dovuto al fatto che gli altri 4 bit del registro TCON non hanno nulla a che vedere con i timer. Essi sono relativi agli interrupt e quindi ne discuteremo nel capitolo dedicato a questo argomento.

Nella tabella precedente, c'e' un ulteriore colonna che riguarda l'indirizzo a bit. E' stata riportata perche' tale registro puo' essere indirizzato a bit, cio' vuol dire che, se vogliamo settare il bit TF1, che e' il bit piu' significativo di TCON, potremmo eseguire la seguente istruzione:

oppure, visto che il registro e' indirizzabile a bit, potremo piu' semplicemente eseguire quest'altra istruzione: In quest'ultimo caso abbiamo il vantaggio di settare il bit piu' significativo di TCON senza cambiare il valore degli altri bit del registro. Generalmente infatti, quando si abilita o disabilita un timer, non si vuole modificare il valore degli altri bit di TCON, allora e' possibile sfruttare il fatto che tale registro e' indirizzabile a bit.

Inizializzare un timer

Ora che conoscaiamo tutti i registri SFR relativi ai timer, siamo in grado di scrivere il codice per inizializzare un timer e farlo partire.

Come vi ricordate, bisogna per prima cosa decidere in quale modalita' il timer verra' utilizzato. In questo caso scegliamo un modo a 16 bit che non ha alcuna dipendenza da pin esterni.

Dobbiamo per prima cosa, inizializzare il registro TMOD. Se lavoriamo con il timer 0 useremo i 4 bit meno significativi di TMOD. I primi due bit GATE0 e C/T0 vanno ambedue posti a 0 poiche' vogliamo che il timer sia indipendendente da pin esterni. Il modo a 16-bit corrisponde al modo "1" e quindi dobbiamo: resettare T0M1 e settare T0M0. In breve l'unico bit da settare e' il bit 0 di TMOD. Per inizializzare il timer eseguiremo la seguente istruzione:

Il timer 0 e' ora configurato per funzionare a 16 bit, ma e' disabilitato. Per attivarlo dobbiamo settare il bit TR0. Cio' puo' essere fatto mediante la seguente istruzione: Non appena verranno eseguite le precedenti due istruzioni, il timer 0 iniziera' a contare in avanti incrementando il suo valore ad ogni ciclo macchina (ogni 12 impulsi di clock).

Leggere lo stato di un Timer

Ci sono due maniere per leggere lo stato di un timer a 16-bit in funzione della specifica applicazione. Voi potete o leggere il valore attuale a 16 bit oppure stabilire soltanto se il timer e' andato in overflow.

Legger il valore del timer

Se il timer e' configurato nel modo ad 8 bit, sia nel modo con auto-reload che in split mode, la lettura del valore e' effettuata semplicemente leggendo il byte corrispondente al timer che state usando.

Quando pero' state usando il modo a 13 o 16 bit la lettura e' leggermente piu' complessa. Cio' e' dovuto al fatto che mentre state leggendo il valore del byte meno significativo del timer e questo valore e' pari a 255, potreste leggere un valore errato del byte piu' significativo che, in quel momento si sta incrementando. Per esempio se lo stato del timer e' 14/255 (byte alto = 14, byte basso = 255), potremmo leggere il valore 15/255 nel momento in cui il timer sta cambiando di stato al valore 15/0.

Quale potrebbe essere la soluzione? In verita' essa non e' molto complessa. Dovreste leggere prima il byte alto, poi quello basso e rileggere nuovamente il byte alto confrontandolo con il valore letto precedentemente. Sei i due valori coincidono, il timer non ha cambiato stato durante la lettura e quindi il valore letto e' utile, altrimenti va ripetuto il ciclo precedente. Il codice sara' del tipo:

Un altra maniera piu' semplice e' quella di congelare momentaneamente il timer (es. CLR TR0), leggere il valore del timer, e riabilitare subito il timer fermato (es. SETB TR0). In questo caso, essendo il timer fermo durante la lettura, non servono particolari precauzioni nella lettura. Ovviamente questo implica che il timer perdera' alcuni colpi di ciclo macchina. Dipende allora dalla specifica applicazione se cio' sia o meno tollerabile.

Rivelare l'Overflow di un timer

In alcuni casi e' sufficiente conoscere soltanto se il timer e' stato resettato o meno. Questo quando non interessa conoscere il valore del timer ma solo sapere se il timer ha raggiunto il massimo del conteggio (overflow). Una volta che il timer raggiunge il valore di 255, il microcontrollore lo riporta automaticamente a zero e setta il bit TFx corrispondente nel registro TCON. Per cui quando il bit TF0 e' settato vuol dire che il timer 0 ha raggiunto l'overflow e alla stessa maniera se TF1 e' settato vuol dire che il timer 1 e' tornato a zero.

Possiamo usare questo approccio per costringere il programma ad eseguire delle operazioni ad intervalli di tempo prestabiliti. Riprendiamo pure l'esempio precedente nel quale era richiesto il conteggio da 0 a 46.079 per un periodo di un ventesimo di secondo. Se vogliamo impostare un tempo pari a quello indicato, tenuto conto che il timer segnala l'overflow quando passa per lo zero, dobbiamo settare il timer ad un valore pari a 65.536 meno 46.079, cioe' 19.457. Quindi per eseguire una pausa di un ventesimo di secondo usiamo il seguente frammento di codice:

Il simbolo "$" viene usato nella maggior parte degli assembler per indicare l'indirizzo dell'istruzione in corso. Per cui, finche' il flag TF0 non e' settato e quindi il timer non e' andato in overflow, il programma rimane in loop nell'istruzione in corso. Al termine del ventesimo di secondo, il timer tornera' a zero settando il flag e a quel punto il programma uscira' fuori dal loop.

Misurare la durata di un evento

L'8051 dispone di un altro utile modo che puo' essere usato per misurare la durata di un evento.

Facciamo un esempio: vogliamo risparmiare energia in un ufficio e vogliamo conoscere quanto tempo una lampada rimane accesa durante la giornata. Quando la lampada e' accesa vogliamo conteggiare il tempo. Quando la lampada e' spento fermiamo il conteggio. Una possibile soluzione e' quella di collegare lo stato dell'interruttore ad un pin del micro. A questo punto il programma deve continuamente monitorare lo stato del pin utilizzato e attivare o disattivare il timer in funzione dello stato del pin stesso. Anche se il sistema funziona correttamente, In realta', l'8051 fornisce un metodo piu' semplice di quello appena prospettato.

Se diamo un'occhiata al registro TMOD, c'e' un bit chiamato GATE0. Finora abbiamo lasciato sempre il bit a zero perche' volevano che il timer continuasse a girare indipendentemente dallo stato dei fili esterni. Adesso pero', tale capacita' ci torna utile. Tutto quello che serve e' di collegare lo stato dell'interruttore al pin INT0 (P3.2) dell'8051 e settare il bit GATE0. A questo punto, il timer eseguira' il conteggio solo quando il pin P3.2 si trovera' nello stato alto (interruttore on, luce accesa). Quando il pin P3.2 si trovera' nello stato basso (interruttore off, luce spenta) il timer si blocchera' automaticamente.

USARE I TIMER COME CONTATORI DI EVENTI

Finora abbiamo discusso di come un timer possa tener traccia del tempo trascorso, ma l'8051 ci permette di usare il timer come contatore di eventi.

Quando puo' essere utile questa funzione? Supponiamo di avere un sensore nel mezzo di una strada che invia un impulso ogni volta che passa un'auto. Questo potrebbe essere utilizzato per misurare l'intensita' del traffico su quella strada. Potremmo allora collegare il sensore ad un pin di I/O dell'8051 e monitorare lo stato del pin per contare il numero degli impulsi. Fare questo non e' eccessivamente difficile, ma richiede l'utilizzo di un po' di codice. Supponiamo per esempio di aver collegato il sensore al pin P1.0; il codice che conta il numero di auto che passano sara' del tipo:

Come potete osservare servono soltanto tre linee di codice. Ma che succede se dobbiamo fare qualche altra cosa nello stesso tempo, visto che il programma rimane quasi sempre fermo in loop sull'istruzione JNB? Certamente e' possibile trovare una soluzione, ma questa renderebbe il codice grande, complesso e poco elegante.

Fortunamente l'8051 ci fornisce un'alternativa per usare il timer come contatore di eventi senza doverci preoccupare di come realizzarlo in software. E' estremamente semplice e indolore: basta configurare un bit addizionale.

Per esempio vogliamo utilizzare il Timer 0 per conteggiare il numero delle auto che transitano. Allora, se diamo uno sguardo al registri TCON possiamo notare la presenza di un bit chiamato C/T0, bit 2 di TCON (TCON.2). Se tale bit e' settato, il timer 0, invece di incrementare se stesso ad ogni ciclo macchina, effettuera' il monitoring della linea P3.4. In questa maniera incrementera' se stesso ogni volta che la linea passa dal valore alto a quello basso. A questo punto e' sufficiente collegare il sensore sulla linea P3.4 e configurare il registro TCON opportunamente.

E' importante pero' notare che l'8051 controlla lo stato della linea P3.4 una volta per ciclo macchina. Questo significa che, se la frequenza con la quale cambia lo stato del pin e' troppo elevata, l'8051 non riuscira' piu' a contare il numero di eventi in maniera corretta. Piu' precisamente, l'8051 riesce a contare un numero di eventi ad un massimo di un ventiquattresimo della frequenza di clock. Cio' vuol dire che, se per esempio usiamo un quarzo che oscilla a 12 MHz, esso riesce a conteggiare fino a 500.000 eventi al secondo (12 MHz * 1/24 = 500.000).



^ INDICE
< Informazioni a basso livello
> Porta Seriale

(C) Copyright 1997, 1998 by Vault Information Services. All Rights Reserved.
Le informazioni sono fornite senza alcuna garanzia. Per favore vedi dettagli.
Contattaci per l'uso e/o il permesso di divulgazione di questo corso.
Traduzione italiana di: Sergio Salvitti
1