PrettyPrint

lunedì 6 settembre 2010

Timer AVR in 5min: 6) Generare forme d'onda PWM (a correzione fase e freq.)

Ultima revisione: 30/Apr/2012 


< Timer AVR in 5min: 5) Gen. forme d'onda PWM (fast) Timer AVR in 5min: 7) Un foglio di calcolo >
pwm2.zip



Un secondo modo di generare segnali a larghezza di impulso variabile, con il Timer1, è quello "a correzione di frequenza e di fase". Questa modalità, a differenza di quella "fast", al variare del duty cicle dell'onda generata mantiene immutata non solo la frequenza del segnale, ma anche la fase; questa caratteristica è dunque da preferire per il controllo di motori.

In questa modalità il timer conta in avanti fra 0 e il valore massimo N. Raggiunto il massimo valore N continua a contare, ma indietro, verso lo 0, per poi ricominciare di nuovo il conteggio verso N e così via, periodicamente, di periodo T.


Dunque, l'andamento nel tempo del dato contenuto del registro del timer1 (TCNT1) assume una forma triangolare simmetrica, piuttosto che a rampa come nella modalità PWM fast, come schematizzato nella figura successiva:



Il fronte di salita dell'onda triangolare descrive il conteggio in avanti (fra il numero minimo 0 e il numero massimo N), mentre quello di discesa rappresenta il conteggio in direzione opposta (fra N e 0).
 
La forma d'onda uscita rettangolare sopra raffigurata si riferisce alla modalità non invertente: in questo modo di funzionamento, la commutazione a 0V del pin OC1A avviene sul confronto nel periodo del conteggio in avanti, mentre la commutazione al livello alto VH dello stesso avviene sul confronto nel  periodo del conteggio in discesa.


Si nota che il periodo del segnale generato (Ts) è il doppio del periodo T che si ottiene in corrispondenza di N e di questo ne dovremmo tenere conto nei calcoli successivi


Si hanno a disposizione fino a due canali PWM: A (pin OC1A) e B (pin OC1B), quindi possiamo avere due forme d'onda con duty cicle diversi, ma ovviamente entrambe aventi lo stesso periodo dal momento che condividono lo stesso timer.


La commutazione avviene al successivo impulso di clock dopo l'avvenuta uguaglianza fra il valore contenuto nel registro TCNT1 e il valore contenuto nel registro di confronto OCR1A (ovvero OCR1B se stiamo usando il secondo canale B), quando il valore massimo N è stato definito nel registro ICR1  (è possibile definire anche il valore massimo in OCR1A, v. datasheet).

L'aggiornamento del registro di confronto (es. OCR1A) in questo modo di funzionamento avviene sempre in corrispondenza del successivo zero del registro TCNT1; questo per evitare asimmetrie nel segnale rettangolare.

Il valore N, che stabilisce così la risoluzione del Timer1, può assumere un valore compreso fra 3 (risoluzione minima di 2bit) e 65535 (risoluzione massima di 16 bit).

Esempio applicativo

Supponiamo di avere un tipico problemino per il controllo di un servomotore:

Generare con il timer1 di un ATmega168, sul pin OC1A, il sequente segnale rettangolare (Ton=2ms) avente frequenza di 50Hz (T=20ms) usando come clock quello di sistema (fc=1MHz):



Calcoliamo innanzitutto un possibile valore di N in corrispondenza del quale abbiamo un periodo T pari alla metà dell'intero periodo (Ts=20ms) della forma d'onda.

Usando l'equazione già vista in precedenza, in cui pero dobbiamo tener conto che è T=Ts/2, avremo:










Per k=1, 8, 64, 256, 1024, e con Tc=0,25us (fc=1MHz), sostituendo, abbiamo:







k N
1 9.999
8 4.999
64 155,25
256 38,06
1024 28,77


Preferiamo k=1 (nessun prescaler) e quindi N=9999.

Individuiamo ora il numero NA in corrispondenza del quale si deve avere il confronto e quindi la successiva commutazione dell'uscita.

