Increasing RPM accuracy with IR sensor

Hi Everyone,

This is my first post to the Forum, so please bear with me. I am trying to increase the accuracy of my rpm reading from 2 IR sensors that are measuring the rpm of a 8.5" diameter rotating shaft. I have placed an encoder strip on each shaft with 100 "tick" marks on it and I am using the attachInterrupt function to record the data. In order to be able to compare the to rotating shafts, I am recording the rpm every 100 ms. I am using time to be abel to compare the 2 shafts so that my data arrays are the same length.

The problem I am having is that if I reduce the accuracy number (recording data more often), I get erratic rpm readings. I do not know if this is because I am trying to take data too often or if it is an alignment issue. Any help or suggestions on this matter would be greatly appreciated.

IR Sensors: SEN-00246 - Optical Detector / Phototransistor - QRD1114

#include <SdFat.h>
#include <String.h>

const int chipSelect = 8;

//Change these variables
int accuracy = 100;   //This is the number of ms between data points
int targetRPM = 900;
int num = 1;        // of our file.

//Naming our Files

String frontFile = "Fron";    //Create an array that contains this name 
String rearFile = "Rear";      //Crears an arrya that contains the name of the rear rpm
String timeFile = "Time";
String text = ".txt";
char fFile[10];
char rFile[10];
char tFile[10];
char contents[100]; 


//SD Card Setup
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile test;
SdFile file;
int i = 0;

//MOTOR
int motorPin = 10;

//Solenoid
int solenoidPin = 9;

//BUTTON
int switchPin = 5;
boolean lastButton = LOW;
boolean ledOn = false;
boolean currentButton = LOW;

//Front RPM Variables
int fRpmPin = 0;       //Digital pin 2
unsigned long rpmFront = 0;
volatile byte frontCount = 0;
unsigned long deltaT = 0;
unsigned long lastRpm_F = 0;
SdFile front;

//Rear RPM Variables
int rRpmPin = 1;    //Digital pin 3
unsigned long rpmRear = 0;
volatile byte rearCount = 0;
unsigned long deltaT_Rear = 0;
SdFile rear;

//Other Variables
unsigned long rpmFactor = 60000;    // millisec*sec = 1000*60
boolean motorOff = false;
boolean changeMotor = false;
unsigned long lastTime = 0;

//Time Variables
unsigned long plotTime = 0;
SdFile time;

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

  //SD CARD SETUP
  pinMode(10, OUTPUT);
  card.init(SPI_FULL_SPEED, 8);
  volume.init(card);
  root.openRoot(volume);

  //Switch SETUP 
  pinMode(switchPin, INPUT);
  pinMode(solenoidPin, OUTPUT);
  pinMode(motorPin, OUTPUT);

  //IR Sensors
  attachInterrupt(fRpmPin, frontISR, CHANGE);
  attachInterrupt(rRpmPin, rearISR, CHANGE);

  NAME_FILE();

}

void loop() {
  currentButton = debounce(lastButton);

  if (lastButton == LOW && currentButton == HIGH) {
    changeMotor = !changeMotor;
    digitalWrite(motorPin, changeMotor);
  }
  NEXT_FILE();
  RPM();

  lastButton = currentButton;
}


void RPM() {
  deltaT = millis() - lastTime;  

  if (deltaT >=accuracy) {
    lastRpm_F = rpmFront;
    rpmFront = rpmFactor*frontCount/(99*deltaT);
    rpmRear = rpmFactor*rearCount/(102*deltaT);  //3 more ticks on this rotor thus the 102 vs 99
    frontCount = 0;
    rearCount = 0;

    Serial.println(rpmFront);

    lastTime = millis();

    if (rpmFront >= targetRPM | rpmRear >= targetRPM | motorOff == true) {

      //Serial.println("writing data");
      front.open(root, fFile, O_CREAT | O_APPEND | O_WRITE);
      sprintf(contents, "%d\n", rpmFront);
      front.print(contents);
      front.close();
      // Serial.println(rpmFront);

      rear.open(root, rFile, O_CREAT | O_APPEND | O_WRITE);
      sprintf(contents, "%d\n", rpmRear);
      rear.print(contents);
      rear.close();

      //Records time for ploting later
      plotTime = plotTime + accuracy;
      time.open(root, tFile, O_CREAT | O_APPEND | O_WRITE);
      sprintf(contents,"%d\n", plotTime);
      time.print(contents);
      time.close();

      if (lastRpm_F >= targetRPM |motorOff == true) {
        //Turns motor off at target RPM
        motorOff = true;
        digitalWrite(motorPin, LOW);
        digitalWrite(solenoidPin, HIGH);
      }


    }
  }
}



