Calcolo Velocità

di il
2 risposte

Calcolo Velocità

Scenario: Arduino(Mega)
Misura di velocità con rilevamento IR su albero con rifrangente, Velocità 60÷300rpm

Ho fatto 2 differenti approcci alla misura:
1) Misura impulsi in isr su finestra temporale fissa
2) Misura dT(microsecondi) sempre in isr tra 2 impulsi, e calcolo in Loop.

Il primo appoccio stando la bassa velocità non è in grado di dare rilevamenti significativi se non aumentando il Tempo finestra di campionamento ma la risoluzione rimane di 60rpm, ho ovviato con ruota fonica a 20 tacche migliorando sensibilmente il risultato, in questo caso la risoluzione non può scendere sotto i 3rpm, se non ho sbagliato i conti.
Questo metodo introduce peraltro un DELAY del tempo finestra, in cui se non erro, il processore non processa nulla, salvo le azioni in isr, ma siccome oltre alla velocità dovrà fare altre cose... non mi piace molto... oppure sbaglio...?

Il secondo sembra non soffrire di grossi problemi, ed offre un grado di precisione teorica migliore con queste bassissime velocità, ed il ritardo introdotto è inversamente proporzionale alla velocità, per come ho scritto il codice che mostro dopo.

Ora diciamo che entrambi danno "un risultato", che sia un risultato da ritenere buono è un discorso differente.
Le misure hanno grosse variazioni, soprattutto nel 2° Approccio, ed avrei pensato di introdurre un filtro.

Mostro banalmente i 2 codici di test, uso questo linguaggio da 2 settimane, quindi sono molto acerbo, pensavo, semplificando molto le cose, di usare un'array, escludendo i valori toppo alti o troppo bassi, e calcolare la mediana.

Quì mi fermo e Vi chiedo una spinta.

Mostro il codice di test:
PRIMO APPROCCIO

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);

const byte interruptPin = 2;

float rev=0;
int rpm;
unsigned long oldtime=0;        
unsigned long time;

void isr()   {rev++;}

void setup()
{
  Serial.begin(9600);
  lcd.init();                    
  lcd.backlight();

  pinMode(interruptPin, INPUT_PULLUP);   
  attachInterrupt(digitalPinToInterrupt(interruptPin),isr,RISING);  //attaching the interrupt
}

void loop()
{
  delay(1000);                  // 1 second delay
  detachInterrupt(digitalPinToInterrupt(interruptPin));           //detaches the interrupt while calculating
  time=millis()-oldtime;        //finds the time 
  rpm=(rev/time)*60000;         //calculates rpm
  oldtime=millis();             //saves the current time
  rev=0;

  lcd.setCursor(0,0);
  lcd.print("RPM:" + String(rpm));
  attachInterrupt(digitalPinToInterrupt(interruptPin),isr,RISING);  //attaching the interrupt
}


SECONDO APPROCCIO:

#include <LiquidCrystal_I2C.h>

uint32_t volatile t;  // per misura periodo in microsecondi
uint8_t  volatile s;  // stato processo misura
uint32_t rpm=0;
const byte interruptPin = 2;

LiquidCrystal_I2C lcd(0x27,20,4); 

void setup()
{
  Serial.begin(9600);
  lcd.init();                     
  // Print a message to the LCD.
  lcd.backlight();
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), isr, RISING);
}


void loop()
{
  // avvia e attende misura 't' da ISR
    for (s = 0;  s != 2; ) {}

    if (t > 60000L)      // se < 1000 rpm calcola RPM da 't' (errore 0.05 rpm, +/-1 rpm)
    {rpm = 60000000L / t;}

  lcd.setCursor(0,0);
  lcd.print(rpm);
  lcd.setCursor(0,2);  
  lcd.print(t);
}

void isr(void)
{
    if      (s == 0) { t = micros();      s = 1; }
    else if (s == 1) { t = micros() - t;  s = 2; }
    else             {                           }
}

