Frequenzimetro Digitale fino a 65KHz

icona frequenzimetroFrequenzimetro Realizzato con un Microcontrollore, Dispone di Due Scale e Permette di Misurare Segnali Sinusoidali, Triangolari ed ad Onda Quadra…


 
 
 
 




 

INTRO

Per realizzare un frequenzimetro preciso e affidabile si utilizzano in questo caso due differenti scale di precisione. La prima va da 2Hz a 2000Hz, la seconda va da 2000Hz a 65KHz. Per selezionare in quale range effettuare la misura vi sarà un interruttore. Per il primo range si va ad utilizzare il modulo CCP e si va a misurare il periodo del segnale, come visto nel progetto del contagiri, per il secondo range si vanno a contare i periodi all’interno di un tempo stabilito. Quindi il secondo range funziona anche con frequenze basse, solo che non restituisce un valore preciso, conviene quindi utilizzare sempre il range massimo e poi scendere al range minore se la frequenza misurata è piccola.  Nel primo range si ha la frequenza istantanea del segnale, nel secondo range si fa una misura su un quarto di secondo, quindi per avere una misura attendibile il segnale deve avere una frequenza stabile se si usa il secondo range.

Questo frequenzimetro permette inoltre di misurare qualsiasi segnale elettrico, come ad esempio onde quadre, sinusoide, denti di sega e onde triangolari. Inoltre grazie alla protezione permette di misurare tensioni anche elevate (in base al diodo Zener e resistenza di protezione che si usa) con ovviamente un limite sull’ampiezza minima che deve essere almeno di 2V di picco. La tensione minima in ogni caso non è un limite perché basta aggiungere un amplificatore.

Purtroppo dispongo della versione demo di MikroC, per questo motivo questo codice si ferma a 65KHz. Se si dispone della versione originale è possibile realizzare un frequenzimetro fino a 5MHz.

 




 

PRINCIPIO DI FUNZIONAMENTO E CODICE

Per il range 2-2000Hz il funzionamento è identico al contagiri, solo che questa volta, dopo aver contato il tempo tra due fronti, si va semplicemente a dividere 1/(tempo tra due fronti) senza moltiplicare per 60. Anche in questo caso vi sarà l’autoset del prescaler per lavorare nella migliore condizione.  Per il range 2000Hz-65KHz invece si va ad usare il timer counter 0 con prescaler 1 e con clock proveniente dall’esterno, e sarà proprio il segnale generato grazie al segnale di ingresso a fornire il clock al timer counter. Quindi si va semplicemente ad accende il timer counter, si va a resettare e si attendono 250ms. dopo di che si spegne il timer counter, si preleva il valore e lo si moltiplica per 4 cisto che la frequenza è definita come il numero di periodi in un secondo.

Per selezionare in quale range si vuole effettuare la misura vi sarà un interruttore connesso a massa con resistenza di pull-up; se chiuso effettuo la misura con il timer counter 0, altrimenti con il modulo CCP.

Se la frequenza è maggiore di 65KHz compare una segnalazione sul display LCD, se invece è minore di 2Hz si accende il LED di segnalazione per segnalare che il timer counter 1 è andato in overflow, quindi la frequenza è molto bassa.

Il codice è il seguente:

sbit LCD_RS at RB0_bit;                 //Definizione libreria LCD
sbit LCD_EN at RB1_bit;
sbit LCD_D4 at RB2_bit;
sbit LCD_D5 at RB3_bit;
sbit LCD_D6 at RB4_bit;
sbit LCD_D7 at RB5_bit;                 //Fine definizione libreria LCD

sbit LCD_RS_Direction at TRISB0_bit;    //Configurazione I/O per LCD
sbit LCD_EN_Direction at TRISB1_bit;
sbit LCD_D4_Direction at TRISB2_bit;
sbit LCD_D5_Direction at TRISB3_bit;
sbit LCD_D6_Direction at TRISB4_bit;
sbit LCD_D7_Direction at TRISB5_bit;    //Fine configurazione I/O per LCD

unsigned short int Stato;               //variabile che tiene conto del cambio di precisione
float Freq;                             //variabile per convertire da conteggio a Freq
bit E;                                  //bit per eseguire i cambi di stato
unsigned long FreqMin;                  //variabile per contenere il valore di Freq
char TxtFreq[7];                        //testo che contiene i Freq al minuti

