PrettyPrint

sabato 25 dicembre 2010

Timer AVR in 5min: 7) Un foglio di calcolo

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

Ultima revisione: 29/Apr/2012

TIMERS_ATMEGA168.ZIP



A conclusione della serie di post: "i timer AVR in cinque minuti" presento un semplice foglio elettronico (in formato OpenOffice.org Calc), che calcola i valori necessari per le configurazioni delle  modalità di funzionamento viste.

Una volta appreso il metodo per calcolare, con carta e penna, i valori di N (e di NA) da caricare nei registri, a seconda della modalità di funzionamento del timer, può tornare utile uno strumento di calcolo che agevoli tale lavoro.

Esso è valido per le mcu AVR ATmegaxx8 (ATmega88/168/328) e - al momento - per il solo Timer1 (16bit); può essere sicuramente fatto meglio, per cui gradirei ben volentieri migliorìe e soprattutto correzioni.

Vediamo come si usa.

Le celle con sfondo colorato sono quelle nelle quali l'utente deve immettere una variabile.

La prima variabile richiesta, per tutti i modi di funzionamento, è la frequenza di clock (fc) del sistema espressa in MHz, nella cella C3 (sfondo colore celeste):


Nella cella immediatamente inferiore comparirà il relativo periodo di clock (Tc) espresso in microsecondi.


MODO NORMALE

A sinistra compariranno i vari valori di tempo, per ciascun fattore di prescaler:




MODO CTC

Sarà richiesto il tempo desiderato T (cella F13, sfondo colore verde lime) espresso in secondi:



A sinistra comparirà una tabella con i valori di N calcolati per ogni fattore di prescaler. I valori che riportano la dicitura "N.R." in rosso, sono non utilizzabili in quanto non possono essere rappresentati nel registro del timer, perché sono maggiori di quello massimo (65535, ricordarsi del +1) oppure minori di zero:


I valori N non interi (cifra decimale diversa da zero) sono comunque riportati, lasciando dunque piena flessibilità di scelta, caso per caso, se approssimare il valore all'intero successivo o precedente.

MODO CTC GEN. FORME D'ONDA

Sarà qui richiesta la frequenza desiderata del segnale (fs), nella cella F3 di colore verde oliva, espressa in Hertz:


 A sinistra, verranno visualizzati i valori di N, per ciascun fattore di prescaler k:



Eventuali valori che riportano la dicitura "N.R." in rosso, sono non utilizzabili in quanto non possono essere rappresentati nel registro del timer, perché sono maggiori di quello massimo (65535, ricordarsi del +1) oppure minori di zero. Anche per questo modo di funzionamento i valori N non interi (cifra decimale diversa da zero) sono comunque riportati, lasciando dunque piena flessibilità di scelta se approssimare, o meno, per difetto o per eccesso all'intero immediatamente vicino.


MODO PWM FAST GEN. FORME D'ONDA

Per questa modalità c'è una prima tabella riportante tutte le frequenze fisse, che possono essere ricavate nelle singole modalità fisse (8, 9 e 10bit), per i diversi valori di prescaler (k):





Più sotto c'è una seconda tabella, che, invece, riporta i valori di N e NA necessari per generare un segnale rettangolare con una frequenza fs e duty cicle desiderati:


Valori che riportano la dicitura "N.R." in rosso, non sono utilizzabili in quanto o sono valori N che non possono essere rappresentati nel registro del timer, perché sono maggiori di quello massimo (65535, ricordarsi del +1) oppure perché minori di 3 (la risoluzione variabile minima è 2bit, quindi il valore minimo è 3 - cfr. Datasheet) oppure perché il valore NA è negativo.

I valori N e NA non interi (cifra decimale diversa da zero) sono comunque riportati, lasciando dunque piena flessibilità di scelta se approssimare, o meno, per difetto o per eccesso all'intero immediatamente vicino. 
Per popolare questa tabella ovviamente occorre definire la frequenza fs (in Hz) nella cella F41 (sfondo colore marrone chiaro) e il duty cicle (in percento) nella cella F42:



