Reading RPM at low RPM with optical encoder or hall sensor

Hi,

I use a motor output of which I need to know the RPM and later use a PWM controller to keep the RPM constant. So part one is cathing the RPM which is currently done by an optical reflective encoder with 4 black/white transitions. I can enhance the number of transistions to 16 if needed.

I need to know if my assumption is correct about this:

q = (int)rpm * 4; // as we use 4 interuptions we mulitply by 4

I cant compare to any other RPM to calibrate.

Any assistance in the right direction would be appriciated.

I use the current code found on the web and which is working inside my other sketch but it was not explained about the numer of transitions used:

//---------------rpm
#define INTERRUPT_PIN 0   // digital pin 2
int q;
int count;
int interruptCount;
boolean interruptCalled = false;  // boolean value
unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;
float rpm = 0;

void setup()
{
  Serial.begin (9600);
  // setup interrupt pin
  pinMode(INTERRUPT_PIN, INPUT);
  attachInterrupt(INTERRUPT_PIN, interruptFired, CHANGE);
}
void loop()
{
  //RPM.......... optical sensor with 4 black white spots.....................
  if( interruptCalled ) 
  {
    currTime = micros();
    timeDiff = currTime - lastTime;
    rpm = (float)interruptCount * 1000000.0 / (float)timeDiff;
    interruptCalled = false;
    q = (int)rpm * 4; // as we use 4 interuptions we mulitply by 4
    interruptCount = 0;
    lastTime = currTime;  // store current time for next iteration
  }
  Serial.println (q);
  delay(100);
}

void interruptFired()
{
  interruptCount++;
  interruptCalled = true;
}

Paco

if you have four interrupts for one rotation the formula should be different.

I rewrote your code so check the differences

//
//    FILE: .ino
//  AUTHOR:
// VERSION: 0.1.00
// PURPOSE:
//    DATE:
//     URL:
//
// Released to the public domain
//

#define INTERRUPT_PIN 0   // digital pin 2

int interruptCount;

unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;

float rpm = 0;

uint32_t lastDisplay = 0;

void setup()
{
  Serial.begin (9600);
  pinMode(INTERRUPT_PIN, INPUT);
  attachInterrupt(INTERRUPT_PIN, interruptFired, CHANGE);
}

void loop()
{
  if (millis() - lastDisplay >= 1000)  // adjust 1000 to needed interval
  {
    lastDisplay = millis();
    currTime = micros();
    timeDiff = currTime - lastTime;
    lastTime = currTime;  // store current time for next iteration

    cli();
    float icount = interruptCount;
    interruptCount  = 0;
    sei();

    float rounds = icount / 4; // four interrupts per round
    float roundsPerSecond = rounds * 1000000.0 / timeDiff;
    rpm = roundsPerSecond / 60;

    Serial.print(rpm, 2);
    Serial.print("\t");
    Serial.println(roundsPerSecond, 2);
  }
}

void interruptFired()
{
  interruptCount++;
}

it might be adequate to only use millis() for the time keeping.

Hi Rob,

Thanks for the assistance. My sketch rpm 156 Result with new sketch by same speed is 0.18 rpm. The 156 rpm looks realistic the 0.18 rpm not :D

Paco

For the millis > I changed currTime = micros(); to currTime = millis(); and now the result is around 160 without averaging.

But I think I have to change float roundsPerSecond = rounds * 1000000.0 / timeDiff; too to float roundsPerSecond = rounds * 1000.0 / timeDiff; but that gives me 0.16 rpm again. so the 1000 factor plays a roll.

Paco

Yes that formula calculated the roundsper micrsecond that should become millisecond.

What is the actual rounds per minute of the device. Is it indeed somewhere around 0.16 rounds per minute?

one fix, the interruptCount must be volatile

#define INTERRUPT_PIN 0   // digital pin 2

volatile unsigned int interruptCount;

unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;

float rpm = 0;

void setup()
{
  Serial.begin (9600);

  pinMode(INTERRUPT_PIN, INPUT);
  attachInterrupt(INTERRUPT_PIN, interruptFired, CHANGE);
}

void loop()
{
  if (millis() - lastTime >= 1000)  // adjust 1000 to needed interval
  {
    currTime = millis();
    timeDiff = currTime - lastTime;
    lastTime = currTime;  // store current time for next iteration

    cli();
    float icount = interruptCount;
    interruptCount  = 0;
    sei();

    float rounds = icount / 4; // four interrupts per round
    float roundsPerSecond = rounds * 1000.0 / timeDiff;
    rpm = roundsPerSecond / 60;

    Serial.print(rpm, 3);
    Serial.print("\t");
    Serial.println(roundsPerSecond, 3);
  }
}

void interruptFired()
{
  interruptCount++;
}

Rob,

I have no idea what the rpm of the device is. It is a part of a hacked hand held cordless drill. I feed it with a variable power supply upto 13 volt. So I can vary the RPM for debug, the old casing said 550 rpm but no idea if that is without load and I think that is the maximum rpm.

