Contagiri con LED - riduzione dell'errore con pulseIn

Ciao a tutti, sono nuovo nel forum ed ho iniziato ad usare Arduino da poco, tuttavia sono a corto di tempo e necessiterei di qualche delucidazione.
Ho fatto una ricerca tra i vari contagiri RPM ma non riesco a trovare una risposta all'idea del software adeguata.
Tanto per iniziare, ho realizzato il seguente circuito: Michael McKGyver McKinley / Arduino_Tachometer
l'ho testato con il codice presente sotto e funziona, tuttavia non riesce a mantenere un numero costante di giri, ma ha un errore abbastanza alto( a 2500 giri, varia +/- di circa 100 giri).
Invece di contare il numero di impulsi in un certo intervallo, ritengo sia più preciso misurare il tempo trascorso tra due impulsi, ed ho provato ad utilizzare il pulseIn.
Inserendo il seguente sketch e testando con un fan F6015B12LY (9 pale, max 3000 giri/min), la durata tra un segnale ad un'altro è assurda (+1000000 us suppongo); inoltre non riesce a leggere i giri/min.
Lo sketch è il seguente, cos'è che sto sbagliando?
è sensato inoltre dire che contando gli la durata tra due impulsi, la misura dovrebbe essere più precisa?

//Pulsein code for rpm counting

//Define Variables
const float pi = 3.14;
unsigned long holes__angle = pi/2;
unsigned long duration;  //
unsigned long rad_sec;  //
unsigned long rpm;  //
unsigned long timeout = 10000000;  //
int ledPin = 2;             // LED connected to digital pin (changes state with each tic of code wheel)
int statusPin = 13;             // LED connected to digital pin (changes state with each tic of code wheel)

void setup()
 {
   Serial.begin(115200);
   Serial.println("CLEARDATA"); //clean excel spreadsheet
   Serial.println("LABEL,Time,Duration,RPM"); //clean excel spreadsheet
   pinMode(statusPin, OUTPUT); //
   pinMode(ledPin, INPUT); //
}
 void loop()
 {
   duration=pulseIn(ledPin,HIGH,timeout);
   rad_sec=(holes__angle*1000000)/duration;
   rpm=(rad_sec*60)/(2*pi);
   Serial.print("DATA,TIME,");Serial.print(duration);Serial.println(rpm);
    }

http://forum.arduino.cc/index.php?topic=157511.0
Ciao
Io permetto di suggerirti di dare un'occhiata a questo mio topic
Ho sbattuto un bel po il muso pure io su pulse in senza ricavarne nullA di buono sostanzialmente
Se vai al penultimo post della prima pagina trovi il codice che ho fatto per avere rpm con interrupt
Non blocca il codice come pulsein ed e molto preciso in quanto riesco senza problemi a governare l, accensione di un motore a benzina
Spero possa esserti di aiuto
EDIT
Una cosa che ho imparato nei miei mesi di prove e che spesso la lettura imprecisa non arriva dal sensore ma da quello che fai con la lettura
Se fai operazioni con numeri interi ogni volta ti taglia via i decimali e a ogni passaggio perdi in precisione

Concordo con lucaleo sia sulla lentezza ed imprecisione dei calcoli con i float sia sull'uso degli interrupt al posto della pulseIn.

leo72:
Concordo con lucaleo sia sulla lentezza ed imprecisione dei calcoli con i float sia sull'uso degli interrupt al posto della pulseIn.

questa la incornicio ahaha
è la prima volta che su due cose che scrivo non c'è manco una cagata :smiley:

in ogni caso crix87 ho guardato un po' il tuo codice e onestamente non capisco bene come funziona (o meglio quello che mi pare di aver capito non può andare)

pulsein ti restituisce il tempo trascorso (in millisecondi? non ricordo, ma attenzione perchè non è detto che sia quello che pensi di usare) durante HIGH o LOW a seconda delle tue preferenze
tuttavia tu devi GIA SAPERE a quanti gradi corrisponde quel segnale sulla rotazione totale (puoi calcolarla sommando la durata di high e low se hai un solo dente a un regime stabile di giri o moltiplicando la somma per il numero di high e low dentro a un giro a seconda di quante pale, denti eccetera tu abbia)

il motivo è molto semplice, vedrai che a parità di dente se tu allontani o avvicini il sensore al centro della rotazione la velocità angolare sarà sempre uguale, ma quella lineare cambia e quindi cambia anche l'ampiezza in gradi di un dente di esempio 3 millimetri di dente a 3 cm da centro o a 1 cm dal centro non sono per niente la stessa cosa

una volta che hai il sensore posizionato e lo hai calibrato allora da li non avrai grossi problemi, solo non fare un hardware ballerino perchè butti via ore e ore a caso (te lo garantisco :P)

spero di non aver scritto tutto ciò inutilmente e che ti sia di aiuto ahaha

EDIT
ultima info per rendere tutto più comprensibile
non farti ingannare dal fatto che stai misurando una ventola

poniamo caso che hai 6 pale

un conto è guardare quando ci metti a leggere 6 pale e dividere un minuto per quel tempo ottenendo rpm (che va bene)

un conto è misurare quando dura una pala
di istinto viene da pensare 6 pale, 6 spazi quindi 6 pale 180° e 6 spazi 180° quindi ogni pala e ogni spazio valgono 30°
ma non è detto che sia così!!
6 pale e 6 spazi potrebbero anche essere 240° le pale e 120° gli spazi (a seconda di come è fatta la ventola) quindi ogni pala misurerebbe 40° e ogni spazio 20°

per questo per sfruttare la durata della pala o dello spazio è FONDAMENTALE sapere a quanti gradi corrisponde quella pala o quello spazio a quella DETERMINATA distanza dal centro della rotazione

Cari Lucaleo e Leo72, vi ringrazio per le risposte.
Lucaleo, in realtà probabilmente la mia situazione è più semplice, perchè devo testare delle turbine eoliche, il sensore misurerà la rotazione di un volano su cui andrò ad eseguire 4 fori (credo bastino, che ne dite), quindi spaziati di 90° (pi/2 radianti).
Ho capito la questione sulla distanza dal centro, ma non ho capito se si applichi al mio caso, in quanto io misurerei solo velocità angolari, non lineari, o mi sbaglio?
Adesso provo ad implementare il codice che mi hai suggerito, sperando che le cose migliorino! 8)

tutto dipende da come affronti il problema
se tu "conti i buchi" non ti interessa nulla di quello che ho scritto sul calcolo dei gradi
se tu conti "quanto dura un buco" devi per forza mettere questo dato in relazione a quanto dura un giro e per "indovinare" quanto dura il giro devi per forza sapere a quanti gradi su 360 corrisponde il buco
se misuri gli spazi senza buco moltiplicando per 4 avrai una misura molto vicina alla realtà ma ti mancheranno sempre i micros dei buchi che non puoi stimare se non sai quanti gradi sono :smiley:

nel tuo caso potresti anche ottenere un buon risultato tramite gli interrupt (se usi pulsein blocchi tutto in continuazione) misurando sia la durata di un buco che la durata dello spazio senza, sommare i valori e moltiplicare per 4 e ottenere il tempo di un giro senza sapere a quanti gradi corrisponde il buco e lo spazio

oppure ancora più preciso fai un contatore, poi prendi i valori di ogni buco che passa e ogni spazio vuoto fino a quando non sono passati 4 buchi e 4 spazi, sommi tutto e hai il tempo esatto di ogni giro

io sul motore non posso usare questi metodi perchè devo sapere quanti giri sto facendo e in che posizione sono del giro prima di un certo momento, ma tu non hai questo limite credo

Mi consola il fatto che il codice dovrebbe essere più semplice di quello ce hai postato nell'altro topic :slight_smile:

Quindi, per misurare i giri, posso ripartire da qui:
http://mckgyver.pbworks.com/w/page/20654133/Arduino_Tachometer
Tuttavia, invece di far aumentare i giri, come faccio a calcolare il tempo tra gli interrupt? uso sempre la libreria stopwatch o ci sono funzioni più semplici?

guarda stopwatch l'ho usata perchè sono scarso e non riesco a usare la funzione millis o micros che sia :cold_sweat:

tuttavia prendo spunto dal codice che hai postato e provo a modificarlo per quello che serve a te