#define Range PORTC.F0                  //Tasto per la selezione del range di freq

#define Hi(param) ((char *)&param)[1]       //definizione funzione Hi
#define Lo(param) ((char *)&param)[0]       //definizione funzione Lo


void display(float Freq){               //subroutine per visualizzare il valore dei Freq
  FreqMin = Freq;                       //passo da floating point a intero
  WordToStr(FreqMin, TxtFreq);          //trasformo da intero a stringa
  Lcd_cmd(_LCD_CLEAR);                  //pulisci lcd
  Lcd_out(2, 3, TxtFreq);               //visualizzo la stringa
  Lcd_out(1, 1, " Frequenza [Hz]");      //mostra la stringa "Frequenza [Hz]" sulla riga 1
  T1CON.TMR1ON = 1;                     //riattivo il timer counter
}                                       //fine subroutine

void TMR_INT() iv 0x0008 ics ICS_AUTO { //interrupt di overflow
  PORTB.B7 = 1;                         //accendo il led di overflow
  T1CON = 0b10110001;                   //timer 1 con prescaler 8
  CCP1CON = 0b00000101;                 //capture ogni fronte di salita
  TMR1H = 0;                            //resetto il timer counter 1
  TMR1L = 0;
  CCPR1 = 0;                            //resetto il valore di capture
  PIR1.CCP1IF = 0;                      //reset del flag di capture
  PIR1.TMR1IF = 0;                      //resetto il flag di overflow
  Stato = 1;                            //riporto lo Stato a quello iniziale
}                                       //fine subroutine interrupt

