PrettyPrint

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)



mercoledì 11 agosto 2010

Timer AVR in 5min: 3) VMLAB e linux

< Timer AVR in 5min: 2) Il modo CTC Timer AVR in 5min: 4) Generare forme d'onda con CTC >




Ultima revisione: 26/Apr/2012 


timer1.zip


VMLAB è un software di simulazione di micro (AVR e ST62) per Windows, completamente gratuito, che si appoggia su WinAVR (l'ambiente di sviluppo gratuito per AVR su Windows).

La sua peculiarità è quella di avere anche un oscilloscopio, mediante il quale è possibile fare delle misure di tempo sulle forme d'onda generate dal micro.

VMLAB si installa e funziona bene anche sotto Linux (grazie a Wine).
 
In ambiente Linux non essendoci WinAVR bisognerà provvedere manualmente alla compilazione, al collegamento ecc. dei vari file.

Dopo una rapida occhiata su Google, ho proceduto in questo modo:

1. Scaricare e installare (tramite Wine) VMLAB, disponibile all'indirizzo: http://amctools.com/download.htm

2. Successivamente scaricare e copiare nella propria home dir. il nuovo modello per l'ATmega168B già modificato per Linux  (curato da ThVortex)

Quindi decomprimerlo nella cartella di installazione di VMLAB. Da  bash shell:

$cd
~$ unzip -o useravr-0.6.zip -d ~/.wine/drive_c/VMLAB


N.B. Senza la modifica del punto 2, la simulazione del Timer1, in alcuni contesti di utilizzo, non avviene correttamente (e questo vale anche sotto Windows).

3.  assicurarci di avere installato sul nostro sistema il toolchain per lo sviluppo AVR con gcc. Con Debian il comando è:

# apt-get install gcc-avr binutils-avr avr-libc gdb-avr avrdude


(Con Ubuntu ricordarsi che c'è sudo)

Installato il necessario, si passa alla preparazione del makefile.

Per la generazione dei file necessari mi sono affidato all'ottimo Makefile di Winavr; l'ho solo modificato un poco per generare anche il file COFF con l'all, e per "spostare" tutta la parte di configurazione (micro, frequenza di clock, file, directory) in un altro file (che ho chiamato config) così da non mettere mani nel Makefile.

Il mio suggerimento è innanzitutto quello di crearsi una directory in cui mettere i propri lavori di Vmlab. Ogni progetto avrà dunque la propria directory nella quale ci sono tutti i file sorgenti, header, le librerie ecc. necessarie.

Creiamo allora una cartella nella nostra home: vmlabproj:

~$ mkdir vmlabproj


e una dir. in cui mettiamo il nostro primo progettino, di nome timer1:

~$ cd vmlabproj
~/vmlabproj$ mkdir timer1


In questa dir. ci copiamo dunque il file sorgente principale, quello dove c'è la main(), che chiamiamo sempre main.c e i file makefile e config. Il makefile non va modificato, il config, sì, secondo le proprie esigenze.

Il main.c è il nostro software per accendere e spegnere il LED ogni sec. che avevamo già scritto:


#include <avr/io.h>
#include <avr/interrupt.h>

#define N 62499 // 1s @ 4MHz/64

int main (void)
{

  DDRB |= (1 << PB1);  // Imposta il pin PB1 come di uscita
  TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC (su OCR1A)
  OCR1A = N;  // carica N nel registro OCR1A
  TIMSK1 = (1 <<  OCIE1A);  // abilita interrupt CTC su OCR1A
  TCCR1B |= (1 << CS11) | (1 << CS10) ; // abilita prescaler (fc/64)

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

  sei();  // abilita gli interrupt

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


/* Routine gestione interrupt (viene eseguita ogni secondo)*/

ISR( TIMER1_COMPA_vect )
{
   PORTB ^=  (1 << PB1) ;  // Se PB1=0 allora PB1=1, se PB1=1 allora PB1=0
}


Il file config è riportato di seguito:

# MCU name

MCU = atmega168

# Processor frequency (in Hz)

F_CPU = 4000000

# 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 =




In MCU si definirà il tipo di micro (atmega168, atmega328, attiny2313 ecc.),

in F_CPU la frequenza di lavoro in Hz (nel nostro caso 4000000 Hz, cioè 4MHz).

TARGET è il solo nome (senza estensione) del nostro file principale.

OBJDIR lasciamo il punto (i file oggetto verranno copiati nella stessa directory).

In SRC, se abbiamo più file sorgenti (es. lcd.c, i2c.c, serial.c ecc.) li specificheremo dopo il target, separandoli con uno spazio.

Stesso discorso per CPPSRC, che riguarda i file C++. Le altre definizioni le useremo se i nostri file si trovano in altre directory, se abbiamo anche file asm ecc.

Con il config preparato, portiamoci nella dir. del nostro progetto e lanciamo un make

~$ cd vmlabproj/timer1
~/vmlabproj/timer1$ make


Dopo questo comando dovremmo trovarci nella nostra directory di lavoro alcuni file, fra i quali quelli che interessano a VMLAB sono:

main.cof
main.elf


Nel caso volessimo cancellare i file generati, basterà il classico clean:

~/vmlabproj/timer1$ make clean


Fatto questo creiamo il file progetto per VMLAB, con un editor di testi, TIMER1.PRJ

.MICRO     "ATmega168b" "CKSEL=0000 CKDIV8=1"
.CLOCK     4MEG
.TOOLCHAIN "GENERIC"
.TARGET    "main.hex"
.COFF      "main.cof"
.STORE 5   ; Time stop della simulazione

; ------------------------------------------------------------

D1 VDD K        ; D1 (LED), morsetto 1: A del LED (obbligatoriamente su VDD), morsetto 2: catodo
R1 K PB1 0.62K  ; R1 (620Ohm), morsetto 1 sul K del LED, morsetto 2 su PB1
.PLOT V(PB1)

Il ";" inizia un commento e quindi tutto quello che cìè scritto dopo è ignorato da VMLAB.

Nelle prime direttive si vanno a definire il micro, la frequenza di clock in MHz, i file che abbiamo generato e il tempo fino al quale vogliamo far eseguire i calcoli di simulazione. Dal momento che il nostro timer genera un periodo di 2 secondi ci si può fermare a po' più del doppio del periodo.

La seconda parte, invece, è quella che riguarda il nostro LED collegato all'uscita PB1

VMLAB mette a disposizione diversi componenti (fino a 8 LED, slide, tastierino ecc.), che possono essere richiamati dall'utente con una particolare sintassi (vedi help).

La sintassi è molto simile a quella di Spice.

Per il LED, la sintassi è:

Dx VDD nomecatodo


Con x che va da 1 a 8, a secondo del LED che vogliamo usare.

L'anodo del LED (il morsetto n.1) deve essere sempre collegato all'alimentazione VDD. Il secondo morsetto del LED (il catodo) lo chiamiamo, per esempio, "K" e lo colleghiamo all'uscita PB1, mediante la resistenza R1 di 620 Ohm.

Questo perché VMLAB impone di collegare +5V sull'anodo e dunque il nostro LED (come già avevamo scelto in partenza) si accenderà sul livello logico 0.

Lo schema quindi è:




L'ultima direttiva, chiede a VMLAB di visualizzare sull'oscilloscopio la tensione sul piedino PB1.

Avviamo VMLAB dal menù Wine...e apriamo il file progetto, dalla barrà dei menù con:
Project -> Open.

Visualizziamo sia il "Panel control" (dove ci sono tutte le periferiche di I/O e quindi anche i LED) e l'oscilloscopio con i comandi, con:

View -> Scope
View -> Control Panel


e lanciamo un build...che in effetti è solo un build per VMLAB, in quanto i file già sono stati "buildati" manualmente con make...

Project -> Project Build

A questo punto lanciamo il Run (premendo F5) e osserviamo il segnale PB1 sull'oscilloscopio.

Notiamo subito che la simulazione di VMLAB non è in tempo reale: il LED non rimane acceso/spento per 1s, ma i tempi computati sull'oscilloscopio sono invece esatti.

Considerati i tempi in gioco, conviene impostare l'oscilloscopio su un Time/DIV di 50ms/DIV e su "Full View".

Ci sono due cursori a disposizione per la misura dei tempi, il primo viene attivato cliccando sul bottone 1 e ha lo stile di una linea continua, il secondo viene attivato cliccando sul bottone 2 e ha lo stile di una linea tratteggiata. Sotto questi bottoni, viene riportata la differenza di tempo: t2-t1

La misura dei tempi avverrà per il tempo in cui l'uscita è bassa e per il tempo in cui l'uscita è alta.

Ovviamente occorrerà fermare la simulazione a un istante in cui si visualizza tutto il semiperiodo, fare la misura e poi riavviarla fino a visualizzare per intero il successivo semiperiodo.

I risultati della simulazione sono riportati di seguito e come si può notare i tempi risultano perfettamente uguali a 1s.

Tempo "on" - LED acceso (PB1=0V):



Tempo "off" - LED spento (PB1=5V)



Buon divertimento!



timer1.zip


< Timer AVR in 5min: 2) Il modo CTC Timer AVR in 5min: 4) Generare forme d'onda con CTC >



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)
- Forum VMLAB: http://www.amctools.com/cgi-bin/yabb2/YaBB.pl?board=news
- Tutorial VMLAB: http://www.scienceprog.com/using-vmlab-as-virtual-oscilloscope/