magari imparo qualcosa su millis intanto :smiley:

  //Arduino Code
  
  /*
  
   * Tachometer
   
   *
   
   * M.G.McKinley
   
   * 6/1/2009
   
   * Adjust 
   
   */
  
  //Define Variables
  
  int ticsPerRev = 16;       // define number of tics per rev of code wheel
  
  float rpm = 0.0;  // I prefer float for rpm
  
  volatile int rpmcount =0;  // volitile because the variable is manipulated during the interrupt
  
  unsigned long timeold = 0; // used to calculate d_t= millis()-timeold;
  
  int d_t;
  
  
  
  
  
  void setup()
  
  {
  
    Serial.begin(9600);
  
    //Interrupt 0 is digital pin 2, so that is where the IR detector is connected
  
    //Triggers on FALLING (change from HIGH to LOW)
  
    attachInterrupt(0, rpm_fun, FALLING);
  
  
  
  
  
  }
  
  void loop()
  
  {
  
    //Update RPM every second
  
  
  
    //Don't process interrupts during calculations
  
    detachInterrupt(0);
  
    d_t=millis()-timeold;
  
  
  
    if (d_t >= 1000)
  
    {
  
      rpm = float(60.0*1000.0)/float((d_t))*float(rpmcount)/ticsPerRev;
  
  
  
      timeold = millis();
  
      d_t=0; //reset d_t
  
  
  
      //Serial Port Output
  
  
  
      Serial.println(rpm);
  
  
  
      rpmcount = 0; //reset rpmcount
  
  
  
    }
  
    //Restart the interrupt processing
  
    attachInterrupt(0, rpm_fun, FALLING);
  
  }
  
  void rpm_fun()
  
  {
  
    //This interrupt is run at each codewheel tic
  
    detachInterrupt(0); //im not sure if this is necessary here
  
  
  
    rpmcount++; //update rpmcount
  
  
  
  
  
  
  
    attachInterrupt(0, rpm_fun, FALLING);
  
    }

ho tolto un po' di roba che faceva confusione per capire la dinamica del codice

così DOVREBBE funzionare, non sono per nulla bravo...

prova :smiley:

ah mi raccomando ricordati di modificare il numero delle pale, per ora sono 16

grazie per la revisione, in effetti sembra andare meglio!
Li tolgo i float dal codice? meglio dichiarare le variabili con long?

mah dipende da quanto può durare la rotazione forse puoi usare pure int visto che usi millis
però non me ne intendo... ho capito qualcosina da poco

in ogni caso quel codice non è pensato per essere molto preciso secondo me, quello imposta un intervallo per il loop di 1 secondo e ogni secondo guarda quanti denti sono passati e con qualche calcolo ti dice i giri