void main() {                  //programma principale

CCP1CON = 0b00000101;          //modalità capture ogni fronte di salita

TRISB = 0;                     //portb uscite
TRISC.B2 = 1;                  //ingresso modulo CCP1
TRISC.B0 = 1;                  //ingresso modulo CCP1
TRISA.B4 = 1;                  //ingresso modulo CCP1
Stato = 1;                     //inizializzo stato=1 (presc 8 e capture ogni fronte)

RCON.IPEN = 0;                 //disabilito la priorità degli interrupt
INTCON.GIE = 1;                //attivo gli interrupt
INTCON.PEIE = 1;               //attivo gli interrupt delle periferie

PIE1.TMR1IE = 1;               //attivo l'interrupt di overflow di tmr1
IPR1.TMR1IP = 0;               //disabilito la priorità dell'interrupt

Lcd_init();                    //inizializza lcd
Lcd_cmd(_LCD_CLEAR);           //pulisci lcd
Lcd_cmd(_LCD_CURSOR_OFF);      //disattiva cursore lcd

T1CON = 0b10110001;            //timer 1 on, clock interno prescaler 8

T0CON = 0b00111000;

while(1){
  while(Range == 1){                          //ciclo infinito
  PIE1.TMR1IE = 1;                            //attivo l'interrupt di overflow di tmr1
    if(PIR1.CCP1IF==1){                       //se si ha l'evento di capture
      PORTB.B7 = 0;                           //resetto il led di overflow
      PIR1.CCP1IF = 0;                        //resetto il flag di capture
      T1CON.TMR1ON = 0;                       //tmr1 off per non avere overflow
      E = 1;

      switch(Stato){                          //switch per selezionare lo stato
        case 1:while(E==1){                   //esegui una volta sola se Stato = 1
                 if(CCPR1 < 32700){           //se stato=1 e valore piccolo
                   T1CON = 0b10100001;        //timer 1 on, clock interno, prescaler 4
                   Stato = 2;                 //stato= 2 (prescaler = 4, capture ogni fronte)
                 }                            //prescaler equivalente 4
                 else{                        //se il valore è opportuno
                   Freq = 125000/CCPR1;      //Freq = (1/tempoconteggio)/CCPR1
                   display(Freq);             //visualizza Freq
                 }
                E = 0;                        //resetta "E" così da non rieseguire
               }

        case 2:while(E==1){                   //esegui una volta sola se Stato=2
                 if(CCPR1 < 32700){           //se stato=2 e valore piccolo
                   T1CON = 0b10010001;        //timer 1 on, clock interno, prescaler 2
                   Stato = 3;                 //stato= 3 (prescaler = 2, captureogni fronte)
                 }                            //prewscaler equivalente 2
                 else{                        //se il valore è opportuno
                   Freq = 250000/CCPR1;       //Freq = (1/tempoconteggio)/CCPR1
                   display(Freq);             //visualizza Freq
                 }
                E = 0;                        //resetta "E" così da non rieseguire
               }
        case 3:while(E==1){                   //esegui una volta sola se Stato=3
                 if(CCPR1 < 32700){           //se stato=3 e valore piccolo
                  T1CON = 0b10000001;         //timer 1 on, clock interno, prescaler 1
                  Stato = 4;                  //stato= 4 (prescaler = 1, capture ogni fronte)
                }                             //prescaler equivalente 1
                else{                         //altrimenti se valore opportuno
                  Freq = 500000/CCPR1;       //Freq = (1/tempoconteggio)/CCPR1
                  display(Freq);              //visualizza Freq
                }
                E = 0;                        //resetta "E" così da non rieseguire
               }

        case 4:while(E==1){                   //esegui una volta sola se Stato=2
                 if(CCPR1 < 32700){           //se stato=4 e valore piccolo
                   T1CON = 0b10110001;        //timer 1 on, clock interno prescaler 8
                   CCP1CON = 0b00000111;      //modalità capture ogni 16 fronte di salita
                   Stato = 5;                 //stato= 5 (prescaler = 4, capture ogni fronte)
                 }                            //prescaler equivalente 0.5
                else{                         //se si ha un valore opportuno
                  Freq = 1000000/CCPR1;      //Freq = ((1/tempoconteggio)/CCPR1
                  display(Freq);              //visualizza  Freq
                }
                E = 0;                        //resetta "E" così da non rieseguire
               }

        case 5:while(E==1){                   //esegui una volta sola se Stato=2
                 if(CCPR1 < 32700){           //se stato=5 e valore piccolo
                   T1CON = 0b10100001;        //timer 1 on, clock interno prescaler 4
                   CCP1CON = 0b00000111;      //modalità capture ogni 16 fronte di salita
                   Stato = 6;                 //stato= 6 (prescaler = 4, capture ogni 4 fronti                 
                   }                          //stato= 2 (prescaler = 4, capture ogni fronte)

                  else{                        //se si ha un valore opportuno
                  Freq = 2000000/CCPR1;        //Freq = (1/tempoconteggio)/CCPR1
                  display(Freq);               //visualizza  Freq
                }
                E = 0;                        //resetta "E" così da non rieseguire
               }

        case 6:while(E==1){                   //esegui una volta sola se Stato=2
                 if(CCPR1 < 32700){           //se stato=6 e valore piccolo
                   T1CON = 0b10010001;        //timer 1 on, clock interno prescaler 2
                   CCP1CON = 0b00000111;      //modalità capture ogni 16 fronte di salita
                   Stato = 7;                 //stato= 7 (prescaler = 4, capture ogni 4 fronti 
                 }                            //prescaler equivalente 0.125
                 else{                        //altrimenti se si ha un valore opportuno
                   Freq = 4000000/CCPR1;      //Freq = (1/tempoconteggio)/CCPR1
                   display(Freq);             //visualizza  Freq
                 }
                E = 0;                        //resetta "E" così da non rieseguire
               }

        case 7:while(E==1){                   //se sono nell'ultimo stato...
               Freq = 8000000/CCPR1;          //Freq = (1/tempoconteggio)/CCPR1
               display(Freq);                 //visualizza Freq
               E = 0;                         //resetta "E" così da non rieseguire
             }
      }

      PIR1.CCP1IF = 0;                        //resetta flag di capture

      while(PIR1.CCP1IF == 0 ){               //se non si ha l'inizio di un ciclo
        TMR1H = 0;                            //rimani bloccato in questo ciclo
        TMR1L = 0;                            //e resetta sempre il timer
      }
      PIR1.CCP1IF = 0;                        //reseta il flag per iniziare un nuovo ciclo
    }//fine if
  }//fine programma range 2-2000Hz
  
  while(Range == 0){                          //se il tasto range è chiuso...
    PIE1.TMR1IE = 0;                          //disattivo l'interrupt di overflow di tmr1
    T0CON.TMR0ON = 1;                         //accendi il timer counter 0
    TMR0H = 0;                                //resetta il timer counter 0 
    TMR0L = 0;
    
    Delay_ms(250);                            //aspetta un quarto di secondo
    T0CON.TMR0ON = 0;                         //spegni il timer
    
    Lo(FreqMin) = TMR0L;                      //inserisci il valore del timer in FreqMin
    Hi(FreqMin) = TMR0H;
    FreqMin = FreqMin * 4;                    //moltiplico per 4 per avere periodi/secondo
    
    if((FreqMin < 65000) && (INTCON.TMR0IF==0)){   //se Freq<65KHZ E non si ha avuto overflow
    
      WordToStr(FreqMin, TxtFreq);                 //trasformo da intero a stringa
      Lcd_cmd(_LCD_CLEAR);                         //pulisci lcd
      Lcd_out(2, 3, TxtFreq);                      //visualizzo la stringa
      Lcd_out(1, 1, " Frequenza [Hz]");             //mostra la stringa "Frequenza [Hz]" sulla riga 1
    }
    
    else if (INTCON.TMR0IF==1){                    //se Freq>65KHz
      Lcd_cmd(_LCD_CLEAR);                         //pulisci lcd
      Lcd_out(1, 3, " Frequenza ");                 //mostra la stringa "Frequenza" sulla riga 1
      Lcd_out(2, 1, " Maggiore 65KHz");             //mostra la stringa "Maggiore 65KHz" sulla riga 1
      INTCON.TMR0IF = 0;                           //resetta flag overflow
    }

   }//fine while range 2000-65KHz
 }//fine ciclo infinito
}//fine

Per visualizzare il valore della frequenza si utilizza un LCD, quindi il primo passo del codice è quello di scrivere la configurazione tra LCD e microcontrollore, e in particolare si utilizza la PORTB. Dopo di che servono alcune variabili, la prima tiene conto di quale prescaler si ha, poi vi è la frequenza in formato floating, una variabile per l’esecuzione di una parte di programma, poi si ha la variabile per il valore convertito della frequenza e infine vi è il testo per visualizzare il valore di della frequenza.

Poi vi è una subroutine che riceve il valore della frequenza in floating, lo trasforma in un numero senza virgola e lo trasforma in stringa, infine questa stringa viene visualizzata sull’LCD insieme alla stringa “Frequenza[Hz]” e infine resetta il valore del timer counter 1.

L’altra subroutine è una subroutine di interrupt,  ovvero dell’interrupt di over flow. Quando si ha over flow si attiva un led che avverte dell’overflow, poi si riporta il prescaler al valore 8 e poi si porta nello stato iniziale e infine  si resettano il timer counter e i flag. Purtroppo nella subroutine di interrupt non è possibile inserire le stringhe per visualizzare l’overflow su LCD, quindi per questo si utilizza il LED.

Nel codice principale si inizializzano le porte, l’interrupt, l’LCD, il timer counter con prescaler 8 e il modulo capture con cattura ad ogni fronte di salita. Inoltre si inizializza il timer counter 0 con clock esterno e senza prescaler. Dopo di che si ha un ciclo infinito.

Nel ciclo infinito si hanno altri due cicli while; il primo viene eseguito se l’interruttore non è chiuso, quindi se si vuole un range tra 2Hz e 2000Hz. Nel primo while si controlla se si ha un evento di capture. Se questo è avvenuto si va a spegnere il timer counter. Dopo di che si va a controllare lo stato, inizialmente si parte da prescaler 8, dopo di che si va a controllare il valore catturato. Se questo è minore di metà del massimo, potrei ottenere una precisione maggiore dimezzando il prescaler, ed è proprio quello che si fa, e se si setta un prescaler 4 si va nello stato 2. Se il valore è opportuno si va a dividere 125000 ((4000000)/4*8) per il valore catturato.

Dopo di che si continua questo controllo finchè non si ha un valore opportuno di prescaler con un valore catturato opportuno.

Purtroppo dentro i case non si riesce ad inserire un if else a meno che non si racchiude tutto in un while. Per fare ciò si utilizza una variabile “E” che viene inizializzata a 1 prima del case e poi viene resettata dopo l’if else.

Finito il case si resetta il flag del modulo capture, visto che durante i processi del case potrebbe avvenire un evento di capture. Finchè il flag è 0 si tiene resettato il timer counter, in questo modo si riesce a contare perfettamente tra due fronti di salita.

Nell’altro while, quello per il range 2000Hz-65KHz, che viene eseguito se il pulsante è chiuso, si va ad attivare il timer counter 0 e si disattiva l’overflow del timer counter 1 visto che per questa parte di codice non serve. Dopo di che si resetta il timer e si aspetta un quarto di secondo prima di spegnere il timer counter e catturare con il codice il valore del timer counter 0. Questo valore si moltiplica per 4 e se non si ha overflow del timer counter 0 e il valore è minore di 65KHz si va a visualizzare il valore su LCD. Se invece si ha overflow si visualizza la stringa per avvertire che la frequenza è elevata.

Il problema del codice sta nella stringa “WordToStr(FreqMin, TxtFreq);                 //trasformo da intero a stringa” che permette di visualizzare solo numeri a 16bit. Se si ha Mikroc originale senza limitazioni si può sostituire questa stringa con la stringa “LongToStr(FreqMin, TxtFreq); per visualizzare numeri maggiori di 650000 e avere quindi frequenze maggiori.

 




 

SCHEMA

Lo schema è il seguente:

schema frequenzimetroPer passare da un qualsiasi segnale periodico, con ampiezza anche molto grande, si utilizza il sistema composto da R5, D1, D2, R4, R3 e dal comparatore LM393. In particolare il diodo Zenner è da 5.1V e insieme alla resistenza R5 fa in modo che la tensione in ingresso al comparatore non sia mai superiore a 5.1V a meno che lo Zenner non si bruci. La resistenza R4 con il diodo D2 crea una tensione di riferimento di 0.7V circa, in modo che ad ogni passaggio da 0.7V della tensione di ingresso venga rilevato il periodo. La resistenza R3 crea la rete di pull-up per il comparatore.

Quindi in uscita dal comparatore si ha un onda quadra con periodo uguale al segnale di ingresso. Se si devono misurare segnali ad onda quadra con ampiezza massima 5V tutti questi componenti non sono necessari e l’ingresso del frequenzimetro diventa l’attuale uscita del comparatore.

Il segnale squadrato giunge al timer counter 0 tramite il pin 6 e al modulo CCP tramite il pin 13. Il circuito di oscillazione è formato da un cristallo da 4MHz e due condensatori da 22pF. L’alimentazione è da 5V e vi è la resistenza R1 da 10K che tiene non resettato il microcontrollore. La resistenza RV1 è la resistenza per il contrasto dell’LCD. I pin di alimentazione del microcontrollore sono i pin 8 e 19 per GND mentre 20 per VCC. Il microcontrollore scelto è il PIC18F252 È inoltre necessario un led con resistenza di protezione sul pin 28 ovvero PB7 se si vuole avere l’informazione di quando si ha overflow.

LCD è connesso al PORTB come da schema e i pin non utilizzati dell’LCD sono connessi a massa. L’interruttore per selezionare la precisione deve avere una azione di latch, ovvero non può essere un pulsante che se rilasciato torna nella posizione di riposo, la resistenza R2 fa da rete di pull-up per avere un segnale alto quando l’interruttore è aperto.

frequenzimetro su breadboard

Questo è lo schema montato su breadboard. per testarlo è stato inserito un multivibratore astabile realizzato con un NE555. Per la programmazione del PIC e la configurazione dei fuses potete leggere la precedente guida sulla programmazione, oppure settare tutti i fuses disabilitati, tranne il Brownout reset.

 

DOWNLOAD

Per il download del codice, dell’HEX da inserire nel microcontrollore con i fuses già settati e per la simulazione cliccate sul seguente LINK!!!





 

[Voti Totali: 0 Media Voti: 0]
Segui la Nostra Pagina Facebook: Facebook

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *