Programmazione AVR: Timer/Counter0 e PWM

Condividi:

In questo articolo tratteremo il modulo Timer0 dell’ Atmega328p e tutte le modalità di funzionamento del generatore di forme d’onda. Timer0 è un modulo composto da un contatore e da due comparatori ad 8 bit. Il segnale di ingresso per il conteggio può essere selezionato tra diverse sorgenti che sono il clock della mcu ed un segnale applicato al pin T0. Dispone di due uscite OC0A ed OC0B (Output Compare A e B) dipendenti dal relativo comparatore. Timer0 genera interrupt su rollover del contatore e su match con ciascuno dei due comparatori

Abilitazione del modulo

Il modulo è normalmente attivo, ma può essere disattivato impostando ad 1 il bit PRTIM0 del registro PRR ( Power Reduction Register )

Descrizione
Sorgente di clock

La sorgente di clock è selezionata tramite i valori dei bit CS[2:0] (Clock Source) del registro TCCR0B. Gli 8 possibili valori permettono scollegare la sorgente di clock arrestando il contatore, collegare il clock della mcu programmando la divisione del prescaler  ( 1, 8, 64, 256 e 1024 ), oppure collegare la sorgente di clock esterna presente sul pin T0 che deve comunque avere una frequenza inferiore a quella di clock della mcu

Counter Unit

Il cuore del modulo è il registro TCNT0 (Timer Counter 0) che viene azzerato, incrementato o decrementato in base alla modalità di funzionamento selezionata tramite i bit WGM0[2:0] (Waveform Generation Mode) dei registri TCCR0A e TCCR0B. C’è una connessione diretta tra la modalità di conteggio e la forma d’onda in uscita sui pin OC0A ed OC0B. L’overflow del contatore viene segnalato tramite il bit TOV0 che può essere utilizzata per generare l’ interrupt TIMER0_OVF_vect

Output Compare Unit

Il valore presente in TCNT0 viene confrontato ad ogni ciclo di clock con quello dei registri OCR0A e OCR0B (Output Compare 0 A e B). Quando i valori corrispondono viene segnalato tramite i flag OCF0A ed OCF0B (Output Compare Flag 0 A e B) che possono essere utilizzati per generare interrupt TIMER0_COMPA_vect e TIMER0_COMPB_vect

Compare Match Output Unit

Controlla come vengono impostate le uscite OC0A e OC0B tramite il generatore di forme d’onda quando si verifica la corrispondenza dei valori tra TCNT0 ed uno dei due registri OCR0A ed OCR0B. I bit di controllo sono contenuti nel registro TCCR0A e sono COM0A[1:0], COM0B[1:0]

Uscite

Il modulo Timer0 permette do generare dei segnali sui pin OC0A ed OC0B che devono essere impostati come uscite tramite i bit DDD6 e DDD5 del registro DDRD per poter leggere i segnali generati

Interrupt

Quando il contatore TCNT0 viene resettato, viene impostato il flag TOV0 e viene generato il segnale di interrupt TIMER0_OVF_vect

Quando si verifica il match tra TCNT0 e d il registro OCR0x, viene impostato il flag OCF0x e viene generato il segnale di interrupt TIMER0_COMPx_vect

Poichè in alcune modalità il contatore viene resettato sul match, non è possibile utilizzare l’interrupt generata da TOV0

I flag possono essere resettati scrivendoci un 1

Modalità di lavoro

La combinazione di Waveform Generation Mode ( WGM0[2:0] ) e Compare Output Mode ( COM0[A|B][1:0] ) definiscono il comportamento del Timer/counter e delle uscite OC0[A|B]

WGM02 WGM01 WGM00 Modalità
0 0 0 Normal Mode
0 0 1 PWM, Phase Correct
0 1 0 Clear Timer on Compare Match ( CTC )
0 1 1 Fast PWM
1 0 0
1 0 1 PWM, Phase Correct
1 1 0
1 1 1 Fast PWM

La frequenza dei segnali generati è funzione della sorgente di clock selezionata tramite i bit CS[2:0] ed ipotizzando che si tratti di quella relativa al clock interno, la identificheremo con f_{cs} = \frac{ f_{clk} }{ N } dove f_{clk} rappresenta la frequenza di clock della mcu ( 16 MHz ) ed N la divisione impostata sul prescaler. Utilizzeremo f_{cs} successivamente per effettuare dei calcoli

Normal mode

Questa è la modalità non PWM più semplice che viene selezionata impostando i bit WGM0[2:0] = 000. TCNT0 conta in avanti fino al valore 0xFF dopo di che viene resettato quindi la frequenza di conteggio è determinata solo dalla sorgente di clock selezionata. Quando il contatore viene resettato viene impostato il flag TOV0 (Timer  Overflow 0 ) generando l’interrupt. Vengono generate anche le interrupt sul match tra il TCNT0 e OCR0A e OCR0B

E’ possibile impostare i bit COM0A[1:0] con il valore 01 per far commutare l’uscita OC0A in corrispondenza del match con il valore contenuto in OCR0A, in modo da avere un’onda quadra in uscita alla frequenza di  f_{OC0A} = \frac{ f_{cs} }{ 512 } Hz

Facendo altrettanto con i bit COM0B[1:0] otteniamo un’onda quadra sull’uscita OC0B della stessa frequenza ma traslata rispetto a quella su OC0A di t = \frac{ OCR0B - OCR0A }{ f_{cs} } secondi

Combinando i due segnali con una porta XOR esterna è possibile ottenere un segnale PWM il cui duty cycle dipende dalla differenza tra i valori di OCR0B ed OCR0A D_{PWM} = \frac{ \left | OCR0B - OCR0A \right | }{ 256 } \%

I due segnali possono essere combinati per ottenere altre forme d’onda

Clear Timer on Compare Match ( CTC )

Questa è la seconda modalità non PWM e viene selezionata da WGM0[2:0] = 010. A differenza della modalità normal mode,  TCNT0 viene azzerato ogni volta che si verifica il match con il registro OCR0A che quindi determina la risoluzione del contatore e la frequenza con cui viene resettato

Per generare un segnale su OC0A occorre impostare COM0A[1:0] = 01 per alternare lo stato logico dell’ uscita OC0A ogni volta che si verifica il match. Come nella modalità normal mode i tempi Ton e Toff del segnale sono uguali e dipendono dalla sorgente di clock selezionata ma in questo caso dipendono anche dal valore di OCR0A. La frequenza del segnale generato in questa modalità vale f_{OC0A} = \frac{ f_{cs} }{ 2 ( 1 + OCR0A ) } Hz

E’ possibile utilizzare una ISR per modificare il valore di OCR0A in modo da modificare i valori Ton e Toff del segnale, che al netto dei tempi necessari ad eseguire il codice nelle ISR sono così calcolati: t = \frac{ 2 ( 1 + OCR0A ) }{ f_{cs}} secondi

Se nella ISR vengono scritti valori troppo bassi in OCR0A, durante l’esecuzione del codice il contatore può superare il valore da confrontare con il risultato di saltare il match. Questo rischio viene ridotto utilizzando il prescaler per abbassare la frequenza con cui viene incrementato TCNT0

Solo il match con OCR0A resetta il contatore, ma come in Normal Mode si può utilizzare il match con il registro OCR0B per farcommutare OC0B tramite la combinazione dei flag COM0B[1:0]=01. In questo caso il match con OCR0B avviene solo se il valore è minore o uguale a OCR0A perchè in caso contrario il contatore verrebbe resettato sempre prima di raggiungere OCR0B. In questo modo si ottengono due segnali ad onda quadra su OC0A ed OC0B con duty cycle pari al 50%, della stessa frequenza determinata da OCR0A e traslati nel tempo di  t = \frac{ 2 ( OCR0B - OCR0A ) }{ f_{cs} } che possono essere utilizzati in combinazione con componenti esterni per generare i segnali desiderati. Nel caso di un segnale PWM ottenuto con una XOR tra OC0A ed OC0B, il duty cycle vale D_{PWM} = \frac{ \left | OCR0B - OCR0A \right | }{ OCR0A } \% ed è evidente l’influenza di OCR0A sulla risoluzione del segnale

In quersta modalità non viene utilizzato il flag TOV0 e non viene generata l’interrupt TIMER0_OVF_vect

Fast Pulse Width Modulation

Questa è la prima modalità PWM, il contatore viene incrementato da 0 al valore massimo e poi riportato a 0. Il valore massimo è definito dai bit WGM0[2:0] e vale 0xFF quando bit WGM0[2:0] = 011 mentre vale OCR0A quando bit WGM0[2:0] = 111

Quando il valore del contatore corrisponde a quello di OCR0A l’uscita OC0A viene impostata in accordo con i valori dei bit COM0A[1:0] quindi tralasciando la combinazione 00 che disabilita l’uscita ci sono 3 diverse combinazioni che generano un segnale

WGM0[2:0] COM0A[1:0] OC0A
Match contatore
OC0A
Clear
Contatore
Descrizione
011 10 0 1 PWM normale
011 11 1 0 PWM Invertita
111 01 commuta Onda quadra

Solo con WGM0[2:0]=011 otteniamo dei segnali PWM, la cui frequenza vale
f_{OCnx} = \frac{ f_{cs} }{ 256 }

Con WGM0[2:0]=111 ed COM0A[1:0]=01 otteniamo il segnale ad onda quadra su OC0A

In questa modalità non è possibile utilizzare COM0B[1:0]=01 e l’uscita OC0B

Ogni volta che che il contatore raggiunge il valore massimo viene impostato il flag TOV0 che può essere utilizzato per generare una interrupt

Phase Correct Pulse Width Modulation

Questa è la seconda modalità PWM e differisce dalla precedente per il fatto che quando il contatore raggiunge il valore massimo viene decrementato fino al valore minimo invece che essere resettato, questo causa il dimezzamento della frequenza del segnale generato. Come nella Fast PWM, il valore massimo del contatore viene stabilito dalla combinazione dei bit WGM0[2:0] e vale 0xFF quando bit WGM0[2:0] = 001 mentre vale OCR0A quando bit WGM0[2:0] = 101. Il comportamento dell’uscita OC0A è analogo alla modalità Fast PWM ma la commutazione avviene sul match sia quando il contatore viene incrementato che quando viene decrementato. Anche qui ci sono 3 combinazioni utili:

WGM0[2:0] COM0A[1:0] OC0A
Match contatore in salita
OC0A
Match
Contatore in discesa
Descrizione
001 10 0 1 PWM normale
001 11 1 0 PWM Invertita
101 01 commuta Onda quadra

Solo con WGM0[2:0]=001 otteniamo dei segnali PWM, la cui frequenza vale f_{OCnx} = \frac{ f_{cs} }{ 510 }

Con WGM0[2:0]=101 ed COM0A[1:0]=01 otteniamo il segnale ad onda quadra su OC0A e vale tutto quanto detto per la modalità Fast PWM con WGM0[2:0]=111

In questa modalità non è possibile utilizzare COM0B[1:0]=01 e l’uscita OC0B

Ogni volta che che il contatore raggiunge il valore minimo viene impostato il flag TOV0 che può essere utilizzato per generare una interrupt

Registri
TCCR0A: Timer/Counter Control Register A
TCCR0A
COM0A1 COM0A0 COM0B1 COM0B0 WGM01 WGM00

I bit COM0A[1:0] e COM0B[1:0] (Compare Match Output Mode) selezionano il modo in cui le uscite OC0A e OC0B si comportano, secondo le modalità di funzionamento, quando il contatore raggiunge il valore massimo, minimo e viene azzerato.

Non PWM Mode – WGM0[2:0] = 000, 010
COM0A1 COM0A0 OC0A COM0B1 COM0A0 OC0B
0 0 Disconnesso 0 0 Disconnesso
0 1 Commuta su Match 0 1 Commuta su Match
1 0 0 su Match 1 0 0 su Match
1 1 1 su Match 1 1 1 su Match
Fast PWM Mode – WGM0[2:0] = 011, 111
COM0A1 COM0A0 OC0A COM0B1 COM0A0 OC0B
0 0 Disconnesso 0 0 Disconnesso
0 1 WGM02 = 0: Disconnesso
WGM02 = 1: Commuta su Match
0 1 Disconnesso
1 0 0 su Match
1 su TCNT0=0,
(non-inverting mode).
1 0 0 su Match
1 su TCNT0=0,
(non-inverting mode)
1 1 1 su Match
0 su TCNT0=0,
(inverting mode)
1 1 1 su Match
0 su TCNT0=0,
(inverting mode)
Phase Correct PWM Mode – WGM0[2:0] = 001, 101
COM0A1 COM0A0 OC0A COM0B1 COM0A0 OC0B
0 0 Disconnesso 0 0 Disconnesso
0 1 WGM02 = 0: Disconnesso
WGM02 = 1: Commuta su Match
0 1 Disconnesso
1 0 0 su Match quando TCNT0 conta in avanti
1 su Match quando TCNT0 conta in indietro
1 0 0 su Match quando TCNT0 conta in avanti
1 su Match quando TCNT0 conta in indietro
1 1 1 su Match quando TCNT0 conta in avanti
0 su Match quando TCNT0 conta in indietro
1 1 1 su Match quando TCNT0 conta in avanti
0 su Match quando TCNT0 conta in indietro

I bit WGM0[1:0] (Waveform Generation Mode), assieme al bit WGM02 del registro TCCR0B, selezionano la modalità di lavoro

TCCR0B – Timer/Counter Control Register B
TCCR0B
FOC0A FOC0B WGM02 CS02 CS01 CS00

I bit FOC0x (Force Output Compare) sono validi solo nelle modalità non PWM ed hanno la funzione di forzare un segnale di match. L’uscita OC0x corrispondente si comporterà secondo la modalità selezionata dai bit COM0x[1:0] come se si fosse trattato di un vero match tra TCNT0 ed OCR0x

Il bit WGM02, insieme a WGM0[1:0] del registro TCCR0A selezionano la modalità di lavoro

I bit CS0[2:0] selezionano la sorgente di clock con cui azionare l’incremento del contatore. La sorgente può essere disabilitata per fermare il contatore, il clock interno attraverso il prescaler oppure un clock esterno presente sul pin T0

CS2 CS1 CS0 Sorgente clock
0 0 0 Clock fermo
0 0 1 Clock interno ( prescaler divide per  1 )
0 1 0 Clock interno / 8
0 1 1 Clock interno / 64
1 0 0 Clock interno / 256
1 0 1 Clock interno / 1024
1 1 0 Clock esterno T0 sui fronti di discesa
1 1 1 Clock esterno T0 sui fronti di salita
TCNT0 – Timer/Counter Register
TCNT0

Questo registro contiene il valore del contatore che viene continuamente incrementato, decrementato o resettato tramite la commutazione della sorgente di clock selezionata e la modalità di lavoro selezionata

OCR0A – Output Compare Register A
OCR0A

Questo registro contiene il valore ad 8 bit con cui viene confrontato il contatore TCNT0. Un match tra i due registri può essere utilizzato per generare una interrupt oppure come sorgente per generare di funzioni d’onda sull’uscita OC0A

OCR0B – Output Compare Register B
OCR0B

Questo registro contiene il valore ad 8 bit con cui viene confrontato il contatore TCNT0. Un match tra i due registri può essere utilizzato per generare una interrupt oppure come sorgente per generare di funzioni d’onda sull’uscita OC0B

TIMSK0 – Timer/Counter Interrupt Mask Register
TIMSK0
OCIE0B OCIE0A TOIE0

I bit di questo registro mascherano le interrupt generate dagli eventi di match e di overflow del contatore.

OCIE0B (Output Compare Match B Interrupt Enable) impostato ad 1 abilita l’interrupt TIMER0_COMPB_vect quando avviene un match tra TCNT0 e OCR0B

OCIE0A (Output Compare Match A Interrupt Enable) impostato ad 1 abilita l’interrupt TIMER0_COMPA_vect quando avviene un match tra TCNT0 e OCR0A

TOIE0 (Timer/Counter0 Overflow Interrupt Enable) impostato ad 1 abilita l’interrupt TIMER1_OVF_vect quando avviene l’overflow ti TCNT0

TIFR0 – Timer/Counter 0 Interrupt Flag Register
TIFR0
OCF0B OCF0A TOV0

Questo registro contiene i flag delle rispettive interrupt. Sono impostati ad 1 per tutta l’esecuzione della relativa ISR e possono essere resettati scrivendoci un 1

PRR: Power Reduction Register
PRR
PRTWI PRTIM2 PRTIM0 PRTIM1 PRSPI PRUSART0 PRADC

L’unico bit che interessa TIMER0 è PRTIM1 che se viene impostato ad 1 disabilita il modulo

Esempi
Timer

In questo esempio realizziamo un timer da utilizzare in un ciclo di ritardo utilizzando Timer0 in normal mode con l’uscita OC0A disconnessa dal generatore di forme d’onda. Utilizziamo una ISR sul match con OCR0A in cui fermiamo il contatore. Attiviamo il timer facendo partire il contatore prima del loop di ritardo, nel loop testiamo che il contatore sia in funzione. Segnaliamo il tempo totale tra la partenza ed il match, al lordo dei cicli macchina necessari ad eseguire il codice frapposto, con due commutazioni del pin PD6.
Vogliamo impostare un ritardo di t_{rit} = 100 \mu s, con il clock a 16 MHz il contatore viene incrementato di 1 ogni t_{clk} = \frac{ 1 }{ 16 \times 10^6 } = 62.5 ns quindi dovremmo fermare il contatore al valore \frac{ t_{rit} }{ t_{clk} } = 1600 che è un valore troppo alto per un registro a 8 bit. Possiamo però impostare il prescaler ad esempio su una divisione di 64 volte, in questo caso il contatore viene incrementato ogni t_{pre} = t_{clk} \times 64 = 4 \mu s pertanto dovremmo fermare il contatore al valore \frac{ t_{rit} }{ t_{pre} } = 25. Poichè il valore 0x00 del contatore vale come un conteggio, inseriremo il valore 24

Il netto dei tempi di esecuzione del codice otteniamo il ritardo voluto

Normal mode con interrupt

In questo esempio utilizziamo le interrupt di overflow e di match con OCR0A per modificare lo stato del pin PD6 impostato come uscita. I limiti di questo generatore di PWM sono i tempi necessari all’esecuzione delle ISR che alterano il periodo ed il duty cicle del segnale

Con la soglia del comparatore impostata a 0x40 otteniamo questo segnale su PD6

Con la soglia del comparatore impostata a 0xA0 otteniamo questo segnale su PD6

Normal mode con OC0A e OC0B

In questo esempio generiamo due segnali ad onda quadra sulle uscite OC0A ed OC0B traslati tra loro dalla differenza dei due valori che fanno commutare le due uscite in tempi differenti. A parte la possibilità di impostare il prescaler non abbiamo alcun controllo sulla frequenza dei segnali che valgono f_{OC0x} = \frac{ f_{clk}}{ 512} } = \frac{ 16000000}{ 512 } } \approx 31.25 kHz e non vengono alterate dai tempi di esecuzione delle ISR come nell’esempio precedente. La combinazione di questi segnali in XOR può essere utilizzata per ottenere un segnale PWM

Questi sono i segnali generati

Clear Timer on Compare Match (CTC) Mode

Questo esempio è identico al precedente con l’unica differenza che la frequenza del segnale può essere controllata con il valore di OCR0A e vale f_{OC0A} = \frac{ f_{clk}}{ 2 N ( 1 + OCRnA ) } } = \frac{ 16000000}{ 2 ( 1 + 160) } } = \frac{ 16000000}{ 322 } } \approx 49.7 kHz. Questo valore però influisce anche sulla risoluzione del segnale PWM

Questi sono i segnali generati

Fast Pulse Width Modulation

In questo esempio mostriamo le 2 combinazioni relative alla modalità fast PWM con cui generiamo i segnali sulle uscite PD6 (OC0A) e PD5 (OC0B) impostando i bit WGM0[2:0] a 011. Inizializziamo OCR0A ed OCR0B per ottenere i duty cycle pari a D_{OC0A} = \frac{ OCR0A }{ 256 } = \frac{ A0}{ 256 } \approx 62.5 \% e D_{OC0B} = \frac{ OCR0B }{ 256 } = \frac{ 20}{ 256 } \approx 12.5 \%. La frequenza dei segnali vale f = \frac{ f_{cs} }{ 256 } = \frac{ 16000000 }{ 256 } \approx 62.5 kHz

Nel primo caso impostiamo i bit COM0A[1:0] e COM0B[1:0] con il valore 10 per ottenere due segnale PWM non invertiti

Nel secondo caso modifichiamo i bit COM0A[1:0] e COM0B[1:0] con il valore 11 per ottenere due segnale PWM invertiti

In entrambi i casi i due segnali risultano in fase

Phase Correct Pulse Width Modulation

In questo esempio mostriamo le 2 combinazioni relative alla modalità phase correct PWM con cui generiamo i segnali sulle uscite PD6 (OC0A) e PD5 (OC0B) impostando i bit WGM0[2:0] a 001. Per confronto utilizziamo gli stessi valori di OCR0A ed OCR0B degli esempi relativi alla modalità Fast PWM. La frequenza dei segnali vale f = \frac{ f_{cs} }{ 256 } = \frac{ 16000000 }{ 510} \approx 31.4 kHz ovvero dimezzata rispetto alla modalità Fast PWM

Nel primo caso impostiamo i bit COM0A[1:0] e COM0B[1:0] con il valore 10 per ottenere due segnale PWM non invertiti

Nel secondo caso modifichiamo i bit COM0A[1:0] e COM0B[1:0] con il valore 11 per ottenere due segnale PWM invertiti

La porzione di segnale in cui è allo stato logico 1 è centrata sul valore massimo di TCNT0