Mini frequenzimetro: dove sbaglio?

Ciao a tutti

Sto sperimentando un piccolo frequenzimetro da inserire all'interno di una radio FM con sintonia manuale. Devo leggere la frequenza dell'oscillatore locale precedentemente divisa per 64 (quindi intorno a 1,5MHz) e visualizzare il valore reale. Effettuo la lettura ogni 100ms, quindi per avere gli Hz dovrò moltiplicare il valore letto per 640. Naturalmente dovrò anche sommare o sottrarre il valore della prima frequenza intermedia.

Poiché voglio tenere liberi i pin D0...D7, che mi serviranno per i cinque display a 7 segmenti, uso il Pin Change Interrupt su PB0. Non capisco perché, però, appena applico un segnale non leggo più nulla dalla seriale (che sto usando per le prove). Ho anche provato a mettere un cli() prima della scrittura sulla seriale e poi un sei(), ma fa la stessa cosa...

Per queste prove sto generando segnali con un AD9850, uscita a onda quadra 0-5V del comparatore.

Mi potete aiutare?

Grazie
Gianluca

volatile uint32_t contatore;
uint32_t t_disp; // Per la visualizzazione ogni 100ms.
uint32_t f=0; // Frequenza da visualizzare in kHz (quindi da 87500 a 108000)

ISR(PCINT0_VECT)
{contatore++;}

void setup()
{ // Ingresso: PB0, PCINT0, I/O 8
pinMode(8, INPUT);
PCICR |=0x01; // Attivo il PCMSK0 (PCIE0).
// Equivalente: PCICR|=_BV(PCIE0);
PCMSK0|=0x01; // Abilito il bit 0 del PCMSK0 (PB0). 
// Equivalente: PCMSK0|=_BV(PCINT0);
sei(); // Abilito gli interrupt.
Serial.begin (9600);
t_disp=millis();
}

void loop()
{
if(millis()-t_disp>=100)
  {
  // f=contatore*10/2*64/1000 in 1/10s con prescaler=64, contando sia i fronti di salita che di discesa, in kHz.
  f=contatore*32/100;
  contatore=0;
  t_disp+=100;
  Serial.println(f);
  }
}
ISR( PCINT0_vect ) {
      contatore++;         // volatile uint32_t
}

void setup() { 
    // Ingresso: PB0, PCINT0, I/O 8
    pinMode(8, INPUT);

    cli();      // disable interrupt first

    PCICR |= 0x01;      // Enable PCINT0
    // Equivalente: PCICR|=_BV(PCIE0);

    PCMSK0|= 0x01; // Abilito PIN_CHANGE on (PB0). 
    // Equivalente: PCMSK0|=_BV(PCINT0);

    sei();                  // Abilito gli interrupt.
    
    Serial.begin (9600);
    t_disp=millis();
}

Prova e fa sapere.
Ciao.

1 Like

Grazie, Maurotec

Con vect minuscolo funziona (anche senza il cli() iniziale), però arriva solo fino a 120kHz in ingresso, indicando 7600 (ma sto usando Arduino Uno, mentre poi userò un quarzo vero)... Perché non va oltre?...
Tempo fa avevo fatto qualche prova e leggeva fino a 6MHz, mi sembra, però non trovo più il programma...

Così legge direttamente la frequenza una volta al secondo fino a circa 120kHz (prima legge un po' meno, poi di più e oltre 131kHz non legge più nulla):

volatile uint32_t contatore;
uint32_t t_disp; // Per la visualizzazione ogni 100ms.
uint32_t f=0; // Frequenza da visualizzare in kHz (quindi da 87500 a 108000)

ISR(PCINT0_vect)
{contatore++;}

void setup()
{ // Ingresso: PB0, PCINT0, I/O 8
pinMode(8, INPUT);
cli();
PCICR |=0x01; // Attivo il PCINT0.
// Equivalente: PCICR|=_BV(PCIE0);
PCMSK0|=0x01; // Abilito il PIN_CHANGE su PB0. 
// Equivalente: PCMSK0|=_BV(PCINT0);
sei(); // Abilito gli interrupt.
Serial.begin (9600);
t_disp=millis();
}

void loop()
{
if(millis()-t_disp>=1000)
  {
  // f=contatore*10/2*64/1000 in 1/10s con prescaler=64, contando sia i fronti di salita che di discesa, in kHz.
  f=contatore>>1;
  contatore=0;
  t_disp+=1000;
  Serial.println(f);
  }
}

Si ok visto che il core lib di default abilita gli interrupts globali prima di abilitare un interrupt e bene fare un cli() configurare i registri e poi sei().

Qui mi hai perso, non ti seguo più.
7600 cosa?
120kHz cosa?

6MHz?? cioè entri un onda quadra a 6MHz su PB0?

Portami per mano passo passo.

Comunque puoi provare con un contatore di un byte la ISR risulta più veloce di 4 volte. Ovviamente non puoi campionare ogni secondo.

Può essere che usavi INT0 rising o falling?

Ciao.

Chiedo / propongo: perchè non usare il timer come contatore con clock esterno? Non chiedermi come perchè non sono molto pratico, ma nel datsheet trovi sicuramente come fare.

Ciao, Ale.

1 Like

7600 viene dai calcoli; 120kHz è la frequenza che applico su PB0 (I/O8).
Nell'ultimo programma ho tolto i calcoli che dovrò fare e leggo la frequenza contando per un secondo. Naturalmente devo dividere per due, perché il conteggio è sia sui fronti di salita che su quelli di discesa.

Perché sogno di farlo, ma 'n'so' bono!!! :slight_smile:
Mi piacerebbe, ma il problema di Arduino è che è quasi impossibile trovare in rete qualche cosa fatta bene da chi sa veramente... Dovrei copiare senza capire e credo che, comunque, dovrei applicare il segnale sulla porta D, che voglio tenere libera per i display.

Se ti chiedo di prendemi per mano vuole dire, mostrami sti calcoli da dove viene fuori 7600, perché a me viene counter = Fx2 e quindi F=counter/2. Quindi counter dovrebbe valere 240000 e quindi fai 240000/2 = 120000Hz.

PS: devo trovare forma di ricatto per estorcerti i passaggi oscuri? :grinning:

Si c'è il pin ICP (Input Capture) e su TIMER1 possiamo contare a 16 bit.

Ciao.

Te l'ho spiegato ed è anche scritto nei commenti... :slight_smile:
f=contatore*10/2*64/1000
in 1/10s (*10) con prescaler=64 (*64), contando sia i fronti di salita che di discesa (/2), in kHz (/1000).

Nell'ultimo programma, però, leggo direttamente la frequenza.

Però devo entrare da PD5... :frowning:

mmm...con che frequenza entri su PB0 quando noti il problema. Comunque alza il baud rate e vedi se la seriale inizia a inviare qualcosa.

Si se vuoi fare avanza il contatore TCNT1 dall'esterno, invece ICP1 si trova proprio su PB0.

Ciao.

Prova a cercare nel forum di Nick Gammon, nella sezione dedicata ai timers c'è più di qualche esempio molto ben commentato.

Non posso usare ICP1, perché registra i tempi degli eventi, quindi va bene per tempi molto maggiori della frequenza di clock. Io, invece, devo contare gli eventi in un certo tempo tenendo libera la porta D.
P.s.: Non hanno pensato a mettere un changeover nell'atmega328p per configurare diversamente i pin, ad esempio proprio per liberare la porta D??? Bastavano una manciata di mosfet, trascurabili rispetto a tutto il resto...

Posso consigliarti di leggerti con attenzione questi due vecchi articoli del Prof. Menniti (vecchio utente di questo forum, autore di parecchi progetti) :slight_smile: :

Guglielmo

Entra su PD5... :slight_smile:
...e pure il "prof" mi sembra che non sia un genio dei microcontrollori e della programmazione! Su Nick Gammon non apro bocca...

Se è indispensabile, posso anche usare il PD5: mi restano PD0,1,2,3,4,6,7 per i 7 segmenti, però che succede se vado a scrivere con PORTD anche su PD5? Se fa danni, anziché scrivere tutto insieme devo fare un |= per portare le uscite a 1 e un &= per portarle a 0...
Però vorrei capire perché come faccio io non va oltre i 120kHz e se accade perché ho sbagliato qualcosa...

Azzardo, potrebbe avere a che fare con i tempi di salvataggio del contesto, esecuzione della ISR e ripristino?
Con 120Khz hai che la funzione ISR viene chiamata più o meno ogni 8uS

1 Like

Giustamente vuoi dire che ci mette qualche microsecondo per fare il salto, qualche altro per incrementare contatore, qualche altro per saltare indietro e con 16MHz di clock non può fare di meglio, quindi l'unica soluzione è un contatore hardware interno...

Si è quello che intendevo. Bisognerebbe vedere l'assembler che il compilatore tira fuori e fare i conti con il numero di istruzioni / cicli clock necessari per l'esecuzione di tutto l'ambaradan (se non sbaglio su AVR dovrebbero essere 2 cicli per istruzione, almeno per quelle più comuni).
Comunque secondo me l'ordine di grandezza è più o meno quello.

1 Like

Questo, entrando su PD5, funziona senza problemi fino a 7,9MHz:

/*
Based on Frequency Counter Arduino Sketch
by: Jim Lindblom
For the LiquidCrystal library, much thanks to:
David A. Mellis
Limor Fried (http://www.ladyada.net)
Tom Igoe

D5 - Frequency input 1, Counter 1, 16 bit
*/
unsigned int tovf1 = 0;
unsigned long f = 0;
unsigned long f1 = 0;
unsigned long temp = 0;

// Timer 1 is our counter
// 16-bit counter overflows after 65536 counts
// tovfl will keep track of how many times we overflow.

ISR(TIMER1_OVF_vect)
{tovf1++;}

void setup()
{
pinMode(5, INPUT); // This is the frequency input
// Timer 1 will be setup as a counter
// Maximum frequency is Fclk_io/2
// (recommended to be < Fclk_io/2.5)
// Fclk_io is 16MHz
TCCR1A = 0;
// External clock source on D5, trigger on rising edge:
TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10);
// Enable overflow interrupt
// Will jump into ISR(TIMER1_OVF_vect) when overflowed:
TIMSK1 = (1<<TOIE1);
Serial.begin(9600);
}

void loop()
{
// Delay a second. While we're delaying Counter 0 is still
// reading the input on D5, and also keeping track of how
// many times it's overflowed.
delay(1000);

f=(TCNT1H<<8)|TCNT1L;
f=(TCNT1H<<8)|TCNT1L;

// Correct weird counter bug
// A small range of frequencies (~30k-50k) are getting
// 42949 appended to the front of them
// Will look into this but this works for now
if(f>40000000)
f-=4294900000;

// 65536 (2^16) is maximum of counter
// We'll multiply that by how many times we overflowed
temp=65536*(unsigned long)tovf1;

// Add the overflow value to frequency
f+=temp;

// Print the proper amount of spaces to make it look pretty
Serial.println(f);

// Reset all counter variables and start over
TCNT1H = 0;
TCNT1L = 0;
tovf1 = 0;
}