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