Calculating RPM from motor encoder

Hello guys!

I want to calculate the RPM from my motor. I am using an Arduino Uno and I am limited to not use pin 2 and 3 so no hardware interrupt possible. I attempted to do so by using the EnableInterrupt library and make it trigger the ISR for the encoder pin on all rising edges.

double currentpulsetime1=0.0;

void encoderISR()
{

  currentpulsetime1=micros()-previoustime1;
  previoustime1=micros();
}

When I wanna get the RPM, I can just call getRPM function.

double getRPM1()
{
  if (currentpulsetime1==0)
  {
    return 0;
  }
  return 60000/(((currentpulsetime1)/1000.0)*562.215);
}

However, the RPM I got is not very stable. It is jumping around +/- 2 the actual RPM. Is it possible to further improve it without extra hardware?

Is it possible to further improve it without extra hardware?

Probably. But NOT from seeing just part of your code.

Time is NOT measured using floats. Time is in milliseconds or microseconds. Both are integral values.

tjj92: It is jumping around +/- 2 the actual RPM. Is it possible to further improve it without extra hardware?

What is the actual RPM? 10000 +/- 2 is pretty good, 5 +/- 2 not so much, obviously.

There's no good reason to store micros() in a double (double precision floating point) by the way. micros() returns an unsigned long and should be stored as one.

Edit: PaulS was faster than me :)

Another edit: I should have mentioned that on Uno and most other Arduino boards, double is not any different from float. Both are 32 Bit, so if float is not precise enough for your application, using double instead will not help. An exception is the Due, where a double is a 64 Bit precision data type.

First, get rid of the floats as suggested.

You have provided no information as to the rpm and the time scale of interrupts, but on 16 MHz Arduino boards micros() has a resolution of four microseconds (i.e. the value returned is always a multiple of four). Perhaps this is the cause of the +/- 2 rpm.

You may be able to get more precision by using a timer interrupt rather than a pin change interrupt. Take a look at FreqMeasure https://www.pjrc.com/teensy/td_libs_FreqMeasure.html and FreqCounthttps://www.pjrc.com/teensy/td_libs_FreqCount.html

tjj92:
I am using an Arduino Uno and I am limited to not use pin 2 and 3 so no hardware interrupt possible.

That is not true. You can use a pinChange interrupt on most (if not all) digital I/O pins.

But before going down that route what is using Pins 2 and 3 that cannot relinquish them to make them available for use as external interrupts?

…R

That is not true. You can use a pinChange interrupt on most (if not all) digital I/O pins.

I attempted to do so by using the EnableInterrupt library and make it trigger the ISR for the encoder pin on all rising edges.

The EnableInterrupt library is using pin change interrupts when the selected pin is not an external interrupt pin.

cattledog: The EnableInterrupt library is using pin change interrupts when the selected pin is not an external interrupt pin.

OOPs.. missed that.

@tjj92, post your complete program

...R

Thanks everyone for the help. I accidentally made the pulse time as double and just corrected to unsigned long instead. My code are as followed now.

#include <DualVNH5019MotorShield.h>
#include <EnableInterrupt.h>
int encoderpin1a=3;
int encoderpin1b=5;

int encoderpin2a=11;
int encoderpin2b=13;

long ticksmoved1=0;
long ticksmoved2=0;

unsigned long currentpulsetime1=0.0;
unsigned long currentpulsetime2=0.0;

unsigned long previoustime1=0;
unsigned long previoustime2=0;

DualVNH5019MotorShield md;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(2000000);
  pinMode(encoderpin1a, INPUT);
  pinMode(encoderpin1b, INPUT);
  pinMode(encoderpin2a, INPUT);
  pinMode(encoderpin2b, INPUT);
  enableInterrupt(encoderpin1a,encoder1change,RISING);
  enableInterrupt(encoderpin2a,encoder2change,RISING);
  md.init();
  md.setSpeeds(100,0);
}

void loop() {
  Serial.println(getRPM1());
}

void encoder1change()
{
  if (digitalRead(encoderpin1b)==LOW)
  {
    ticksmoved1++;
  }
  else
  {
    ticksmoved1--;
  }
  currentpulsetime1=micros()-previoustime1;
  previoustime1=micros();
}

void encoder2change()
{
  if (digitalRead(encoderpin2b)==LOW)
  {
    ticksmoved2++;
  }
  else
  {
    ticksmoved2--;
  }
  currentpulsetime2=micros()-previoustime2;
  previoustime2=micros();
}

double getRPM1()
{
  if (currentpulsetime1==0)
  {
    return 0;
  }
  return 60000/(((currentpulsetime1)/1000.0)*562.215);
}

double getRPM2()
{
  if (currentpulsetime2==0)
  {
    return 0;
  }
  return 60000/(((currentpulsetime2)/1000.0)*562.215);
}

cattledog:
First, get rid of the floats as suggested.

You have provided no information as to the rpm and the time scale of interrupts, but on 16 MHz Arduino boards micros() has a resolution of four microseconds (i.e. the value returned is always a multiple of four). Perhaps this is the cause of the +/- 2 rpm.

You may be able to get more precision by using a timer interrupt rather than a pin change interrupt. Take a look at FreqMeasure FreqMeasure Library, for Measuring Frequencies in the 0.1 to 1000 Hz range, or RPM Tachometer Applications
and FreqCounthttps://www.pjrc.com/teensy/td_libs_FreqCount.html

I am required to use a custom made Arduino shield to connect my encoder and it is only connected to pin 11 and 13 for one encoder and pin 3 and pin 5 for another so I guess it won’t work for the other encoder.

couka:
What is the actual RPM? 10000 +/- 2 is pretty good, 5 +/- 2 not so much, obviously.

There’s no good reason to store micros() in a double (double precision floating point) by the way.
micros() returns an unsigned long and should be stored as one.

Edit: PaulS was faster than me :slight_smile:

Another edit: I should have mentioned that on Uno and most other Arduino boards, double is not any different from float. Both are 32 Bit, so if float is not precise enough for your application, using double instead will not help.
An exception is the Due, where a double is a 64 Bit precision data type.

The actual RPM is maxed out at ~140.

unsigned long currentpulsetime1=0.0;
unsigned long currentpulsetime2=0.0;

What is the point in using a floating point value to initialize an integer variable?

camelCaseNames are far easier to read that alllowercasenames.

I am required to use a custom made Arduino shield to connect my encoder

Why?

PaulS: unsigned long currentpulsetime1=0.0; unsigned long currentpulsetime2=0.0;

What is the point in using a floating point value to initialize an integer variable?

camelCaseNames are far easier to read that alllowercasenames. Why?

Oh man sorry. Careless again and it is a bad habit of mine to name all the variables in lower case :/ I am doing a school project and it is a must for us to use the shield. They don't allow us to not use it so all my pins are fixed.

Also I just realise the RPM reading seems to be correct but there is a weird repeating pattern that is fluctuating the readings. Reading

tjj92: Oh man sorry. Careless again

Can you remove all the carelessness and post a "cared for" version of your program in your next Post. Then I will have a look at it.

...R