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.

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.

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

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.

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;
}