2 Risposte

  • Re: Calcolo Velocità

    Ho rifatto un po di compiti a casa... ed i metodi funzionano discretamente entrambi.
    METODO TEMPO CAMPIONAMENTO FISSO (1sec)
    #include <LiquidCrystal_I2C.h>
    #include "filter.h"
    
    
    LiquidCrystal_I2C lcd(0x27,20,4);
    Filter filtro;
    
    const byte interruptPin = 2;
    
    int sampleF;
      
    long   prev_time                ; // [us]
    float  rev                      ;
    int    rpm                      ;
    int    events_per_round =      20; // Number of Pulse in a Round (Fori della ruota fonica) 
    double freq_acquisition =      1; // [Hz]
    double period                   ;
    
    void isr()   {rev++;}
    
    void setup()
    {
      Serial.begin(9600);
      lcd.init();                   
      lcd.backlight();
      filtro.setDepthFilter(5);     //imposta la profondità del filtro
      period = 1000/freq_acquisition;
      pinMode(interruptPin, INPUT_PULLUP);   
      attachInterrupt(digitalPinToInterrupt(interruptPin),isr,RISING);  //attaching the interrupt
    }
    
    void loop()
    {
      delay(period);                  
      
      rpm=(rev/(micros() - prev_time))*60000000L;      //calculates rpm
      rpm=rpm/events_per_round;
      sampleF = filtro.filterSamples(rpm); 
    
      lcd.setCursor(0,0);
      lcd.print("RPM:" + String(rpm)+ " RPMf:" + String(sampleF));
    
      rev=0;
      prev_time=micros();             //saves the current time
      attachInterrupt(digitalPinToInterrupt(interruptPin),isr,RISING);  //attaching the interrupt
    }
    METODO CONTEGGIO TEMPO TRA 2 FRONTI SALITA
    
    #include <LiquidCrystal_I2C.h>
    #include "filter.h"
    
    long   curr_time                ; // [us]
    long   prev_time                ; // [us]
    int    events_per_round =      1;
    int    rpm                      ;
    double freq_acquisition =    100; // [Hz]
    double period                   ;
    
    const byte interruptPin = 2;
    
    int sampleF;
    
    LiquidCrystal_I2C lcd(0x27,20,4); 
    Filter filtro;
    
    void setup()
    {
      Serial.begin(9600);
      lcd.init();                     
      lcd.backlight();
      filtro.setDepthFilter(5);     //imposta la profondità del filtro
      period = 1000/freq_acquisition;
      pinMode(interruptPin, INPUT_PULLUP);
      attachInterrupt(digitalPinToInterrupt(interruptPin), isr, RISING);
    }
    
    void loop()
    {
      delay(period);
    
      sampleF = filtro.filterSamples(rpm); 
      lcd.setCursor(0,0);
      lcd.print("RPM:" + String(rpm)+ " RPMf:" + String(sampleF));
    
    }
    
    void isr(void)
    {
      curr_time = micros(); // For lower speeds use millis() function since micros() only works with n<16000 (16ms)
      long elapsed_time = curr_time - prev_time;
      
      rpm = 60000000L/(elapsed_time*events_per_round);
      prev_time = curr_time;
    }
    Per il filtro ho implementato una classe, sfruttando librerie già realizzate, introducendo l'esclusione degli estremi MIN/MAX e facendo la media, con Array a profondità dinamica usato come registro FIFO, quindi accodo sempre l'ultimo campione nuovo scodando il primo, impostato a 5 per il test.

    Non mi soddisfa moltissimo... parte con il SoftStart ovviamente essendo l'array vuoto ed in fermata tarda... per lo stesso motivo.

    Non allego il codice dell'interfaccia... credo non sia necessario.
    
    #include "filter.h"
    #include "Arduino.h"
    
    // [Constructor]
    Filter::Filter()
    {
    setDepthFilter(10);
    }
    int Filter::filterSamples(int _sample)
    {
      int sum = 0;
      int min;
      int max;
      min=samples[0];
      max=samples[0];
      
      for(int i = 0; i < depth ;i++)
      {
        samples[i] = samples[i+1];
      }
      samples[depth-1] = _sample;
      for(int i = 0; i < depth ;i++)
      {
    	  if(max<samples[i]) max=samples[i];
        if(min>samples[i]) min=samples[i];
    	  sum += samples[i];
      }
      sum = sum - (max + min);
      return sum/(depth-2);
    }
    
    bool Filter::setDepthFilter(int _depth)
    {
      depth =_depth;
      free(samples);
      samples = (int*)calloc(depth,sizeof(int));  
      
      if(samples == NULL){
         return false;
      }  
      return true;
    }
    Il vantaggio del secondo metodo, ovvero quello che conta il tempo tra 2 Fronti di salita, oltre alla precisione, è che non devo installare la ruota fonica, in quanto basta un pezzo riflettente sull'alberino, quindi si semplifica la realizzazione meccanica.

    Se ci sono suggerimenti per errori concettuali e/o di dichiarazioni che ho sbagliato mi farebbe piacere ricevere commenti.

    Prossimo passo, raddoppio del sensore per determinazione senso di rotazione.
  • Re: Calcolo Velocità

    Ho modificato la Lib del filtro, non mi piacevano i 2 cicli a mio avviso inutili se non ho preso un granchio...
    Non credo si notino le differenze, ma mi dava fastidio...aggiunta la possibilità di fare media su tutti i campioni o eliminare Min/Max e mediare i restanti.
    #include "filter.h"
    #include "Arduino.h"
    
    // [Constructor]
    Filter::Filter()
    {
      setDepthFilter(5,true);
    }
    
    int Filter::AddSamples(int _sample)
    {
      int _sum = 0;
      int _min    ;
      int _max    ;
      _min=_sample;
      _max=_sample;
      _sum=_sample;
     
     
      for(int i = 0; i < depth-1 ;i++)
      {
        samples[i] = samples[i+1]           ;  //Firt Out - Shift all to the left
        if(_max<samples[i]) _max=samples[i] ;
        if(_min>samples[i]) _min=samples[i] ;
    	  _sum += samples[i]                  ;
      }
      samples[depth-1] = _sample            ; // Put new Sample in the LAST index
      if (removeMinMax)
      {
        _sum -= (_max + _min);
        _sum=_sum/(depth-2);
      }
      else  
      {
        _sum=_sum/depth;
      }
      return _sum;
    }
    
    bool Filter::setDepthFilter(int _depth, bool _removeMinMax)
    {
      removeMinMax=_removeMinMax;
      depth =_depth;
      free(samples);
      samples = (int*)calloc(depth,sizeof(int));  
      
      if(samples == NULL){
        return false;
      } 
      
      return true;
    }
Devi accedere o registrarti per scrivere nel forum
2 risposte