MODO PWM A CORR. DI FASE E FREQUENZA

Essendo questa modalità tipicamente usata per il controllo di motori, si è preferito far immettere il periodo del segnale Ts (piuttosto che la frequenza) e la durata della larghezza dell'impulso Ton (piuttosto che il duty cicle); questi due tempi vanno inseriti, rispettivamente, nelle celle F52 (sfondo colore magenta) e F53 (sfondo colore celeste) e devono essere espressi in ms:


Una volta definita questa coppia di dati, nella tabella a sinistra appariranno i valori di N e NA, a secondo del fattore di prescaler k:


Per quei valori di N e NA che riportano l'indicazione "N.R." vale lo stesso discorso fatto per il modo PWM Fast.

Buon divertimento!


TIMERS_ATMEGA168.ZIP

giovedì 28 ottobre 2010

Usare Arduino come devboard per software scritto con avr-libc




La tentazione è forte: abbiamo comprato un'ottima scheda italiana Arduino e ora vogliamo cimentarci oltre il linguaggio Wiring, con programmi in assembler o in C (es. avr-libc).

Non c'è bisogno di comprare una scheda di sviluppo, perché è possibile usare il nostro Arduino come una scheda di sviluppo per programmi scritti in avr-libc, assembly, piuttosto che per gli sketch in linguaggio wiring, senza la necessità di un programmatore hardware esterno: il nostro codice oggetto verrà eseguito dal boot loader del micro di Arduino, in modo semplice e soprattutto totalmente "indolore"...

Il nostro file config va modificato aggiungendo delle opzioni per avrdude (il software di programmazione per i micro AVR) che ci consentiranno di "uploadare" il codice oggetto sulla nostra scheda Arduino.

Di seguito il file config per Arduino 2009 (ATmega168 e clock da 16MHz). Per successive versioni di Arduino fare riferimento al tipo di micro e alla frequenza del quarzo.


# MCU name

MCU = atmega168

# Processor frequency (in Hz)

F_CPU = 16000000

# Target file name (without extension)

TARGET = main

# Object files directory
#     To put object files in current directory, use a dot (.), do NOT make
#     this an empty or blank macro!

OBJDIR = .

# List C source files here. (C dependencies are automatically generated.)
# The first one must be TARGET above defined, the other ones
# must be separated by a space. Examples:
# SRC = $(TARGET).c lcd.c i2c.

SRC = $(TARGET).c


# List C++ source files here. (C dependencies are automatically generated.) spaced

CPPSRC = 

# List Assembler source files here.
#     Make them always end in a capital .S.  Files ending in a lowercase .s
#     will not be considered source files but generated files (assembler
#     output from the compiler), and will be deleted upon "make clean"!
#     Even though the DOS/Win* filesystem matches both .s and .S the same,
#     it will preserve the spelling of the filenames, and gcc itself does
#     care about how the name is spelled on its command-line.

ASRC =



# List any extra directories to look for include files here.
#     Each directory must be seperated by a space.
#     Use forward slashes for directory separators.
#     For a directory that has spaces, enclose it in quotes.

EXTRAINCDIRS = 

# List any extra directories to look for libraries here.
#     Each directory must be seperated by a space.
#     Use forward slashes for directory separators.
#     For a directory that has spaces, enclose it in quotes.

EXTRALIBDIRS = 


# Porta programmatore
# COM1...COMx (porta seriale), LPT1 (porta parallela)
# Linux: /dev/ttyUSB0 oppure /dev/ttyUSB1

AVRDUDE_PORT = /dev/ttyUSB0  

# Tipo programmatore (stk500v1 per Arduino)

AVRDUDE_PROGRAMMER = stk500v1

# Velocità seriale (19200bps)

AVRDUDE_PORTSPEED = 19200




AVRDUDE_PORT: specificare il device dove è collegato Arduino. Sotto Linux sarà generalmente /dev/ttyUSB0. Per conoscerlo lanciare da shell:


$ dmesg | grep FTDI

e trovare la riga dove viene specificata la porta:

[20560.252484] usb 4-3: FTDI USB Serial Device converter now attached to ttyUSB0

L'opzione AVRDUDE_PROGRAMMER non va modificata, a meno che non si voglia usare il nostro makefile con altri programmatori hardware.

Come software di prova, usiamo un semplice programmino che accende cinque LED in successione. Attraverso un pulsante è possibile selezionare l'effetto di scorrimento desiderato (MODO) e attraverso un secondo pulsante è possibile modificare il tempo di lampeggio da 50ms a 500ms in step da 50ms.  Lo schema è raffigurato di seguito:


(schema creato con gEDA)


Per quanto riguarda la corrispondenza pin ATmega168 / pin Arduino:



(immagine creata con Fritzing)



Metodo di risoluzione:  ad intervalli di tempo regolari (scanditi da un timer), cambierà la combinazione di LED accesi (combinazione che chiamiamo frame), secondo una modalità definita nel vettore del relativo modo.
Ogni elemento di questo vettore contiene un byte che codifica l'accensione dei sei LED, frame per frame. La convenzione è 1 -> LED acceso, 0 -> LED spento.  Il vettore verrà così scandito (a un intervallo di tempo definito da un timer) in ricircolo.

Per esempio, il modo out_in, avrà la seguente sequenza di frame. Arrivato all'ultimo elemento si azzera il contatore dei frame, ripartendo così dal primo elemento del vettore:




Per i tempi viene impiegata una temporizzazione hardware (basata su Timer1 in modo CTC con interrupt) così da mettere ancora meglio in pratica quanto visto nei post precedenti e per la gestione dei pulsanti vengono usati i due interrupt INT0 e INT1. Per la modifica dei tempi, viene modificato in fase di esecuzione il registro OCR1A a "step" di 50ms. Il porgramma parte con un tempo di 50ms e quindi dopo 9 pressioni del pulsante "Tempo" si raggiungerà il valore massimo di 0,5s; alla decima pressione il tempo riparte da 50ms

50ms, 1-> 100m, 2-> 150m, 3-> 200m, 4-> 200m, 5->250m, 6->300m, 7->350m, 8->400m, 9->500ms

Di seguito il sorgente (main.c):


/*
 * LIGHTS 
 * fp, 2010
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>


#define N            31249  /* Numero del timer1 per avere un tempo max (31249=0.5s@fck=16MHz:256) */
#define NA           3124   /* Numero del timer1 per avere uno step minimo (3124=50ms@fck=16MHz:256) */
#define NMODI        4      /* Numero di modi di funzionamento */
#define PORTDATALED  PORTB  /* Porta dove sono collegati i sei LED */
#define PORTDDRLED   DDRB   /* Registro direzione di PORTDATALED */


/* Prototipi delle routine dei vari modi di funzionamento */
void Shift_DX();
void Shift_SX();
void Shift_SX_DX();
void Out_In();


/* Variabili globali (classificate static perché devono "vivere" anche all'uscita delle funzioni */
/* e qualificate volatile per quelle condivise con le routine di servizio interrupt) */
static int frame=0;                   /* Frame iniziale da visualizzare */
volatile static uint8_t modo=0;       /* Modo iniziale di funzionamento (da 0 a NMODI -1) */
static uint8_t stato=0;               /* Variabile di stato (usata per il modo ShiftSXDX e OnOff) */


/* Sequenze di frame per ciascun modo */
/* memorizzate nella memoria flash (per risparmiare RAM) */
const uint8_t  sx_dx[6] PROGMEM = {0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000001};
const uint8_t out_in[4] PROGMEM = {0b00100001, 0b00010010, 0b00001100, 0b00010010};



/* Inizializza/configura l'hwardware (direzione pin I/O, timer, interrupt) */
void init()
{
   PORTDDRLED |= 0b00111111; /* pin Px5..Px0 di uscita */
   EIMSK |= (1 << INT0)| (1 << INT1) ; // abilita interrupt INT0 e INT1
   TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC (su OCR1A)
   OCR1A = NA;      // carica NA nel registro OCR1A (tempo minimo)
   TIMSK1 = (1 << OCIE1A);  // abilita interrupt CTC su OCR1A
   TCCR1B |= (1 << CS12); // abilita prescaler (fck/256)
}



int main (void)
{

   init();

   sei();  // abilita gli interrupt

   while(1)
   {
      /* non fa niente...aspetta un'interruzione */
   }
}



/* Routine servizio interrupt TIMER1 CTC: viene eseguita ogni (OCR1A+1)*256*Tck sec. */
ISR( TIMER1_COMPA_vect )
{
   switch(modo){
      case 0: Shift_DX();
        break;
      case 1: Shift_SX();
        break;
      case 2: Shift_SX_DX();
        break;
      case 3: Out_In();
        break;  
   }
}



/* Routine servizio interrupt INT0 (pulsante variazione del tempo in step di 50ms ) */
ISR ( INT0_vect )
{
  OCR1A += NA;    // incrementa di 50ms
  if (OCR1A >= N) // raggiunto il tempo max...
     OCR1A=NA;    //...torna al tempo min.
  _delay_ms(500); // debounce
}



/* Routine servizio interrupt INT1 (pulsante variazione del modo fra o e NMODI) */
ISR ( INT1_vect )
{
  modo++;          // passa al modo successivo
  if (modo==NMODI) // raggiunto il massimo modo...
     modo=0;       // ...riparti dal modo 0
 _delay_ms(500);   // debounce
}



/* Modo 0: Scorre i LED verso destra */
void Shift_DX()
{
   if (frame==6) // raggiunto l'estremo dx...
      frame=0;   // ...vai al LED estremo sinistro
   PORTDATALED = pgm_read_byte(&sx_dx[frame]);  // leggi il frame e invialo al port
   frame++;      // passa al frame successivo
}


/* Modo 1: Scorre i LED verso sinistra */
void Shift_SX()
{
   if (frame==-1)  // raggiunto l'estremo sx...
      frame=5;     // ...vai al LED estremo destro

   PORTDATALED = pgm_read_byte(&sx_dx[frame]); // leggi il frame e invialo al port
   frame--;        // passa al frame precedente
}



/* Modo 2: Scorre i LED da sinistra e poi da destra */
void Shift_SX_DX()
{
  if (frame==6) // raggiunto il LED estremo destro
  {
    frame=4;    // vai indietro di un LED
    stato=1;    //  stato 1: ora scorri i LED da dx a sx
  }
  if (frame==-1) // raggiunto il LED estremo sinistro
  {
    frame=1;   // vai avanti di un LED
    stato=0;   // stato 0: ora scorri i LED da sx a dx
  }

  PORTDATALED = pgm_read_byte(&sx_dx[frame]); // leggi il frame e invialo al port

  switch (stato) {
   case 0: frame++; break; // se scorri da sx a dx, incrementa
   case 1: frame--; break; // se scorri da dx a sx, decrementa
  }

}


/* Modo 3: Scorre i LED dall'esterno verso l'interno e poi di nuovo verso l'esterno */
void Out_In()
{
  if (frame>=4)  // se siamo a un frame superiore a quello massimo
     frame=0;    // riparti dal primo frame
  PORTDATALED = pgm_read_byte(&out_in[frame]); // leggi il frame e invialo al port
  frame++;
}



Creiamo una cartella (es. lights) nella quale ricopiamo i file:

main.c
config
makefile


Portiamoci in questa directory e lanciamo

make all 

e dunque "uploadiamo" il file oggetto su Arduino con:

make program

Se tutto va a buon fine, nella nostra shell apparirà il seguente output di avrdude:

make program
avrdude -p atmega168 -P /dev/ttyUSB0   -c stk500v1 -b 19200     -F -U flash:w:main.hex 

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x000000
avrdude: Yikes!  Invalid device signature.
avrdude: Expected signature for ATMEGA168 is 1E 94 06
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: input file main.hex auto detected as Intel Hex
avrdude: writing flash (790 bytes):

Writing | ################################################## | 100% 0.59s

avrdude: 790 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex auto detected as Intel Hex
avrdude: input file main.hex contains 790 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.53s

avrdude: verifying ...
avrdude: 790 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done.  Thank you.

lights.zip



I contenuti di questo documento, sono stati resi possibili grazie ai seguenti strumenti gratuiti:
- FidoCADJ (disegno grafici)
- Google Code Prettify (sorgenti con sintassi evidenziata)
- gEDA (disegno schemi elettrici)
- Fritzing (disegno board Arduino)
- Inkscape (aggiunta nomi pin al disegno della board Arduino)



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 

sabato 14 agosto 2010

Timer AVR in 5min: 4) Generare forme d'onda con CTC

< Timer AVR in 5min: 3) VMLAB e linux Timer AVR in 5min: 5) Gen. forme d'onda PWM (fast) >




Ultima revisione: 26/Apr/2012 


quadra.zip


Supponiamo di avere il seguente compito:

Generare, con il timer1 da 16bit, un segnale rettangolare simmetrico (DC=50%) e unipolare avente frequenza di 1 kHz, usando come clock quello di sistema fc=4MHz

La commutazione del livello logico di un pin di uscita, ad intervalli di tempo regolari T, può essere vista come un'onda rettangolare simmetrica e unipolare (livelli di tensione 0V/VH):


Il periodo dell'onda quadra, che chiamiamo Ts, è evidentemente sempre pari a 2*T, ove T è il nostro tempo generato con il timer.

Nel compito assegnato, dunque abbiamo Ts=1/1000=1ms da cui ricaviamo T=0,5ms

A questo punto, possiamo prendere il software che abbiamo già scritto per il lampeggio del LED cambiando semplicemente il numero N calcolandolo per avere un tempo di 0,5ms invece di 1s

C'è però una soluzione migliore.

Con gli AVR (così come con altri micro) è possibile effettuare la commutazione del livello di uscita di un pin, non solo via software come si è fatto nella I parte (mediante un interrupt e un'operazione di ex-or) ma anche completamente via hardware, con tutti i vantaggi del caso.

Se, quindi, il nostro compito è semplicemente quello di commutare il livello di un piedino di uscita a intervalli di tempo regolari ovvero, in altre parole, generare un'onda quadra simmetrica è preferibile utilizzare sempre il modo CTC già visto, ma con un meccanismo hardware invece di uno software.

Nel modo CTC con meccanismo hardware non ci sono interrupt e relative routine di gestione da scrivere nel software; il modo CTC fa sempre il suo lavoro: allorquando il dato contenuto nel registro del timer1 TCNT1 eguaglia quello del registro OCR1A (1), al successivo impulso di clock avviene l'azzeramento del timer e il conteggio riparte.

Quello che cambia è cosa fare all'occorrenza dell'azzeramento: non si usa più un interrupt, ma avviene (totalmente via hardware) una commutazione del livello logico di un particolare piedino d'uscita chiamato OC1A; oppure, se configuriamo diversamente il nostro software, come vedremo, l'azzeramento ( ovvero V(t)=0V) o l'attivazione dell'uscita (ossia V(t)=VH, tipicamente 5V).

A differenza del "meccanismo software", che usa un interrupt, dove si è liberi di scegliere non solo cosa fare ogni T secondi, ma anche il piedino sul quale avere la commutazione, usando il modo CTC con il meccanismo hardware si è però vincolati a usare un preciso pin, riportato nel datasheet con il riferimento OC1A (che guardacaso negli ATmegax8 corrisponde proprio con PB1):



Il periodo Ts del segnale è



e quindi il tempo T del nostro timer, come già detto, dovrà essere pari a:



Determiniamo il numero N da caricare nel registro OCR1A per i diversi valori di prescaling: 1, 8, 64, 256 o 1024 tramite la nota formula:





ove il periodo Tc è immediatamente noto come reciproco della frequenza:



Calcoliamo per ogni valore di k, il relativo numero N:







k N
11.999
8249
64 30,25
2566,81
1024 0,95



A questo punto prendiamo in considerazione solo fattori di prescaler per i quali abbiamo numeri N rappresentabili dal timer a 16 bit (numeri interi e non superiori a 65535), quindi scartiamo da subito k=64, k=256 e k=1024.

Rimangono infine k=1 e k=8, dei due preferiamo il più piccolo (nessun prescaler) in corrispondenza del quale N=1999.

Scriviamo il pseudocodice:

  1. Imposta modo CTC (con confronto sul registro OCR1A) sul Timer1;
  2. Carica N nel registro OCR1A;
  3. Imposta la commutazione hardware del pin OC1A successivamente all'azzeramento del timer
  4. Imposta prescaler a 1 (nessun prescaler);


Vediamo le istruzioni, passo passo:

1. Imposta modo CTC (con confronto sul registro OCR1A) sul Timer1

E' la stessa configurazione già vista per il modo CTC con interrupt; bisogna attivare il solo bit WGM12 che si trova nel registro TCCR1B:

TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC (su OCR1A)



2. Carica N nel registro OCR1A;


L'istruzione di caricamento è immediata:
OCR1A = N;
ove N è la solita etichetta simbolica, che va definita all'inizio, con il consueto commento esplicativo sul tempo generato in corrispondenza del numero N:

#define N 1999 // T=0,5ms @4MHz :1



3. Imposta la commutazione hardware del pin OC1A successivamente all'azzeramento del timer


L'impostazione della commutazione hardware, avviene impostando i bit COM1A1 e COM1A0 del registro TCCR1A, che definiscono "cosa fare" sull'azzeramento del timer: 1) commutare l'uscita (toogling), 2) azzerare l'uscita, 3) settare l'uscita, come riportato nella tabella successiva:











che in codice avr-libc si traduce come:

TCCR1A |= (1 << COM1A0);  // Imposta toogle su OC1A




4. Imposta prescaler a 1 (nessun prescaler);

 
Dalla tabella 16-5 riportata nel datasheet dell'Atmega168, ricaviamo che bisogna attivare il solo bit CS10 del registro TCCR1B:




TCCR1B |= (1 << CS10) ; // nessun prescaling (k=1)




Combinando i vari frammenti di codice, siamo ora in grado di scrivere il software definitivo:



#include <avr/io.h>

#define N 1999  // T=0,5ms @4MHz :1

int main (void)

{


   DDRB |= (1 << PB1);  // Imposta il pin PB1(OC1A) come di uscita

   TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC (su OCR1A)

   OCR1A = N;  // carica N nel registro OCR1A

   TCCR1A |= (1 << COM1A0);  // Imposta funzione di commutazione su OC1A

   TCCR1B |= (1 << CS10) ; // nessun prescaling (k=1)



   // Da questo punto in poi il timer è avviato!



   while(1)

      {

        /* non fa niente...*/
        /* sul reset del timer (dopo il numero 1999) */
        /* avverrà la commutazione hardware del pin OC1A */

      }

Simuliamo il nostro software con vmlab (come già spiegato precedentemente) e il risultato è appunto un'onda quadra di 1kHz.




Per misurare la frequenza, cliccare sulla traccia e quindi sul pulsante Analyze.

Nello zip allegato, oltre al sorgente, c'è anche il file progetto (QUADRA.PRJ) per VMLAB.

----
Nota 1) E' possibile utilizzare, in alternativa al registro OCR1A, il registro ICR1 (v. datasheet per la diversa configurazione)

< Timer AVR in 5min: 3) VMLAB e linux Timer AVR in 5min: 5) Gen. forme d'onda PWM (fast) >





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)
- gEDA (disegno schemi elettrici)