Pages: [1]   Go Down
Author Topic: Smoothing RPM data  (Read 913 times)
0 Members and 1 Guest are viewing this topic.
Newcastle, NSW, Australia
Offline Offline
Newbie
*
Karma: 0
Posts: 33
Just a mechanic
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi all,
I'm working on a project which logs the RPM of a shaft; shaft has magnets on it and I'm using a reed switch which pulls an input to ground when a magnet is near. The code is using an interrupt and measuring the period to work out RPM. This works fine. However I'd like to include some 'smoothing' or noise reduction in the data.

My ISR for the rpm calc is as follows;

Code:
void DSIsr()
{
  unsigned long DSnow = micros();
  unsigned long DSinterval = DSnow - DSlastPulseTime;
  if (DSinterval > 100)
  {
    DSrpm = 60000000.0/(DSinterval * DSpulsesPerRev);
    DSlastPulseTime = DSnow;
  } 
}

I then log the result of DSrpm (to SD card eventually but currently just to the Serial output) at an adjustable refresh rate using;

Code:
unsigned long refreshcurrentMillis = millis();
  if(refreshcurrentMillis - refreshpreviousMillis > refresh) {
    refreshpreviousMillis = refreshcurrentMillis;
    lcd.setCursor(0, 1);
    lcd.print(DSrpm);
    lcd.print("  ");
    lcd.print("RPM ");   
    // Serial.println(DSrpm);
  }

'refresh' is 50ms at the moment.

I would like a way to 'average' the rpm results, say over 5 or 10 samples, to smooth the data that I'm getting.

I tried a similar method I've used to smooth analog inputs;

Code:
    rawvalue = 0;
    for (int i=0; i< count; i++) rawvalue += analogRead(A1);
    rawvalue = rawvalue / count;

but that doesn't seem to work using an ISR-derived value, as each time the ISR is triggered, DSrpm is re-calculated.

Is there anyway I can use a similar idea to 'average' a few samples of DSrpm and smooth the data??

Cheers,
Ryan.
   
Logged

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 33
Posts: 1273
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Adding in a fraction, say 30%, of the difference between the current value and the new data creates a much smoother change profile and eliminates spikes and fluctuation.
« Last Edit: July 21, 2012, 05:52:51 am by marco_c » Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 615
Posts: 49388
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Your ISR should simply count pulses (one per time called). You should only calculate RPM when you need to display it. The RPM value is a function of the number of pulses per unit of time. The number of pulses is known, as is the unit of time (the refresh time).
Logged

Southern California
Offline Offline
Full Member
***
Karma: 0
Posts: 108
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

like marco_c said, using a fraction will smooth the data.  I use the code below in several sketches, a method I found in the book "30 Arduino Projects for the Evil Genius" by Simon Monk.  Alpha is a number between zero and one.  Larger alpha for more smoothing.

Code:
float value, rawvalue, oldvalue=0, alpha=.7;

void loop() {
  rawvalue = analogRead(1);
  value = alpha*oldvalue + (1-alpha)*rawvalue;
  oldvalue = value;
}
Logged

United Kingdom
Offline Offline
Tesla Member
***
Karma: 224
Posts: 6619
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I would do 2 things:

1. Use a Hall sensor instead of a reed switch to detect the magnets. Contact bounce in the reed switch is likely to cause jitter. There may be less need for smoothing if you eliminate this jitter. Hall sensors cost very little these days.

2. One way of smoothing the output is to measure the time for several rotations instead of just one. You could have the ISR record the times of e.g. the last 8 pulses instead of just the last one, like this (warning: untested code!):

Code:
const int nReadings = 8;

volatile unsigned long times[nReadings ];
volatile unsigned char lastReading = 0;

void DSIsr()
{
  unsigned long DSnow = micros();
  if (DSnow - times[lastReading] > 100)
  {
    lastReading = (lastReading + 1) % nReadings ;
    times[lastReading] = DSnow;
  } 
}

...

unsigned long refreshcurrentMillis = millis();
  if(refreshcurrentMillis - refreshpreviousMillis > refresh) {
    refreshpreviousMillis = refreshcurrentMillis;
    noInterrupts();
    unsigned int lr = lastReading;
    unsigned long interval = times[lr] - times[(lr + 1) % nReadings];
    interrupts();
    unsigned long DSrpm = ((nReadings - 1) * 60000000UL)/interval;
    lcd.setCursor(0, 1);
    lcd.print(DSrpm);
    lcd.print("  ");
    lcd.print("RPM ");   
  }

I've done the calculation in unsigned long instead of float for better speed, but you can use float if you want to display fractional RPM.
Logged

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Newcastle, NSW, Australia
Offline Offline
Newbie
*
Karma: 0
Posts: 33
Just a mechanic
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I would do 2 things:

1. Use a Hall sensor instead of a reed switch to detect the magnets. Contact bounce in the reed switch is likely to cause jitter. There may be less need for smoothing if you eliminate this jitter. Hall sensors cost very little these days.

2. One way of smoothing the output is to measure the time for several rotations instead of just one. You could have the ISR record the times of e.g. the last 8 pulses instead of just the last one, like this (warning: untested code!):

Code:
const int nReadings = 8;

volatile unsigned long times[nReadings ];
volatile unsigned char lastReading = 0;

void DSIsr()
{
  unsigned long DSnow = micros();
  if (DSnow - times[lastReading] > 100)
  {
    lastReading = (lastReading + 1) % nReadings ;
    times[lastReading] = DSnow;
  } 
}

...

unsigned long refreshcurrentMillis = millis();
  if(refreshcurrentMillis - refreshpreviousMillis > refresh) {
    refreshpreviousMillis = refreshcurrentMillis;
    noInterrupts();
    unsigned int lr = lastReading;
    unsigned long interval = times[lr] - times[(lr + 1) % nReadings];
    interrupts();
    unsigned long DSrpm = ((nReadings - 1) * 60000000UL)/interval;
    lcd.setCursor(0, 1);
    lcd.print(DSrpm);
    lcd.print("  ");
    lcd.print("RPM ");   
  }

I've done the calculation in unsigned long instead of float for better speed, but you can use float if you want to display fractional RPM.

Thanks for that!
Fractional RPM isn't needed, not sure why I used float in my code actually, I think it's code I used from another project that displayed a fractional RPM.

The whole length of the event I'm logging will only be 9 seconds (hopefully a bit less) and the shaft will accelerate from 0 RPM to about 7000 in that 9 seconds. I'm looking for the most accurate RPM calculation I can get to analyse the acceleration rate of the shaft over the 9 seconds. The shaft will have 4 magnets at 90 degree intervals, so there will be 4 'pulses' per revolution.

Will your method give a less accurate or less 'responsive' result then the original calculations, or will the difference be negligible?
Logged

UK
Offline Offline
Shannon Member
****
Karma: 223
Posts: 12630
-
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


I'm looking for the most accurate RPM calculation I can get to analyse the acceleration rate of the shaft over the 9 seconds.


I think you need to be clearer about what you're trying to achieve. Smoothing will reduce the accuracy, not increase it. Exactly what are you trying to get out of this thing?
Logged

I only provide help via the forum - please do not contact me for private consultancy.

Pages: [1]   Go Up
Jump to: