Frequenzimetro con ATmega16

Frequenzimetro digitale, uscita su LCD o su RS232 (su monitor pc)

Tempo fa avevo postato un articolo sull’utilizzo di uno schermo LCD interfacciato con un ATmega16; costruita l’interfaccia e scritto il programma di gestione, il progetto era terminato li. Adesso ho bisogno di un frequenzimetro (digitale) per misurare una frequenza di un segnale in uscita da un oscillatore, e ho pensato di sfruttare l’interfaccia per visualizzare il risultato. In generale l’uscita sullo schermo può essere riutilizzata su qualsiasi LCD con BUS di controllo parallelo (connettore solitamente di 15-16bit), con qualche piccola modifica. In ogni caso ho riadattato il codice per l’uscita su RS232 verso un pc o altro.

la misura

Il segnale da misurare è un onda quadre RZ (Return-to-Zero), di 5 Volt di ampiezza (più o meno) con frequenza di circa 120kHz. In questa situazione torna utile un progetto sviluppato in università per un corso di sistemi elettronici: sfruttare un timer dell’ATmega per “contare” quanti impulsi arrivano in un intervallo di tempo prefissato. In questo caso quindi utilizzo il TIMER1 come contatore mentre il TIMER0 come cronometro. Per avere il risultato corretto in Hz serve solo qualche normalizzazione.

funzionamento


La routine di calcolo di frequenza è semplice, conteggio gli impulsi sul pin T1 (collegato al timer1) per un tempo definito dal timer2, in questo caso attendo l’overflow (indicato dal flag TOV2 settato). Infine il calcolo si riduce a moltiplicare il contenuto di TCNT1 (impulsi arrivati) per un valore “costante”, indicato dalla define.

Il circuito quindi comprende solo il collegamento al pin T1 dell’ATmega, nell’ipotesi che il segnale da misurare rientri nel livello logico supportato dal pin (0-5V+- 10%), oppure è necessario prevedere un circuito di condizionamento  dedicato (buffer, fotoaccoppiatori, smorzatori, etc).

Di seguito è riportato un frammento di codice, riguardante la routine di calcolo della frequenza (per il codice completo vedere a fondo pagina).

Timer2 utilizzato come cronometro, al raggiungimento dell’overflow (TOV a livello alto) interrompe l’acquisizione del Timer1.

Timer1 è configurato come contatore di eventi esterni, in particolare è sensibile a fronti positivi del segnale applicato su PB1.

#define F_CPU 3686000UL
#define TCCR2MAX 255UL		//valore top del timer2
 
#define N 1024UL		//prescaler sul timer2
 
#define costante (F_CPU/(N*TCCR2MAX))
void misura(void) {
uint16_t contatore;
 
TIFR |= (1<<TOV2);	//cancello eventuali flag residui
TIFR |= (1<<TOV1);
SFIOR |= 1<<PSR10;	//riazzero prescaler interno (timer2)
 
TCCR1B |= (1<<CS11)|(1<<CS12)|(1<<CS10);   //lancio misurazione
TCNT1 = 0x0000;
TCCR2 |= ((1<<CS20)|(1<<CS21)|(1<<CS22));
TCNT2 = 0x00;
while(!(TIFR &(1<<TOV2)));	//attendo fine tempo di gate
 
contatore = TCNT1;
TCCR2 &= (!(1<<CS20)&!(1<<CS21)&!(1<<CS22));      //blocco timer2
TCCR1B &= (!(1<<CS11)&!(1<<CS12)&!(1<<CS10));	//blocco timer1
 
TIFR |= (1<<TOV2);	//cancello eventuali flag residui
TIFR |= (1<<TOV1);
SFIOR |= 1<<PSR10;	//riazzero prescaler interno (timer2)
freq =  contatore * costante;	//calcolo freq

Nella variabile freq è contenuta la frequenza (in floating point) calcolata utilizzando il fattore di correzione “costante” fornito come #define

limiti

Il range di frequenze misurabili risulta:

 

Fmin quando TCNT1 = 0, quindi quando la finestra di apertura di conteggio è più breve del periodo del segnale. Abbiamo quindi (con i valori delle #define qui sopra)

Fmin = F_CPU/(N*TCCR2MAX) = 14 Hz

Da notare che comunque scendendo in frequenza l’errore diventa sempre più importante (errore ha andamento parabolico decrescente al crescere di F).

Fmax è condizionata da 2 fattori: la velocità di clock del processore e il valore massimo di TCNT1, in questo caso:

TCNT1 max = 65534

Fmax = TCNT1max * costante = 925KHz

il fattore limitante in questo caso è il timer1; una possibile soluzione sarebbe l’aumento del prescaler del timer2, per limitare il periodo di apertura al conteggio, purtroppo è già impostato come valor massimo a 1024.

In ogni caso comunque è consigliabile tenersi lontano da F_CPU/2, frequenza utilizzata per la sincronizzazione dei latch sui pin del microcontrollore.

Nel caso di misurazioni “di fino” è necessario l’utilizzo di un quarzo come clock per il microcontrollore, mentre nel mio caso l’oscillatore interno RC calibrato a 4MHz è sicuramente sufficiente, dal datasheet è indicata una stabilità nell’ordine dell’1%.

invio dati

Nel caso di utilizzo di un LCD a interfaccia parallela, non ci sono particolari differenze rispetto al codice postato nell’articolo citato all’inizio. Alle routine già presenti ho inserito l’invio del risultato del calcolo, aggiornandolo ogni circa 200ms (sovrascrive vecchio valore su schermo).

Nel caso di trasmissione attraverso RS232 si pulisce sicuramente il codice delle 15 routine di gestione dell’LCD, si riacquista l’uso del timer1 e aumenta la velocità di aggiornamento dei dati, MA è necessaria la trasformazione di formato dei dati prima dell’invio, visto che i dati sono immagazzinati in RAM come floating point (double) mentre per visualizzare qualcosa di utile a schermo abbiamo bisogno di un’uscita ASCII!

Fortunatamente esiste, nella libreria string.h, la funzione snprintf, appositamente creata per la gestione dei caratteri ASCII a video; molto simile alla sprintf, ha il vantaggio di gestire un eventuale overflow del buffer caratteri.

int snprintf	(
char * 	buffer,
size_t 	dimensione,
const char * 	testo_aggiuntivo,
double * 	valore_floating,
...
)

Dove:

buffer è il puntatore a carattere del buffer sul quale viene memorizzata la stinga ASCII pronta per la trasmissione

dimensione è il valore massimo di lunghezza della stringa (NOTA: la dimensione di __s dev’essere >= di __n)

testo_aggiuntivo è un puntatore a carattere nella memoria programmi, utile se si vuole inserire una stringa aggiuntiva in fase di conversione (vedi aggiunta dell’unità di misura). In questo campo è possibile inserire comandi per impostare il formato di uscita, per esempio “%s%.3f%s” fornisce uscita in formato scientifico, con 3 cifre dopo la virgola. Tutti i comandi possibili li trovate a questo link (guida di avr-libc, sotto vfprintf)

valore_floating è la locazione di memoria dove troviamo il valore in floating point da convertire in ASCII

NOTA IMPORTANTE: l’utilizzo della funzione snprintf necessita di un’aggiunta al makefile standard utilizzato, in particolare l’introduzione di alcuni comandi specifici per il linker (nella versione in download qui sotto, il makefile è già corretto). I comandi sono questi: -Wl,-u,vfprintf -lprintf_flt -lm

Sezione download:

Uscita su LCD         –    frequenzimetroLCD

Uscita su RS232      –     freq_RS232

Questa voce è stata pubblicata in Tech Corner e contrassegnata con , , , , , , , , , , . Contrassegna il permalink.