Frequenzimetro

no, dico che è la stessa cosa per i tuoi fini, però dico che è meglio accorpare la logica in una sola funzione generica sia per RISING che per FALLING.

così se devi modificare qualcosa in un colpo solo lo fai per entrambi i picchi, evitando stupidi errori di copia-incolla che fanno solo perdere tempo.

poi trovo che la stesura del codice va sempre fatta con un occhio di rigurado al fine che si vuole ottenre, si chiama architettura.

chiaramente hai ragione quando parli di architettura, ma l'avevo detto all'inizo, è solo un primo abbozzo della logica, poi tutto si può migliorare :slight_smile:
quindi in pseudocodice (anche non pseudo :slight_smile: ) tu che faresti?
edit:
... pensavo che se uso l'evento change, poi non devo andare a leggere "a mano" lo stato del pin introducendo ulteriore ritardo?

Rifatto e semplificato con pinchange interrupt

#include <PinChangeInt.h>
volatile unsigned long previousMillis = 0;  //variabile per controllo tempo di scan
long interval = 1000;  //intervallo invio letture
volatile unsigned long hticks=0;
volatile unsigned long  lticks=0;

void setup()
{ 
  PCintPort::attachInterrupt(2, &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
    unsigned long totale=hticks+lticks;
    Serial.print("numero ticks Hi= ");
    Serial.println(hticks);
    
    Serial.print("numero ticks lo= ");
    Serial.println(lticks);
    
     Serial.print("frequenza (Hz)= ");
    Serial.println((totale/2));
     Serial.print("periodo (uS)= ");
    Serial.println(((1.0/hticks)*1000000.0),4);
    hticks=0;
    lticks=0;
  }}
  
  
  void quicfunc() {
  if (digitalRead(2)==HIGH){
  hticks++;}
  else{
  lticks++;
  }
}

legge bene fino a da 1 a 15000hz (limite dell'oscillatore che uso per le prove)
che ne dite?

il llimite della digitalRead è di 145KHz. Se usi direttamente i registri, sali a 4MHz

more info:
http://www.billporter.info/ready-set-oscillate-the-fastest-way-to-change-arduino-pins/

piccola chicca per il tempo:

#define byte MAX 10 //una define non ha bisogno di essere volatile!
volatile byte contatore = 0;
volatile unsigned long lastDuration[MAX];

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 (digitalRead(2)==HIGH){
    hticks++;
  }else{
    lticks++;
  }
}

NON È TESTATO! ma in pratica riempo un array delle ultime 10 durate di impulso (però non c'è modo per sapere se il primo impulso è alto o basso, potresti usare un boolenao di quelli che avanzano per indicare se la lettura in posizione 0 è high o low e da li ricavare il resto)
al posto di un int e dei vari if, potresti usare una union, quindi crei una variabile di X bit (e quindi che con l'overflow va a 0 da sola) e sfruttare uno dei bit che ti avanza al posto della booleana.
ottimizzazione spinta, senza andare troppo nel basso livello :slight_smile:

Lesto, può essere che ciò che dico sia a sproposito in questa discussione, e mi pare peraltro di averlo già scritto. Io e te abbiamo parlato tempo fa di una lib, che a te non piace molto (ma le motivazioni erano troppo ostiche per le mie misere conoscenze), ma con la quale io leggo senza problemi fino a poco più di 7MHz, riferendomi ad un segnale prodotto da un Generatore.
Questa mi affermazione contrasta con la tua (4MHz con digitalRead) o sono due cose differenti?

sì ricodo ma non ricordo la libreria. forse era la digitalWriteFast?

un alibreria non può essere più veloce del semplice codice, per il semplice fatto che si perdono clock nel salto a funzione (e non credo proprio che la cosa sia ottimizzata nel compilatore)

ho controllato, e il link da cui ho preso i dati si parla di SCRITTURA, mentre noi parliamo di lettura.

quindi ho cercato nei menadri del forum: http://arduino.cc/forum/index.php/topic,47106.0.html

PIND & B00000010

impiega 2 cicli macchina, quindi frequenza massima di 8MHz con quazo a 16MHz

edit: e comunque non stiamo tenedo conto del coide di contorno, mooolto più pesante.

E' una lib specifica per far funzionare Arduino come frequenzimetro, ho dovuto metttere mano ad alcuni parametri, forte del fatto che avevo strumentazione seria per i confronti, ma alla fine ho letto oltre 7MHz senza problemi, visualizzandoli su un LCD; l'avevo vista sul tuo sito e ti ho chiesto se l'usavi anche tu e mi hai risposto che non ti piaceva, comunque parliamo sempre e comunque di lettura e misurazione, NON di scrittura

ah, ho capito, parli della freqcounter di Martin Nawrath.. in pratica usa il timer0 come contatore, per un intervallo di tempo deciso da timer2.

per questo raggiunge la precisione di una pura lettura digitale (2 clock circa). In effetti quel codice è più ottimizzato dell'uso dell'interrupt, ma ci perdi 2 timer e la possibilità di studiare l'andamento PWM.
certo è un bel esercizio di stile, ma non la userei mai in un codice che debba fare pure altro.

Nel mio caso non deve fare altro, dovrò solo implementare la lettura di un pulsante ed il controllo di due pin, il resto è tutto sano hardware :slight_smile:

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

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

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.

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?

@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/(66060) = 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.

lesto non ho capito quasi una beneamata.... spiega meglio :grin:

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

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

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

lesto posa i'fiasco, come si dice da me :stuck_out_tongue_closed_eyes: :stuck_out_tongue_closed_eyes:
L'overflow di millis avviene ogni 49,7 GIORNI. A che stavi pensando? :wink:

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.

la micros è scritto sul reference che sballa dopo pochi minuti, molto prima dell'overflow.

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 :slight_smile:

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

per cosa? per l'overflow? il codice l'ho già postato qui: Frequenzimetro - #24 by lestofante - Software - Arduino Forum

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

#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

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?

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.