Come si vede dal primo diagramma temporale, fra 0 e NA corrisponde una differenza di tempo pari a Ton/2, dunque:



sostituendo, abbiamo NA=999


Scriviamo il programma

Abbiamo tutti i dati per scrivere un possibile pseudocodice per la configurazione del solo timer

  1. Imposta la modalità PWM a correzione di fase e di frequenza (e valore N definito in ICR1)
  2. Carica N=9999 nel registro ICR1;
  3. Carica NA=999 nel registro OCR1A;
  4. Imposta la polarità della forma d'onda: non invertita;
  5. Imposta prescaler :1 (nessun prescaling, k=1)

Come noto, l'istruzione di definizione del prescaler va sempre messa per ultima, in quanto dopo la sua esecuzione viene avviato il timer.

Vediamo come codificare i vari passi in GNU AVR-libc:

1. Imposta la modalità PWM a correzione di fase e di frequenza (e valore N definito in ICR1)

Per quanto riguarda l'impostazione della modalità PWM a correzione di fase e di frequenza, con valore massimo N (TOP secondo la convenzione del datasheet) definito in ICR1, basta guardare la Tabella 16-4 del datasheet dell'ATmega168:


 
Il modo è il numero 8, per il quale bisogna attivare il solo bit WGM13 del registro B di configurazione del timer1. In termini di istruzione :

TCCR1B |= (1 << WGM13); // PWM a corr. di fase e freq. con ICR1=N (Tab.16-4) 


2. Carica N=9999 nel registro ICR1
 
All'istruzione  di caricamento:

ICR1 = N;

dobbiamo far precedere la definizione dell'etichetta simbolica N, sempre opportunamente commentata:

#define N 9999   // @ Ts=20ms , fc=1MHz (:1)

 
3. Carica NA=999 nel registro OCR1A

OCR1A = NA; 

#define N 999   // @ Ton=2ms , fc=1MHz (:1)

 
4. Imposta la polarità della forma d'onda: non invertita

La forma d'onda desiderata deve essere coerente con quella di definizione di duty cicle, ossia durante il tempo Ton l'uscita deve essere a livello alto (es. +5V) e durante il tempo Toff l'uscita deve essere a livello 0V

La Tabella 16-3 del d/sheet definisce il comportamento dei pin OC1A e OC1B allorquando il valore del registro OCR1A (per OC1A) e/o del registro OCR1B (per OC1B) eguaglia quello del registro del timer TCNT1, attraverso la combinazione dei bit COM1A1/COM1A0 (per configurare la forma d'onda sul pin OC1A) e COM1B1/COM1B0 (per configurare la forma d'onda sul pin OC1B) del registro TCCR1A:



Dunque, volendo una forma d'onda non invertita, sul solo pin OC1A, il codice sarà:

TCCR1A |= (1 << COM1A1); // Forma d'onda non inv. (Tab.16-3)


5. Imposta prescaler :1 (nessun prescaling, k=1) 

Per impostare un prescaler unitario (ossia nessun prescaler) bisogna impostare il solo bit CS10 del registro di configurazione B, come indicato nella tabella 16-5 del d/sheet:




TCCR1B |= (1 << CS10); // Nessun prescaler (Tab.16-5) 



Combinando fra loro tutti i vari pezzi  e ricordandosi di impostare il pin PB1/OC1A come uscita, siamo in grado di stilare il sorgente completo:

#include <avr/io.h>

#define N = 9999;  // @ Ts=20ms , fc=1MHz (:1)
#define NA = 999;  // @ Ton=2ms , fc=1MHz (:1)

int main (void)

{

  DDRB |= (1 << PB1);  // imposta pin PB1(OC1A) come uscita
  TCCR1B |= (1 << WGM13); // PWM a corr. di fase e freq. con ICR1=N (Tab.16-4) 
  ICR1 = N;  // Conta da 0 fino a N 
  OCR1A = NA;  // Commuta pin OC1A, quando TCNT1=NA
  TCCR1A |= (1 << COM1A1);  // Forma d'onda non inv. (Tab.16-3)
  TCCR1B |= (1 << CS10); // Nessun prescaler (Tab.16-5)

  while(1)

  {
    /* il compito viene svolto completamente via hardware dal Timer1.. */
  }

}


Simulazione con VMLAB

I risultati della simulazione con VMLAB, sono riportati di seguito.

La misura di Ton, come differenza tra i tempi misurati dai due cursori:




E infine, la misura della frequenza, che conferma Ts=20ms (ovvero fs=50Hz):




Nel file pwm2.zip sono contenuti il sorgente main.c, i file makefile e config e il file progetto pwm2.prj per VMLAB.





I contenuti di questo documento, sono stati resi possibili grazie ai seguenti strumenti gratuiti:
- Datasheet completo dell'ATmega48/88/168/328
- LaTex online equation editor
- FidoCADJ (disegno grafici)
- Google Code Prettify (sorgenti con sintassi evidenziata)
- Libreoffice Calc (calcolo valori prescaler ris. fissa)



< Timer AVR in 5min: 5) Gen. forme d'onda PWM (fast) Timer AVR in 5min: 7) Un foglio di calcolo >

domenica 5 settembre 2010

Timer AVR in 5min: 5) Gen. forme d'onda PWM (fast)

< Timer AVR in 5min: 4) Generare forme d'onda con CTC Timer AVR in 5min: 6) Generare forme d'onda PWM (a correzione fase e freq.) >


Ultima revisione: 26/Apr/2012 

pwm1.zip


La modalità CTC consente di generare forme d'onda rettangolari, ma soltanto simmetriche (tempo di on uguale a quello di off ossia duty cicle del 50%).

Forme d'onda rettangolari con duty cicle diverso dal 50% possono essere generate con un'altra modalità di funzionamento del timer: la PWM (Pulse Width Modulation: modulazione a larghezza d'impulso).

Sul Timer1 dell'ATmega8 si possono usare tre tipologie di PWM:

1) Fast
2) A correzione di fase
3) A correzione di fase e di frequenza.

La 1) è la più veloce, consente di raggiungere frequenze più alte delle restanti due; al variare del duty cicle il periodo (ovvero la frequenza) rimane costante, ma varia la fase;
la 2) e la 3), invece, non comportano una variazione della fase al variare del duty cicle e dunque si prestano per applicazioni quali il pilotaggio di motori, laddove non sono richieste elevate frequenze di funzionamento ma la fase deve mantenersi costante.

Un possibile compito, che vede impiegata la FastPWM, potrebbe essere il seguente:

Generare, con il timer1 da 16bit, il sequente segnale rettangolare (d.c.=25%) avente frequenza di 1 kHz, usando come clock quello di sistema (fc=4MHz):

Nella modalità Fast PWM, come già detto, il periodo Ts rimane costante al variare del duty cicle ed è determinato dalla risoluzione del timer.

La risoluzione può essere scelta fra alcuni valori fissi (8bit, 9 bit o 10 bit) oppure variabile, da 2 bit fino alla risoluzione massima del timer1: 16bit.

Nella risoluzione fissa, il timer dunque conta da 0 fino a un numero N pari a 2n-1 :

N=255 (8bit),
N=511 (9bit)
N=1023 (10bit)

La durata dell'intero periodo Ts dipende da questi valori, dal periodo di clock, dal prescaler, secondo la nota formula già vista:



Con la nostra frequenza di clock fc di 4MHz (Tc=0,25us), per i vari valori di prescaler, si ottengono i seguenti valori di Ts (espressi in ms) per le varie risoluzioni fisse del timer:


Come si può facilmente constatare, non abbiamo nessun valore di Ts=1ms.

Solo con N=511 (9bit) e prescaler :8 ci avviciniamo a 1ms (1,024ms). Se non è richiesta una precisione spinta, potremmo allora "accontentarci" di un valore praticamente uguale a quello assegnato e configurare il timer1 in modo FastPWM con risoluzione fissa da 9bit.

Supponendo, invece, di volere esattamente un valore del periodo del segnale pari a 1ms, dobbiamo allora ricorrere alla risoluzione variabile. Impostando il modo PWM con risoluzione variabile bisogna determinare una risoluzione compresa fra 2 bit (quindi il timer conta fino a N=3) e quella massima consentita dal timer: 16bit nel caso del Timer1 (e quindi conta fino a N=65535).

Quindi, in definitiva, se impostiamo la risoluzione variabile il valore N può essere compreso fra 3 e 65535.

Quale sia un possibile valore di N per avere Ts=1ms, lo ricaviamo dalla formula inversa:



Per k=1, 8, 64, 256, 1024, e con Ts=0,25 us (fc=4MHz)ricaviamo:








k N
1 3.999
8 499
64 61,5
256 14,63
1024 2,906



Sono utilizzabili solo i prescaler 1 e 8, perché danno come risultati dei numeri, come appena detto, compresi fra 3 e 65535.

Usiamo k=1 (nessun prescaler) e dunque N=3999

Tale numero N va caricato nel registro ICR1

Definito il numero necessario per avere l'intero periodo Ts, rimane ora da determinare il numero (compreso nel nostro caso fra 0 e 3999) in corrispondenza del quale si ha la commutazione del pin (OC1A oppure OC1B) quando Ton=0,25ms.

Se vogliamo generare la nostra forma d'onda usando il canale A, dunque segnale in uscita sul pin OC1A, caricheremo questo valore nel registro OCR1A; viceversa, se vogliamo usare il canale B, dunque forma d'onda sul pin OC1B, caricheremo questo valore nel registro OCR1B.

Supponendo di volere usare il canale A (pin OC1A), la situazione è raffigurata di seguito:



Finché il dato del Timer1 (ovvero il valore del registro TCNT1) è inferiore al numero NA, l'uscita OC1A assume valore alto (es. 5V). Allorquando il valore di TCNT1 eguaglia quello contenuto nel registro OCR1A, al successivo impulso di clock, si ha una commutazione del pin OC1A a 0V.

Infine, al raggiungimento del successivo valore N, il pin OC1A commuta a VH (es. 5V)

Il registro di conffronto (OCR1A nel nostro caso) viene sincronizzato a ogni successivo valore 0 del registro TCNT1, questo per evitare asimmetrie nella forma d'onda generata.

Da precisare che questa è la modalità non invertente. E' possibile configurare la generazione della forma d'onda anche in modalità invertente, per la quale, ovviamente, i livelli di commutazione sono invertiti e quindi è invertita anche la forma d'onda.


Resta da determinare il numero NA in corrispondenza del quale si ha che il tempo trascorso dall'ultimo reset del Timer1 è proprio Ton.

E' molto semplice.

Se per impiegare un tempo Ts, il timer deve contare fino a N, evidentemente per un tempo Ton più piccolo il timer dovrà contare fino a un numero NA < N:



Dunque, per calcolare Ton:


mentre per il periodo Ts, già si è visto che:



Facendo il  rapporto Ton/Ts ricaviamo così il duty cicle e da qui il numero NA:


Nel nostro caso, essendo d.c.=0,25 e N=3999, ricaviamo NA=999

Abbiamo tutti i dati per scrivere il software, partendo dal seguente pseudocodice:

  1. Carica NA=999 nel registro OCR1A;
  2. Imposta il modo FastPWM a risoluzione variabile (e valore N definito nel registro ICR1);
  3. Imposta forma d'onda non invertita;
  4. Carica N=3999 nel registro ICR1;
  5. Imposta prescaler :1;


1. Carica NA=999 nel registro OCR1A;


Per il caricamento di questo valore nel registro OCR1A, conviene definire precedentemente una label NA corrispondente al numero 999, con il solito commento indicante il dyty cicle/Ton che si ottiene in corrispondenza di esso:


#define NA 999  // d.c. 25% (Ton=.25ms) @fc=4MHz :1

OCR1A = NA;


2. Imposta il modo FastPWM a risoluzione variabile (e valore N definito nel registro ICR1)



Con l'ausilio del datasheet dell'ATmega168, si va a vedere su quali registri di configurazione del timer1 agire e quali bit settare, per ottenere la modalità PWM desiderata.

Dalla tabella 16-4 del datasheet, si legge che la modalità Fast PWM con risoluzione variabile e con valore N ("TOP" secondo la convenzione del datasheet) specificato in ICR1, viene attuata attivando i bit WGM13, WGM12 e WGM11 (modo 14):







I bit WGM13 e WGM12 sono nel  registro B di configurazione del timer1 (TCCR1B):




mentre il restante bit WGM11 si trova nel  registro A di configurazione (TCCR1A):






Dunque scriveremo:

TCCR1B |= (1 << WGM13) | (1 << WGM12); 
TCCR1A |= (1 << WGM11);



3. Imposta forma d'onda non invertita


Vogliamo che la nostra forma d'onda sia non invertita, cioè così come quella corrispondente alla definizione di duty cicle (livello di tensione alto durante Ton, e livello 0V durante Toff) e per questo bisogna guardare la tabella 16-2, che ci dice come impostare i bit COM1A1 e COM1A0 (ovvero COM1B1 e COM1B0 se ci riferiamo all'uscita OC1B) del registro TCCR1A, per ottenere il comportamento desiderato dell'uscita, nella modalità Fast PWM, quando il timer raggiunge il numero contenuto nel registro di confronto OCR1A:







Quindi, attiveremo il solo bit COM1A1:

TCCR1A |= (1 << COM1A1); 

4. Carica N=3999 nel registro ICR1;


Per questa istruzione seguiamo lo stesso metodo usato per caricare il registro OCR1A, con etichetta e commento relativo al tempo Ts che si ottiene in corrispondenza di esso:

#define N 3999 // Ts=1ms @fc=4MHz :1


5. Imposta prescaler :1


Infine, l'impostazione del prescaler, che è decisa dalla terna di bit CS12, CS11 e CS10 del registro TCCR1B così come definito nella Tab. 16-5 del d/sheet:





Per avere un prescaler :1 (nessun prescaler) bisogna dunque attivare il solo bit CS10:

TCCR1B |= (1 << CS10);



Il sorgente definitivo è dunque:

#include <avr/io.h>

#define N 3999 // Ts=1ms @ fc=4MHz:1
#define NA 999 //  d.c.=25% (Ton=.25ms) @ fc=4MHz:1

int main (void)

{

  DDRB |= (1 << PB1);  // imposta pin PB1(OC1A) come uscita
  OCR1A = NA;  // carica OCR1A con il valore NA
  TCCR1B |= (1 << WGM13) | (1 << WGM12); // FastPWM con ICR1=N (Tab.16-4)
  TCCR1A |= (1 << COM1A1) | (1 << WGM11) ;  // Non inv. (Tab.16-2) | FastPWM con ICR1=N (Tab.16-4)
  ICR1 = N; // carica ICR1 con il valore N
  TCCR1B |= (1 << CS10); // Nessun prescaler (Tab.15-5)

  while(1)
  {
   /* il compito viene svolto completamente via hardware dal Timer1.. */
  }

}


SIMULAZIONE CON VMLAB

I risultati della simulazione sono riportati di seguito. In allegato, lo zip con tutti i file del progetto VMLAB. Per usare VMLAB sotto Linux seguire il relativo tutorial

Misura di Ton (0,25 ms)


Misura di Ts (1ms)






< Timer AVR in 5min: 4) Generare forme d'onda con CTC Timer AVR in 5min: 6) Generare forme d'onda PWM (a correzione fase e freq.) >





I contenuti di questo documento, sono stati resi possibili grazie ai seguenti strumenti gratuiti:
- LaTex online equation editor
- FidoCADJ (disegno grafici)
- Google Code Prettify (sorgenti con sintassi evidenziata)
- Libreoffice Calc (calcolo valori prescaler ris. fissa)



Ultima revisione: 26/Apr/2012