Go Down

Topic: Frequenzimetro (Read 21702 times) previous topic - next topic

menniti


se implementi l'anti-bounce in hardware hai fatto cinquina :)

pensavo di aggiungere una funzione col controllo di un interrupt, ogni pressione cambia lo stato dei due pin OUT, non deve fare altro.
Se mi dici che questo complica la vita alla lettura il debounce hw lo posso anche aggiungere, ma ci sta che quando cambio un ingresso devo fare almeno un paio di misure prima di avere una frequenza corretta, funziona così anche su strumenti professionali, quindi i 100-200ms di ritardo introdotti dalla funzione non mi creano problemi.
Manuale "Arduino e le tecniche di programmazione dei microcontrollori ATMEL"
http://www.michelemenniti.it/manuale_di_programmazione.html
http://www.michelemenniti.it/offerta.html
Articoli ElettronicaIN
http://www.michelemenniti.it/elettronica_in.html

brainbooster

#31
Oct 25, 2012, 10:41 am Last Edit: Oct 25, 2012, 10:51 am by BrainBooster Reason: 1
ma, visto che hai comunque dell'hardware esterno non ci metti nulla a fare un debouncer hardware con una porta logica un condensatore una resistenza, così eviti che qualche routine timerdipendente dia fastidio quando non deve...
nelmio caso, ho notato però che la precisione decresce notevolmente man mano che ci si allontana dal tempo di gate prefissato, cioè nel mio caso io leggo una volta al secondo e conto i ticks , questo non andrebbe bene per le frequenze sub-hertz e nello stesso tempo con le frequnze troppo alte un numero eccessivo di tick in un secondo potrebbe overfloware i 4,294,967,295 facilmente con risultati astratti!
quindi ci vorrebbe una sorta di "autorange" che adatti il tempo di scansione alla frequenza che si sta misurando, io stavo pensando di usare la tecnica delle approssimazioni successive, voi non avete riscontrato questo problema?

lestofante

@menniti: se usi in unterrupt d ingresso, quando premi il tasto ti ritrovi decine se non centinaia di interrupt da gestire, i quali romperebbero le balle agli interrupt del contatore (o viceversa dipende dalla priorità) ricorda che hai perso il timero 0 e 2, quindi per fare il debounce SW o usi il timer1 o ti basi sul numero di loop/s (la frequenza del loop!) ma devi ricalcolarlo ogni pezzo di codice che aggiungi.

@BrainBooster: ottima osservazione. Ma se il tempo di lettura è 1s, stai tranquillo che i 4 miliardi non li superi, dato che la frequenza massima che puoi leggere è 8 milioni di Herz. Quindi il problema si pone nella misurazione di frequenze over-Herz.
Dato che la micros ha un tempo di overflow intorno ai 10 minuti, se ricordo bene, la frequenza massima misurabile è 6 volte l'ora, ovvero 1/(6*60*60) = 4,6*10^-6 Hz, ovvero 0.000046296Hz (con 296 periodico)

se la tua frequenza è maggiore, puoi salvarti anche il valore di millis, e poi con un pò di matematica più o meno complessa ricavare anche la precisione micros (conosci i micros al tempo X, e i micros al tempo Y, e conosci il valore di overflow. Quindi calcoli a quanti micros() saresti se fosse scattato esattamente adesso il tempo Y, sottrai questo valore al valore micros che hai letto, e trovi il discostamento in micros dal tampo millis attuale)
A questo punto puoi leggere frequenze di massimo 1 volta ogni 50anni circa (se ricordo bene il tempo di overflow di millis è 50 anni).
poi se non ti basta ancora puoi contare il numero di overflow del timer millis per ottenere i secondi, e via così...

Però a questo punto il vero problema diventa la precisione del clock, che come ben sappiamo sballa di parecchi secondi al giorno (per maggiori info leggiti il thread di leo sulla sua swRTC), in oltre la micros è molto più errata della millis visto che non usa i timer ma un sistema un pò "losco", oltre al fatto che ha una precisione minima di +-2, ovvero si incrementa di 4micros (durata della funzione stessa!)

Nella realtà, se sati usando il mio metodo ti salvi in corner: poichè conosci la durata del segnale alto/basso in micros (quindi max 10 minuti), ogni volta che fai una lettura puoi usare quetso valore nel loop per decidere quanto sarà il tempo di campionamento. In questo modo eviti il problema delle approssimazioni.
Guida per principianti http://playground.arduino.cc/Italiano/newbie
Unoffical Telegram group https://t.me/genuino