The result now is 0.167 rpm due to 3 behind the point instead of the 2.

If I look at the device it clearly does more then 1 revolution per second. It looks more it does 3 to 4 revs per second.

here is a dropbox link to a video of the device running at the low RPM. https://www.dropbox.com/s/kusiexmd8bak6ze/hacked%20cordless%20drill%20running%20low%20rpm.MOV?dl=0

Paco

looks indeed like 3-4 rps => 200-250 rpm.

can you add these 2 lines to see the raw counter?

    Serial.print(icount, 3);
    Serial.print("\t");

I have the feeling that interrupts are missed.

added LED pulsing in the interrupt, to see if the interrupts come in

can you give it a try?

#define INTERRUPT_PIN 0   // digital pin 2
#define LED 13

volatile int ledState = LOW;
volatile unsigned int interruptCount;

unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;

float rpm = 0;

void setup()
{
  Serial.begin (9600);

  pinMode(INTERRUPT_PIN, INPUT);
  pinMode(LED, output);
  attachInterrupt(INTERRUPT_PIN, interruptFired, CHANGE);
}

void loop()
{
  if (millis() - lastTime >= 1000)  // adjust 1000 to needed interval
  {
    currTime = millis();
    timeDiff = currTime - lastTime;
    lastTime = currTime;  // store current time for next iteration

    cli();
    float icount = interruptCount;
    interruptCount  = 0;
    sei();

    float rounds = icount / 4; // four interrupts per round
    float roundsPerSecond = rounds * 1000.0 / timeDiff;
    rpm = roundsPerSecond / 60;

    Serial.print(icount, 3);
    Serial.print("\t");
    Serial.print(rpm, 3);
    Serial.print("\t");
    Serial.println(roundsPerSecond, 3);
  }
}

void interruptFired()
{
  interruptCount++;
  ledState = 1 - ledState;
  digitalWrite(LED, ledState);
}

Rob,

I attached the recording of the serial monitor as dropbox link. I see led 13 flickering.

I had to change pinMode(LED, output); to pinMode(LED, OUTPUT); ;-)

dropbox link. https://www.dropbox.com/s/0esx7y0nv2lw7sx/rpm%20recording.avi?dl=0

Paco

so the raw counter indicates about 40 - 50 changes per second,
gives indeed about 10-12 rounds per second

aaaargh, stupid me (/60 should be *60 ) …

Try this:

#define INTERRUPT_PIN 0   // digital pin 2
#define LED 13

volatile int ledState = LOW;
volatile unsigned int interruptCount;

unsigned long lastTime = 0;
unsigned long currTime = 0;
unsigned long timeDiff = 0;

float rpm = 0;

void setup()
{
  Serial.begin (9600);

  pinMode(INTERRUPT_PIN, INPUT);
  pinMode(LED, OUTPUT);
  attachInterrupt(INTERRUPT_PIN, interruptFired, CHANGE);
}

void loop()
{
  if (millis() - lastTime >= 1000)  // adjust 1000 to needed interval
  {
    currTime = millis();
    timeDiff = currTime - lastTime;
    lastTime = currTime;  // store current time for next iteration

    cli();
    float icount = interruptCount;
    interruptCount  = 0;
    sei();

    float rounds = icount / 4; // four interrupts per round
    float roundsPerSecond = rounds * 1000.0 / timeDiff;
    rpm = roundsPerSecond * 60;

    Serial.print(icount, 3);
    Serial.print("\t");
    Serial.print(rpm, 3);
    Serial.print("\t");
    Serial.println(roundsPerSecond, 3);
  }
}

void interruptFired()
{
  interruptCount++;
  ledState = 1 - ledState;
  digitalWrite(LED, ledState);
}
[/code[

:D Now we are on 330 rpm without load as we tested before on low voltage and so low rpm. If I crank the power supply up we reach 1665 rpm.

So now I have an inidication what speed the output that drives a rotational load needs to be.

My intension was to use one magnet and a hall sensor but I presume with only one magnet the resolution will go down at the low speeds and stable closed loop control will become a problem. Or I have to add the magnet at the rear end of the motor shaft and read from there for the closed loop control and devide the unknown gear ratio through it to get the output rpm to display.

I pasted the code in my main sketch and it runs fine. I also use and average to get some more flat readings instead of the jumping values. So now I can go one step further.

Thanks for your assistance Rob.

Paco

welcome,

the jumping values....

What still can be a problem is you miss interrupts.

if there is no load the amount of interrupts should be quite constant, it should not differ more than 5%

can you put the sensor closer or shield it from unwanted light sources?

I will test tomorrow loaded and unloaded. I think the values are already inside 5%.

Paco

Rob,

Both with load https://www.dropbox.com/s/61nlgbq2ubqtgap/average.avi?dl=0

https://www.dropbox.com/s/o5bpnby67pxpgf9/no%20average.avi?dl=0

Paco

I cannot relate the output of the video's to the sketch but it looks not bad at all (but I'm no stakeholder ;)

Only thing I noticed was that it seems you print same value multiple times, think before printing you should update the measurements...