domenica 1 agosto 2010

Timer AVR in 5min: 2) Il modo CTC

< 1)Timer AVR in 5min: 1) Partiamo da zero 2) Timer AVR in 5min: 3) VMLAB e linux >




Ultima revisione: 26/Apr/2012 


Supponiamo di avere un compito piuttosto semplice:

Accendere e spegnere un LED (collegato sul pin1 del PortB) ogni secondo, usando il Timer1 di un ATmega168. Il clock è quello di sistema del micro, che è di 4MHz.

Dividiamolo in due parti:

"...Accendere e spegnere un LED (collegato sul pin1 del PortB) ...":

Il programma dovrà provvedere a spegnere il LED se è acceso, viceversa ad accenderlo se è spento.

Per quanto riguarda l'hardware, possiamo collegare il catodo del LED sul pin1 di PortB, mediante una resistenza di limitazione opportunamente dimensionata:




in questo modo, quando l'uscita sarà a livello "1" (5V)  il LED sarà spento, viceversa, quando l'uscita sarà a livello "0" (0V), il LED sarà acceso:


Lasciamo in sospeso per il momento come implementare via software questa prima parte del compito e concentriamoci invece sulla seconda:

"... ogni secondo, usando il Timer1 di un ATmega168. Il clock è quello di sistema del micro, che è di 4MHz...":

Quindi per un tempo T di 1s  il LED dovrà essere acceso, per il successivo tempo di 1s dovrà essere spento e così via, periodicamente.

Dalla frequenza di clock fc ricaviamo il periodo del clock di sistema Tc



Ora, il Timer1 (16 bit), al massimo può contare fino a N = 65535  (2 16 -1) dunque, il massimo tempo Tmax che possiamo ottenere da questo timer è ricavabile dalla formula generale:



ossia 16,384 ms, un tempo molto inferiore a 1s

N.B.  c'è il "+1" perché il conteggio inizia dallo 0 e quindi anche per esso viene speso un ciclo di clock

Come fare per arrivare a 1s?

I bit non li possiamo aumentare, ma c'è la possibilità di applicare al timer un periodo più grande di quello di sistema Tc ossia applicare al timer una frequenza di clock fc più bassa di quella di sistema.

Internamente l'AVR ha un prescaler (divisore di frequenza) programmabile, che consente di pilotare il timer anche con una frequenza di clock inferiore a quella del sistema, di un fattore: 8, 64, 256 o 1024 (questo per il Timer0 e il Timer1) oppure, ragionando in termini di periodo ... che consente di pilotare il timer con un periodo di clock maggiore di quello di sistema, di un fattore: 8, 64, 256 o 1024

Dunque, il clock al quale lavora il timer usando i prescaler, non è più Tc, ma Tc *k. , ove k è il fattore di prescaler; cioè ci consente di avere durate di tempo maggiori di quelle massime ottenibili con la frequenza di clock pari a quella di sistema.

Pertanto la formula per il calcolo del tempo massimo T , che tiene conto anche del fattore di prescaler k diventa:



Siamo così in grado di calcolare il massimo tempo T, per ogni fattore di prescaler:



























k T
116,384ms
80,13s
64 1,05s
2564,19s
1024 16,7ss


Scartiamo ovviamente i fattori k=1 e k=8, perché entrambi non ci consentono di arrivare a 1s e quindi restringiamo la scelta fra k=64, 256 oppure 1024.

Il numero N del timer per avere un desiderato tempo T sarà dato dall'equazione:




che, per T=1s, per i soli k sopra individuati diventa:





k N
64 62.499
25615.624
1024 3.905,25


A questo punto dobbiamo prendere il considerazione solo fattori  k per che danno numeri N rappresentabili nel registro del timer; il timer da 16 bit può infatti rappresentare numeri interi compresi fra 0 e 65535.

Quindi possiamo scegliere solo k=64 oppure k=256 perché i rispettivi N=62499 e N=15624 sono rappresentabili, a differenza di k=1024 che dà un numero N non intero.

Ci rimane allora da scegliere k=64 oppure k=256.

Scegliamo k=64, perché, in definitiva:

1) ci dà un numero N rappresentabile (62499)
2) è il minimo fattore che ci consente di avere un tempo superiore a quello desiderato e quindi ci assicura una risoluzione migliore (cioè un periodo di clock più piccolo).

Resta ora solo un problema: far azzerare il timer dopo 62499 invece che dopo 65535.

Come fare?  

Configurare il timer per farlo lavorare in modo CTC (Clear Timer on Compare) e usare un interrupt che viene generato all'azzeramento dopo il numero desiderato.

Il timer che lavora in modo CTC non conta fino al massimo numero dato dal suo numero di bit, ma fino a un numero massimo definito in un particolare registro: OCR1A (per il Timer1) (1).

Un impulso di clock dopo il raggiungimento del numero massimo N,  il timer viene azzerato e riparte il conteggio dal numero zero.

Dunque, in fase di configurazione del timer1, si andrà a caricare il numero 62499 nel registro OCR1A, perché vogliamo che il nostro timer non conti più fino a 65535, ma fino a 62499.

Il pseudo codice per la sola configurazione del timer è:

  1. Imposta modo CTC (con il numero massimo definito in OCR1A);
  2. Carica il numero N in OCR1A;
  3. Attivare l'interrupt su OCR1A;
  4. Imposta il prescaler;
  5. Abilita tutti gli interrupt;

Vediamo come codificare i vari passaggi, sempre con avr-libc:

 1. Imposta modo CTC (con il numero massimo definito in OCR1A); 

Il timer1 dispone di tre registri di controllo: TCRR1A, TCCR1B e TCCR1C, i cui bit, opportunamente attivati, consentono di configurare diverse opzioni di funzionamento del timer1.

Fra questi, ci sono quattro bit che riguardano  la configurazione del timer come generatore di forme d'onda: si trovano "dislocati" fra il registro A e il registro B e sono identificati dall'etichetta WGM1x:



I bit WGM10 e WGM11 sono nel registro di controllo A:







mentre i restanti WGM12 e WGM13 si trovano nel registro di controllo B:





Tutti i 4 bit sono di lettura/scrittura e di default si trovano a 0.


La tabella 16.4 del datasheet dell'ATmega168/328 descrive come attivare/disattivare questi quattro bit per usare la modalità di generazione di forma d'onda desiderata. A noi interessa la modalità CTC e precisamente la modalità n. 4, per la quale il numero massimo N che vogliamo contare  (TOP secondo la convenzione del d/sheet) va caricato nel registro OCR1A:


Per il modo n.4, come si vede, occorre attivare solo il bit WGM12, che, come appena visto, si trova nel registro TCR11B, dunque il codice sarà:

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


2) Carica il numero N in OCR1A;


E' bene fare uso di un'etichetta simbolica N, che verrà definita all'inizio del sorgente (v. sorgente), così da migliorare la manutentibilità del codice:
OCR1A = N; // Carica il numero N nel registro OCR1A
L'etichetta simbolica sarà dunque, nel nostro caso:
#define N 62499  // T=1s @fck=4MHz :64
E' sempre consigliabile commentare il numero N, indicando il tempo che si ricava in corrispondenza di esso e per quale valore di clock e prescaler.


3) Abilitare l'interrupt su OCR1A;

Il timer1 dispone di un registro per il mascheramento delle interruzioni (TIMSK1) che riguardano il suo funzionamento.

L'interruzione che dobbiamo attivare è quella relativa al confronto fra OCR1A e il valore del registro dati del timer TCNT1: quando questi due valori si uguagliano, al successivo impulso di clock, la normale esecuzione del programma deve essere interrotta per eseguire la relativa funzione di gestione, nel cui corpo andrà inserito il codice che vogliamo far eseguire all'occorrenza (v. avanti)







Per attivare questa interruzione, occorre attivare il solo bit OCIE1A del registro TIMSK1:

TIMSK1 = (1 <<  OCIE1A);  // abilita interrupt CTC su confronto con OCR1A



4) Impostare il prescaler;





I primi tre bit del registro di configurazione B, consentono di impostare il tipo di clock (se interno, se esterno, se assente) e il fattore di prescaling del clock interno, secondo la seguente tabella (Tabella 16.5, datasheet ATmega168/328):






Dal momento che vogliamo attivare il prescalere con un fattore :64, abbiamo dunque da attivare solo i bit CS11 e CS10:

TCCR1B |= (1 << CS11) | (1 << CS10) ; // abilita prescaler (fc/64)
Dopo l'istruzione di impostazione del prescaler, il timer si avvia partendo da 0, perciò questa deve essere sempre l'ultima della configurazione del timer.



5) Abilita tutti gli interrupt;