brainbooster

lesto non ho capito quasi una beneamata.... spiega meglio  :smiley-mr-green:

lestofante

dimmi cosa spiegare meglio. In pratica ho trovato che per un uso "normale", ovvero dai ~8MHz ai 0.000046296Hz non hai problemi al di fuori della qualità del quarzo
Guida per principianti http://playground.arduino.cc/Italiano/newbie
Unoffical Telegram group https://t.me/genuino

brainbooster

...e scrivi due righe di codice che possiamo provare no? :)

leo72


se ricordo bene il tempo di overflow di millis è 50 anni

lesto posa i'fiasco, come si dice da me  :smiley-yell: :smiley-yell:
L'overflow di millis avviene ogni 49,7 GIORNI. A che stavi pensando?  ;)

PS:
ho letto solo di sfuggita il tuo intervento, però ricordati che il core di Arduino "gioca" con il timer 0, millis e micro, nel senso che usano una frequenza del timer di 976 Hz per cui utilizzano poi un contatore di overflow per introdurre un valore di correzione e riportare la durata di 1000 ms ad 1 secondo. Anche per micros adottano una formula matematica, e non so se ci sono correzioni anche lì per cui il tempo misurato può in alcuni casi essere diverso da quello reale.

lestofante

#37
Oct 25, 2012, 11:45 pm Last Edit: Oct 25, 2012, 11:48 pm by lesto Reason: 1
la micros è scritto sul reference che sballa dopo pochi minuti, molto prima dell'overflow.

Quote
lesto posa i'fiasco, come si dice da me

è che non avevo voglia di controllare e sono andato "a naso". non avrò beccato l'unità di tempo ma almeno il numero :)

Code: [Select]
...e scrivi due righe di codice che possiamo provare no?
per cosa? per l'overflow? il codice l'ho già postato qui: http://arduino.cc/forum/index.php/topic,128514.msg969617.html#msg969617

per il tempo di lettura automatico? ma abbiamo capito che non ti serve se vuoi leggere frequenze con durata maggione di 70 minuti (overflow micros)... ok facciamo così, ma niente tempo automatico che mi pare una cosa inutile, se non hai un RTC

Code: [Select]

#define MAX 10 //una define non ha bisogno di essere volatile!

unsigned long previousMillis = 0;  //variabile per controllo tempo di scan
long interval = 1000;  //intervallo invio letture

volatile byte contatore = 0;
volatile unsigned long lastDuration[MAX];
volatile unsigned long hticks=0, lticks=0;
volatile int stato=-1;

void setup()
{
  attachInterrupt(2, quicfunc, CHANGE);
  Serial.begin(9600);
  lastDuration[0] = -1; //valore di "tappo"
}

unsigned long tempL, tempH;
void loop()
{
 
 if(millis() - previousMillis > interval) {  //se è trascorso l'intervallo impostato
   boolean tmpStato=-1;
   unsigned long totale, TMPduration[MAX];
   byte TMPcontatore;
   detachInterrupt(0); //feeeerma tutto, ma facciamo in fretta!
   //fermo tutto per fare in modo che le variabili non mi cambino da sotto il naso
   //in particolare nella copia dell'array
   tempL = lticks;
   tempH = hticks;
   
   lticks=0;
   hticks=0;
   
   totale=tempL+tempH;
   
   TMPcontatore = contatore;
   for (int i=0 ;i < min(10, totale); i++){
     TMPcontatore=TMPcontatore<MAX-1?TMPcontatore+1:0;
     TMPduration[i] = lastDuration[TMPcontatore];
   }
   tmpStato = stato;
   lastDuration[ contatore<MAX-1?contatore+1:0 ] = previousMillis = millis(); //resetto il tempo, prendi il dato più aggiornato possibile, notare che faccio in modo che ciò non rovini troppo il primo impulso
   
   attachInterrupt(0, quicfunc, CHANGE); //RRRRRIIIIPARTIIII
   
   /*
   ok, ora possiamo perdere tutto il tempo che vogliamo con la serial! (bhe max 950 caratteri, se il buad è 9600... mi sa che sforo!)
   */
   
   Serial.print("Il primo impulso dei seguenti è ");
   if (TMPcontatore%2==tmpStato){ //little mindfuck: se il contatore è pari, allora l'impulso è dello stesso segno di stato (true = HIGH. false=LOW)
      Serial.println("LOW");
   }else{
      Serial.println("HIGH");
   }
   
   for (int i=0 ;i < min(10, totale); i++){
     Serial.print("durata impulso ");
     Serial.print(i);
     Serial.print(": ");
     Serial.print(TMPduration[i]);
   }
   Serial.print("numero ticks Hi= ");
   Serial.println(tempH);
   
   Serial.print("numero ticks lo= ");
   Serial.println(tempL);
   
   Serial.print("frequenza (Hz)= ");
   Serial.println((totale/2));
 }
}

void quicfunc() {
 lastDuration[ contatore<MAX-1?contatore+1:0 ] = millis(); //inizializza il tempo di inizio impulso del PROSSIMO segnale
 lastDuration[contatore] = millis()-lastDuration[contatore]; //calcola la durata del segnale attuale
 contatore = contatore<MAX-1?contatore+1:0;
 
 if (stato==-1) //do the read only one time
    stato = digitalRead(2)==HIGH?true:false;

 if (stato){
   hticks++;
   stato=false;
 }else{
   lticks++;
   stato=true;
 }
}


uff mezzora per scrivere sta roba... devo smeterla di usare l'ide arduino. Il codice NON è testato, semplicemente perchè son stanco e non ho voglia di disfare il circuito che sto testando. kiss
Guida per principianti http://playground.arduino.cc/Italiano/newbie
Unoffical Telegram group https://t.me/genuino

menniti

Posto che approfondiremo al momento opportuno, sto cercando di capire che problema reale potrebbero crearmi due pulsanti:
Il primo serve per commutare uno tra i tre ingressi, per regola devo premerlo mentre non sto misurando nulla e solo dopo la selezione dell'ingresso vi attacco un segnale o, al massimo, il segnale c'è già, ma una misura di frequenza non è una cosa istantanea, personalmente la osservo almeno 4-5 secondi, quando ormai i vari interrupt dovrebbero essere tutti serviti no?
Il secondo serve per impostare la base dei tempi, cioè l'intervallo di misurazione, in questo caso il segnale è già sull'ingresso corretto ed io uso queste "portate" per avere una maggiore risoluzione o una maggiore velocità di lettura, ma anche in questo caso metto in conto che dopo la pressione devo aspettare 2-3 letture, ma vi assicuro che questo succede normalmente nei frequenzimetri fino alla fascia media, è praticamente impossibile che un frequenzimetro dia la corretta lettura al primo ciclo di clock.
Ho un paio di NOT liberi ma me ne servirebbero 2 per pulsante, a questo punto aggiungerei un integrato debounce hardware tipo il MAX6817 e risolvo in modo egregio.
Ma anche dopo aver usato questo metodo in qualche modo devo leggerli i pulsanti no? non lo perdo lo stesso il tempo macchina per "contare" il numero delle pressioni e memorizzare lo stato attuale per la prossima pressione? Se realizzo il debounce software con millis non miglioro il comportamento?
Manuale "Arduino e le tecniche di programmazione dei microcontrollori ATMEL"
http://www.michelemenniti.it/manuale_di_programmazione.html
http://www.michelemenniti.it/offerta.html
Articoli ElettronicaIN
http://www.michelemenniti.it/elettronica_in.html

astrobeed


Posto che approfondiremo al momento opportuno, sto cercando di capire che problema reale potrebbero crearmi due pulsanti:


Non ti danno nessun problema, tanto la prima lettura, dopo aver premuto un pulsante, è sempre da buttare via perché non attendibile, e vale per tutti i frequenzimetri di questo mondo, pure quelli che costano cifre con tre zeri, non ti stare a creare problemi che non esistono.
Il modo corretto per realizzare un frequenzimetro con l'AVR è quello che stai utilizzando, ovvero contare tramite un timer in modalità counter il numero di impulsi ricevuti, tenendo nel frattempo conto degli overflow, in un tempo prefissato, tipicamente 0.1 ,1 , 10 secondi, in alternativa, solo per le frequenze basse, il periodo tra due impulsi in modo da aumentare la risoluzione e ridurre il tempo di misura.
Scientia potentia est

menniti

