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);
}
}
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.
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.
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!!!
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?
Si c'è il pin ICP (Input Capture) e su TIMER1 possiamo contare a 16 bit.
Te l'ho spiegato ed è anche scritto nei commenti... 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.
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) :
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;
}