Dopo la configurazione vanno abilitati tutti gli interrupt, globalmente, diversamente verrà ignorato anche l'interrupt, precedemente attivato. Con avr-lib c, l'istruzione a tale scopo è:

sei();  // abilita tutti gli interrupt

Questa istruzione va inserita come ultima di ogni altra configurazione (es. configurazione dell' I/O)



Ricodarsi ovviamente di includere nel sorgente l'header per la gestione delle interruzioni, subito dopo quello dell'I/O:
#include <avr/io.h>
#include <avr/interrupt.h>


La scrittura della routine di gestione dell'interrupt


Infine, si scrive la routine di gestione dell'interrupt , nel cui corpo funzione andremo a mettere il compito che abbiamo "schedulato" ogni T secondi

In avr-libc, l'etichetta simbolica associata al nostro interrupt è TIMER1_COMPA_vect, dunque avremo:

// Viene eseguita ogni T sec

ISR( TIMER1_COMPA_vect )
{
  // Accendi o spegni il LED su PB1
}


Rimane il compito di "Accendere o spegnere il LED su PB1", il cui codice andrà nel corpo funzione della routine di servizio dell'interrupt.

Innanzitutto, bisogna configurare la porta PB1 come di uscita essendo il LED collegando su questa. Ciò viene fatto attivando il bit 1 del registro direzione dati del PortB, quindi del registro DDRB

Dunque bisognerà inserire anche questa istruzione di configurazione del pin PB1, insieme a quelle già viste per il timer1:

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


Per quanto riguarda l'accensione e lo spegnimento, possiamo collegare il LED con il catodo sull'uscita e l'anodo al ramo di alimentazione positiva (con ovvia resistenza di limitazione) e quindi questo si accenderà quando il bit di uscita è 0 (0V) e si spegnere quando il bit in uscita è 1 (per esempio 5V)

La commutazione viene realizzata via software, mediante un'operazione di ex-or (bit a bit) del solo bit letto su PB1 con un bit "1" e dei restanti bit con un bit "0".

Il contenuto di PortB viene così aggiornato con il risultato dell'operazione:

PORTB <---- PORTB ex-or 00000010 //leggi PORTB, fanne l'ex-or con 10 e riponi in PORTB
Se PB1 è 0 viene commutato a 1, viceversa se è 1, viene commutato a 0; i restanti bit rimangono immutati. Esempio:

Caso  in cui PB1=1 (LED spento)


      PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
PORTB  0   0   0   1   0   1   1   0         ^
       0   0   0   0   0   0   1   0         =
---------------------------------------------
PORTB  0   0   0   1   0   1   0   0         (1 diventa 0 => il LED si accende)



Caso  in cui PB1=0 (LED acceso)

      PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
PORTB  0   0   0   1   0   1   0   0         ^
       0   0   0   0   0   0   1   0         =
---------------------------------------------
PORTB  0   0   0   1   0   1   1   0         (0 diventa 1 => il LED si spegne)


Con avr-libc (avr-gcc), l'istruzione è:
PORTB = PORTB ^ (0b00000010);  // ex-or con 1, del solo bit PB1
che in sintassi C più compatta e leggibile diventa:
PORTB ^=  (1 << PB1) ;  // Se PB1=0 allora PB1=1, se PB1=1 allora PB1=0
Questo sarà dunque, in definitiva, il codice che vogliamo eseguire ogni secondo.

Il sorgente, combinando fra loro tutte le istruzioni di configurazione e la routine di interrupt, diventa in definitiva:

#include <avr/io.h>
#include <avr/interrupt.h>

#define N 62499  // T=1s @fck=4MHz :64

int main (void)
{

   DDRB |= (1 << PB1);  // Imposta il pin PB1 come di uscita
   TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC (su OCR1A)
   OCR1A = N;  // carica N nel registro OCR1A
   TIMSK1 = (1 <<  OCIE1A);  // abilita interrupt CTC su confronto con OCR1A
   TCCR1B |= (1 << CS11) | (1 << CS10) ; // abilita prescaler (fc/64)

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

   sei();  // abilita tutti gli interrupt

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


/* Routine gestione interrupt (viene eseguita ogni T=1s)*/

ISR( TIMER1_COMPA_vect )
{
   PORTB ^=  (1 << PB1) ;  // Se PB1=0 allora PB1=1, se PB1=1 allora PB1=0
}
---- Nota 1) E' possibile utilizzare, in alternativa al registro OCR1A, il registro ICR1 (v. datasheet , Tab. 16-4)


< 1)Timer AVR in 5min: 1) Partiamo da zero 2) Timer AVR in 5min: 3) VMLAB e linux >



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)