Grazie, sto facendo le ulteriori prove, avevo dimenticato  :~ che oggi sono di compleanno e mi sono preso mezza giornata libera, speriamo bene!
Manuale "Arduino e le tecniche di programmazione dei microcontrollori ATMEL"
http://www.michelemenniti.it/manuale_di_programmazione.html
http://www.michelemenniti.it/offerta.html
Articoli ElettronicaIN
http://www.michelemenniti.it/elettronica_in.html

lestofante


Ma anche dopo aver usato questo metodo in qualche modo devo leggerli i pulsanti no? non lo perdo lo stesso il tempo macchina per "contare" il numero delle pressioni e memorizzare lo stato attuale per la prossima pressione? Se realizzo il debounce software con millis non miglioro il comportamento?


ricordati che col sistema del timer0 contatore perdi la millis! al massimo puoi usare la micros() (da verificare)

è il tuo compleanno? cento di questi arduini  :)
Guida per principianti http://playground.arduino.cc/Italiano/newbie
Unoffical Telegram group https://t.me/genuino

brainbooster

ho cambiato totalmente approccio al problema e ho riscritto tutto per funzionre con pcint e misurare il dutycycle

Code: [Select]

#include <PinChangeInt.h>
volatile unsigned long previousMillis = 0;  //variabile per controllo tempo di scan
long interval = 1000;  //intervallo invio letture
boolean Flag;
volatile unsigned long startTimeH;
volatile unsigned long stopTimeH;
volatile unsigned long tdurataHIGH;
volatile unsigned long startTimeL;
volatile unsigned long stopTimeL;
volatile unsigned long tdurataLOW;
volatile unsigned long periodo;
float frequenza;
float dutycycle;
void setup()
{
  PCintPort::attachInterrupt(A2, &quicfunc, CHANGE);
  Serial.begin(9600);
}

void loop()
{
  unsigned long currentMillis = millis(); //prendo il tempo
  if(currentMillis - previousMillis > interval) {  //se è trascorso l'intervallo impostato

    previousMillis = currentMillis;  //resetto il tempo



    Serial.println();

    Serial.print("Durata High (uS)=");
    if (stopTimeH > startTimeH){
      tdurataHIGH= stopTimeH - startTimeH;
    }
    else{
      tdurataHIGH= startTimeH - stopTimeH;
    }
    Serial.println(tdurataHIGH);
    Serial.print("Durata Low (uS)=");
    if (stopTimeL > startTimeL){
      tdurataLOW= stopTimeL - startTimeL;
    }
    else{
      tdurataLOW= startTimeL - stopTimeL;
    }
    Serial.println(tdurataLOW);

    Serial.print("Periodo1 (uS)= ");
    periodo= tdurataLOW + tdurataHIGH;

    Serial.println(periodo);
    Serial.print("Frequenza (Hz)= ");
    frequenza = (1.0/(periodo));

    Serial.println((frequenza*1000000),4);
    Serial.print("Duty Cycle (%)= ");
    dutycycle =( float(tdurataHIGH) / float( periodo ))*100.0;
    Serial.println(dutycycle,4);
    Serial.println();
  }
}


void quicfunc() {
  Leggi_Rapido_A2();
  if (Flag==1){
    startTimeH=micros()-4; //tolgo i 4 micros che si mangia la funzione
    stopTimeL=micros()-4;
    ;
  }
  else{
    stopTimeH=micros()-4;
    startTimeL=micros()-4;
    ;
  }
}

void Leggi_Rapido_A2()  //lettura ultrarapida dello stato del pin A2
{
  char pincState = 0;
  pincState = PINC2;
  if ( ((PINC>>PINC2) & 1) ) { Flag = true; } else { Flag = false; }
}

Provatelo se potete ;)

lestofante

stai toglieno -4 siqa dal tempo di partenza che quello di fine, uindi allas fine i - si compensano. insomma non devi tenerne conto :)
Guida per principianti http://playground.arduino.cc/Italiano/newbie
Unoffical Telegram group https://t.me/genuino

brainbooster

#44
Oct 26, 2012, 12:06 pm Last Edit: Oct 26, 2012, 12:07 pm by BrainBooster Reason: 1
vero :)  che pir..
del resto che ne dici?
ti piace la lettura rapida della porta al posto del digital read?
digital read era un elefante nell'interrupt :D

Go Up