io proverei a far partire un contatore alla prima pala e fermarlo quando inizia la 16esima (con falling quando inizia la pala tu hai l'interrupt), in questo modo avresti il vero tempo di un giro

se ho scritto delle boiate che qualcuno mi corregga :smiley:

Millis restituisce un unsigned long, quindi devi usare questo tipo di dato per immagazzinare il valore restituito dalla funzione.

leo72:
Millis restituisce un unsigned long, quindi devi usare questo tipo di dato per immagazzinare il valore restituito dalla funzione.

possibile che tu mi abbia appena spiegato perchè non sono mai riuscito a usare millis :smiley:

Vi allego il codice aggiornato - credo funzioni ma la ventola di prova non è attendibile :slight_smile:

//Arduino Code
/*
* Tachometer
*
* M.G.McKinley
* 6/1/2009
* Adjust 
*/
//Define Variables
 
int ticsPerRev = 7;       // define number of tics per rev of code wheel
unsigned long rpm;  // 
volatile int rpmcount;  // volitile because the variable is manipulated during the interrupt
unsigned long timeold; // used to calculate d_t= millis()-timeold;
unsigned long d_t;
  
void setup()
{
  Serial.begin(9600);
  Serial.println("CLEARDATA"); //clean excel spreadsheet
  Serial.println("LABEL,Time,RPM"); //clean excel spreadsheet
  attachInterrupt(0, rpm_fun, FALLING);
}
 
void loop()
{
  detachInterrupt(0);
  d_t=micros()-timeold;
  if (d_t >=500000)
   {
     rpm = (60*1000000)/((d_t))*(rpmcount)/ticsPerRev;
     timeold = micros();
     d_t=0; //reset d_t

//Serial Port Output
     Serial.print("DATA,TIME,");Serial.println(rpm);
     rpmcount = 0; //reset rpmcount
  }
     attachInterrupt(0, rpm_fun, FALLING);
}
 
void rpm_fun()
{
    detachInterrupt(0); //im not sure if this is necessary here
    rpmcount++; //update rpmcount
    attachInterrupt(0, rpm_fun, FALLING);
 
}

Tuttavia come detto non è ancora abbastanza accurato, stavo pensando di spostare l'INPUT dal pin2 al pin8 e utilizzare il timer1 inserito nel codice. tuttavia il timer1 non mi è chiarissimo e non riesco ad utilizzarlo:

  1. credete che sia possibile utilizzardo, spostando solo un filo a livello hardware?
  2. consigli a livello software? non riesco a trovare degli sketch adeguati per un rpm! :cold_sweat:

ma cosa devi fare?
è più semplice aiutarti se lo sappiamo
e poi sono curioso :wink:

devo fare dei test in galleria del vento di una piccola turbina eolica, praticamente partendo da zero devo misurare la velocità massima della turbina e il tempo con cui ha raggiunto il massimo dei giri (per intenderci, ad 1 m/s di velocità d'aria, la turbina magari gira a 200 rpm, arduino mi deve indicare nel modo più accurato in quanto tempo la turbina partendo da zero è arrivata a 200 rpm).
In poche parole al momento il codice non va bene perchè non resta stabile ad un picco di giri, in quanto magari varia di +/- 100 giri a 2500rpm, ed inoltre il software non sembra capace di leggere i giri al di sotto dei 15 rpm. L'hardware invece va bene perchè è stato testato con l'oscilloscopio, quindi è solo una questione di software!
Fatemi sapere se vi viene in mente una soluzione!! :cold_sweat:

crix87:
Tuttavia come detto non è ancora abbastanza accurato, stavo pensando di spostare l'INPUT dal pin2 al pin8 e utilizzare il timer1 inserito nel codice. tuttavia il timer1 non mi è chiarissimo e non riesco ad utilizzarlo:

Se vuoi usare il timer 1 come Input Capture con timestamp, il timestamp altro non è che il valore del registro del timer 1 stesso,. Dovresti impostare una certa frequenza, altrimenti quel valore è inutilizzabile, poi volta che viene registrato l'evento, il timer 1 ti salva il contenuto del suo registro in un altro registro. A quel punto devi far sollevare un interrupt, dall'interrupt estrarre il valore e poi resettare il contatore.

se invece utilizzassi Stopwatch senza Interrupt per capire il tempo tra due HIGH?
Ha senso dire nelle variabili byte status = LOW ; poi fare un loop che attivi lo StopWatch al momento dell'HIGH (Stopwatch_start), e lo fermi al secondo HIGH (Stopwatch_stop), per poi riportare Stopwatch_start=Stopwatch_stop e calcolare i giri tramite la different Stopwatch_stop-Stopwatch_start?

prova questo

#include <StopWatch.h>



const byte pin2 = 2;     // interrupt startt
const byte pin3 = 3;     // interrupt stopp

volatile int statestartt = HIGH;    // stato normale startt
volatile int statestopp=HIGH;       // stato normale stopp

StopWatch sw_micros(StopWatch::MICROS);    // timer durata dente



long time = 0;           //valore durata dente microsecondi



int rpm = 0;             //numero giri
int gradidente = 6;     //ampiezza dente in gradi

void setup() {
Serial.begin(9600);     //comunicazione seriale

  pinMode(pin2, INPUT_PULLUP);
  pinMode(pin3, INPUT_PULLUP);
  attachInterrupt(0, startt, FALLING);   //interrupt in inizio dente
  attachInterrupt(1, stopp, RISING);     //interrupt fine dente
}

void loop()
{
  //se il dente è iniziato
  if(statestartt == LOW){
    sw_micros.start();//inizia a contare durata dente
    statestartt=HIGH;// reimposta startt
  }

  //se il dente è finito
  if(statestopp == LOW) {
    time=sw_micros.elapsed();                  //memorizza durata dente
    
   
    statestopp = HIGH;                         //reimposta stopp
    sw_micros.reset();     //azzera timer durata dente
   
     rpm=60000000/((time/gradidente)*360);      //calcolo rpm
Serial.println(rpm);                       //stampo rpm
   
   
 
    
   
  }


  
 } // End Loop

void startt()//interrupt startt
{
  statestartt = !statestartt;  //inverti startt
}

void stopp()//interrupt stopp
{
  statestopp = !statestopp;    //inverti stopp
}

devi solo calcolare i gradi della pala (misura il raggio dal sensore al centro, fai la circonferenza, misura la larghezza della pala a quel raggio e ottieni una buona approssimazione
dovresti ottenere una stampa di rpm ogni pala alla fine della pala (quindi nello spazio vuoto dandoti il tempo di fare calcoli e stampare)

collega il filo del sensore sia al pin 2 e al pin 3
per legere i buchi o gli spazi inverti rising e falling negli interrupt

prova :smiley:

Perfetto lo provo subito! L'unico problema è che mi dice che sulla riga
Stopwatch sw_micros(Stopwatch::MICROS); // timer durata dente
non mi rileva stopwatch e la variabile sw_micros
non è questa la libreria stopwatch?
http://www.avdweb.nl/arduino/hardware-interfacing/stopwatch.html#h0-1-5-1-stopwatchh