//This code read whether the switch is on or off
boolean debounce(boolean last)
{
  boolean current = digitalRead(switchPin);
  if (last != current)
  {
    delay(5);
    current = digitalRead(switchPin);
  }
  return current;
}

//creates a new file and restarts the cycle
void NEXT_FILE() {
  if (rpmFront <=40 && motorOff == true) {
    num++;
    rpmFront = 0;
    rpmRear = 0;
    digitalWrite(solenoidPin, LOW);
    delay(7000);
    digitalWrite(motorPin, HIGH);

    motorOff = false;
    frontCount = 0;
    rearCount = 0;
    plotTime = 0;

    frontFile = "Fron";    //Create an array that contains this name 
    rearFile = "Rear";      //Crears an arrya that contains the name of the rear rpm
    timeFile = "Time";
    NAME_FILE();
  }
}

void NAME_FILE() {
  frontFile = frontFile + num;
  frontFile.concat(text);
  frontFile.toCharArray(fFile, 14);
  Serial.println(fFile);

  rearFile = rearFile + num;
  rearFile.concat(text);
  rearFile.toCharArray(rFile,14);
  Serial.println(rFile);

  timeFile = timeFile + num;
  timeFile.concat(text);
  timeFile.toCharArray(tFile,14);
  Serial.println(tFile);
  
}

//Front interrupt Servie routine
void frontISR() {
  frontCount++;
}

//Rear interrupt service routine
void rearISR() {
  rearCount++;
}

The problem I am having is that if I reduce the accuracy number (recording data more often), I get erratic rpm readings. I do not know if this is because I am trying to take data too often or if it is an alignment issue. Any help or suggestions on this matter would be greatly appreciated.

quick look

  • I would change volatile byte frontCount = 0; to volatile unsigned long frontCount = 0; to keep overflows out ...(now occurs every 256 pulses
  • I would change attachInterrupt(....CHANGE) to RISING , otherwise you get 2 interrupts per tick mark (causes the byte to overflow twice as fast ...)

might help

KE7GKP:
I can't see why you would need any more than 10 segments per revolution or even 6 or 4 or 2!

That's because you are assuming something like an electric motor driving a constant load. Imagine a four cylinder engine or a motor driving a few compressor cylinders. If you plot instantaneous shaft velocity versus time you can easily tell what the load is doing just from that plot. That may not be the application here, but that is an example of why it could matter.

Or you just want to know where (which angle) it stopped ... Most of the time there are other mechanical thingies connected to the wheel measured, the might need to know that angle (e.g. valves)

Or maybe you want to sense the relative positions of both ends of the same shaft to measure torque.

Then 100 tick marks may be a minimum I guess - I can imagine it working perfectly for that too -

I would implement both of robtillaart's suggestions. Also, in RPM() don't call Serial.println between clearing frontCount and backCount and assigning lastTime - you want to reset the counts as close to assigning lastTime as possible. You might even need to disable interrupts during these 3 operations. Something like this:

void RPM() {
  deltaT = millis() - lastTime;  

  if (deltaT >=accuracy) {
    noInterrupts();
    unsigned long currentTime = millis();
    unsigned int lastFrontCount = frontCount;
    unsigned int lastRearCount = rearCount;
    frontCount = 0;
    rearCount = 0;
    interrupts();
    deltaT = currentTime - lastTime;
    lastTime = currentTime;

    lastRpm_F = rpmFront;
    rpmFront = rpmFactor*lastFrontCount/(99*deltaT);
    rpmRear = rpmFactor*lastRearCount/(102*deltaT);  //3 more ticks on this rotor thus the 102 vs 99
    Serial.println(rpmFront);
...