Capire le specifiche su clock, timer e prescaler

Salve, sto studiando il datasheet dell'ATMega328 per poter verificare l'applicabilità ad un mio progetto su cui però non vorrei parlare. Ho bisogno di fare misure di tempo ed eseguire istruzioni in instanti ben precisi. Il quarzo montato su Arduino Duemilanove e un 16MHz però però qual'è il periodo di esecuzione di una istruzione macchina? Funziona come per i PIC che si deve dividere la frequenza per 4? Poi si hanno 2 timer a 8 bit e uno a 16. Gli intervalli di tempo che ho bisogno di misurare vanno da 5-6ms a 0.1 ms utilizzando il timer a 8 bit. Ho pensato da impostare il prescaler a 256 però poi leggendo il datasheet e venuto fuori che il prescaler riduce pure la frequenza di cloc della cpu cosa che secondo me ha poco senso :o . Ho capito male io o è davvero così?

dunque, il datashettd dice che la maggior parte delle istruzioni impiega un singolo clock. Se scarichi il datasheet dall'atmel c'è l'elenco di istruzioni (assembly) con tanto di tempo macchina. comunque in un secondo hai 16.000.000 clock, se vuoi leggere un dato ogni 0.1 ms non devi sforare i 1600 clock per ciclo... vedi te se ce la fai p.s.: non ho capito la storia del prescaler... bhe a dirla tutta non so neanche che è il prescaler ::)

Il prescaler sull'atmega è un registro (uno per ogniuno dei 3 timer) dove è possibile impostare un fattore divisore della frequenza di clock che per default è 1 ma è programmabile con valori fino a 1024. Il registro si chiama CS (clock select) .

Al momento di inviare la domanda stavo leggendo la parte del clock ed avevo confuso il prescaler del clock con quello dei timer che in realtà è unico ed ha più uscite muxate ai vari timer. Quindi dite che non è come per i PIC che devo dividere la frequenza del quazo per avere la frequenza con cui esegue la maggior parte delle funzioni assembler? Altra cosa tra le uscite del prescaler, divisore di frequenza, ci sono 256 e 1024 e a servono entrambi in quanto al limite delle specifiche. Reimpostare il timer è un'operazione che richiede diversi ciclio di clock per assestsarei oppure come ho letto nel datasheet basta modificare un registro? Nel datasheet si fa riferimento al pericolo dovuto alla modifica del prescaler mentre un timer è in azione però non ho capito che pericoo ci possa ossere oltre all'ovvia ragione che il conteggio risulterà sbagliato.

Forse questo può darti una mano:
http://www.gioblu.com/index.php?option=com_content&view=article&id=103:che-cose-il-pwm&catid=38:programmazione&Itemid=7

Articolo davvero interessante che spiega molto benel'architettura dei timer nel ATMega328. L'unica copsa che non sono riuscito a capire è la modifica dei registri:

TCCR2A = _BV ( COM2A0 ) | _BV ( COM2B1 ) | _BV ( WGM21 ) | _BV ( WGM20 );

TCCR2A è il registro, COM2A0, COM2B1, WGM21 e WGM20 sono i campi del registro ma le sisntassi non la conosco. Chi me la spiega o mi da un link? Ho provato a cercare m invece di trovare cosa fa il comando trovo come modificare il registro per ottenere l'effetto voluto.

l'operazione sembra un Bitwise inclusive OR, la funzione _BV() non la conosco

ma quindi se il cristallo ha frequenza 16mhz col prescaler a 2 mi ritrovo 8mhz... Rallenta il clock.. giusto?

ora mi leggo l'articolo, scusate se ho chiesto cose già presenti li dentro :)

Dipende da che prescaler stai usando, ne esiste uno per la cpu che quindi rallenta la frequenza con cui vengono eseguite le istruzioni e ne esiste uno per i timer. Se usi solo quello per i timer rallenta solo la frequenza con cui il contatore del timer viene incrementato.

scusatemi per la risposta frettolosa.. sul datasheet dell'atmega328 da pag 161 in poi ci sono le mappature e l'uso dei registri che vi interessano

Quello che non so è il significato della funzione _BV.

_BV è una macro per il bitmasking che non fà parte dell’ide ma è della famiglia avr gcc.
se sei interessato guarda in arduino-0018\hardware\tools\avr\avr\include\avr\sfr_defs.h

@Andrea

Concentriamoci su un esempio reale che usa un ATmega8 (es. ATmega168):

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

"…Accendere e spegnere un LED (collegato sul pin1 del PortB) ogni secondo…":

Ogni sec il programma deve provvedere a spegnere il LED se è acceso, viceversa ad accenderlo se è spento. … lasciamo in sospeso per il momento come risolvere questa parte del compito e concentriamoci invece su…

"… usando il Timer1 e come clock quello di sistema del micro, che è di 4MHz…":

Il periodo del clock di sistema Tc = 0,25 micros (Tc=1/fc=1/4MHz) e al massimo il Timer1 (16 bit) può contare fino a N = 65535 (216-1). Dunque, il massimo tempo T che possiamo ottenere da questo timer è:

__T = ( N +1) * Tc __

ossia 16,384ms

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 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.

Pertanto la formula per il calcolo di T con il prescaler diventa:

T = ( N +1) * Tc * k

ove k è il fattore di divisione del prescaler. Quindi abbiamo, con N=65535, il massimo tempo che possiamo ricavare per ogni fattore di prescalamento:

-------------
   k | Tmax 
-------------
   8 | 0,13s 
  64 | 1,05s 
 256 | 4,19s 
1024 | 16,77s 
--------------

Scegliamo k=64, perché è 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).

Pertanto il numero N del timer per avere T=1s sarà:

N = [ T / (Tc * k) ] - 1

Per T=1s otteniamo N=62499

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 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).

Dunque, in fase di configurazione del timer1, si andrà a caricare il numero 62499 nel registro OCR1A.

Il pseudo codice per la configurazione del timer è:

Imposta modo CTC;
Carica il numero N in OCR1A;
Impostare l'interrupt su OCR1A;
Imposta il prescaler;

Con avr-libc il codice è:

TCCR1B |= (1 << WGM12); // imposta il Timer1 in modo CTC
OCR1A = 62499;  // carica 62499 nel registro OCR1A
TIMSK1 = (1 <<  OCIE1A);  // abilita interrupt CTC su OCR1A
TCCR1B |= (1 << CS11) | (1 << CS10) ; // abilita prescaler (fc/64)

Dopo l’istruzione di impostazione del prescaler, il timer si avvia partendo da 0.

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:

ISR( TIMER1_COMPA_vect )
{
   // Accendi il LED su PB1 se è spento, spegnilo se è acceso 
}

Esiste un secondo metodo di usare il modo CTC che è implementato completamente via hardware (quindi più “robusto”): non si appoggia su un interrupt: all’azzeramento del timer (in modo CTC) commuta, resetta o setta un particolare pin del micro (OC1A che corrisponde al PB1 del mega168).

Spero di non averti ulteriormente confuso le idee … ;D

Dunque vediamo se ho capito:
devo misurare l’intervallo di tempo che intercorre tra due fronti di salita (o di discesa) del segnale ad onda quadra che arriva al pin di interrupt esterno, uno dei due. Prevedo che i periodi saranno compresi tra 1ms e 100us. 62.5n25664=1.024ms. Imposto il prescaler a 64. Decido di utilizzare il timer0, perché magari gli altri sono occupati. Imposto il timer in modalità free running e nella subroutine dell’interrupt esterno mi copio il valore del registro TCNT0 che appunto lo contiene, e faccio la differenza con quello precedente, tale differenza la moltiplico per la costante di tempo 62.5n*64=4us e trovo il periodo con cui posso determinare la frequenza. Detto qualche castroneria?

PS: gestire l’overflow del timer non dovrebbe essere un problema, basta verificare se il numero attuale è minore del precedente.

Quello che devi fare tu rientra nella terza modalità di funzionamento di un timer/contatore: cattura di eventi di ingresso, che fa uso del registro ICRx

Questo esempio di codice credo che faccia al tuo caso:

http://winavr.scienceprog.com/avr-gcc-tutorial/program-16-bit-avr-timer-with-winavr.html

Perfetto c'è pure una funzione apposita nel micro. Però mi sfugge una cosa, che differenza c'è tra usare il registro ICR1 e fare le cose a mano? Rapidità e precisione?

Nella modalità di cattura, quando viene rilevata la transizione desiderata (di discesa o di salita) il contenuto del registro del timer (TCNTx) viene immediatamente "immortalato" (completamente via hardware) nel registro ICRx, che puoi andarti poi a leggere "con calma" via software....ovviamente compunque prima che arrivi l'altro fronte ;)

Quello che tu fai invece via software, invece, sicuramente risente dei tempi propri di esecuzione della routine di interrupt (salvataggio registri, program counter, istruzioni ecc.) e quindi fra l'istante in cui effettivamente avviene l'evento di interrupt e l'istante in cui, via software, vai a leggerti il TCNTx sicuramente è passato del tempo e dunque ti troveresti un TCNTx con "dei numeri più avanti" di quello effettivo che c'era all'accadere dell'interruzione.

Questo è quello che mi viene in mente, aspettiamo pareri migliori ;)

Qual’è il modo migliore per modificare i registri? Esiste una funzione specifica per arduino o devo usare istruzioni sandard c?

TCCR1A&=_BV(~COM1A1) &  _BV(~COM1A0) & _BV(~COM1B1) & _BV(~COM1B0) & _BV(~WGM11) & _BV(~WGM10)

Questa scrittura mi porta ad avere TCCR1A=0000–00? Dove – sono bit riservati?

Un'altra domanda, dove trovo l'elenco completo degli interrupt? Cioè l'elenco completo dei nomi riservati tipo ISR(TIMER1_CAPT_vect) quel TIMER1_CAPT_vect, come faccio a sapere se ne esistono altri?

come faccio a sapere se ne esistono altri?

Cerca nel datasheet del tuo ATmega: "Interrupt Vectors".

Qual'è il modo migliore per modificare i registri? Esiste una funzione specifica per arduino o devo usare istruzioni sandard c? Code: TCCR1A&=_BV(~COM1A1) & _BV(~COM1A0) & _BV(~COM1B1) & _BV(~COM1B0) & _BV(~WGM11) & _BV(~WGM10)

Questa scrittura mi porta ad avere TCCR1A=0000--00? Dove -- sono bit riservati?

Mi rispondo da solo. Per non modificare i bit riservati lascrittura corretta è:

TCCR1A&=~(_BV(COM1A1) &  _BV(COM1A0) & _BV(COM1B1) & _BV(COM1B0) & _BV(WGM11) & _BV(WGM10))