InfraRed sensor used for rpm calculation of a motor

Hello,

First time posting on a forum ever, so, please forgive me if I do not explain myself well or do basic mistakes. Thank you in advance !

So, my problem concern the use of an IR sensor to measure the rpm of a motor (actually two motors with two sensors). The first thing I need to say is that the motor used is a stepper. I know that stepper motors are not actually used in speed control but more in position. Moreover, by knowing the number of steps done in a certain amount of time, we can extrapolate the rotation speed of the motor. But, these were the only motors available and the rpm calculation by IR sensor is a way to assure that everything is function in the way we want to.

Let’s get back to the actual problem. The rpm calculation is done by using interrupt routines. These interrupt routines seem the get triggered more than it actually should be triggered. I tried to look at the signal of one of the interrupt pin in the serial plotter (see attached picture) to see if I can observe any issues. As you can see, it seems to behave as wanted but for every falling edge I get the interrupt routine running 1,2 or even 3 times (randomly). Do you have any idea why ? I tried to impose a time between two interrupt routine by doing the following

void InterruptRoutine()
{
  if ( millis() - lastInterrupt > 120) {
    // Do the routine
    lastInterrupt = millis()
  }
}

but it didn’t helped…

Thank you very much for your help.

My code (yeah the full code just in case) :

#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>
#include <TimerOne.h>
#include <TimerThree.h>

// The LCD uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();


// These #defines make it easy to set the backlight color
#define OFF 0x0
#define ON 0x1

// External interrput pins for sensors
#define interrupt1Pin 2
#define interrupt2Pin 18

// Definition of variables used for the rpm calculation
long lastUpdate = 0;  // for timing display updates
volatile long accumulatorF = 0, accumulatorB = 0;  // sum of last 8 revolution times
volatile unsigned long startTimeF = 0, startTimeB = 0; // start of revolution in microseconds
volatile unsigned int revCountF = 0, revCountB = 0; // number of revolutions since last display update

// Definition of Pins of the motors
#define motor1Pin 11
#define motor2Pin 3
#define motor1DirPin 10
#define motor2DirPin 4

//***************************************************************************************************
// Here define the wanted speed in us/step
int targetSpeed1[32] = {2500};
int targetSpeed2[32] = {2500};
// For clarity define the name of the states here
String StateUp[32] = {"Test IR"};
String StateDown[32] = {"Flyer at 60rpm"};
int screen = 0;
//****************************************************************************************************

// Variable definition for the motor control
int motor1Speed;
int motor2Speed;
int i = 0;
int old_i = 0;

void setup() {
  // set up of the LCD
  lcd.begin(16, 2);
  lcd.setBacklight(ON);

  // Set the slow mode speed
  targetSpeed1[31] = 10388;
  targetSpeed2[31] = 10862;

  // Enable the pins of the motor
  pinMode(motor1Pin, OUTPUT);
  pinMode(motor1DirPin, OUTPUT);
  digitalWrite(motor1DirPin, LOW);
  pinMode(motor2Pin, OUTPUT);
  pinMode(motor2DirPin, OUTPUT);
  digitalWrite(motor2DirPin, HIGH);

  // waiting procedure
  lcd.setCursor(0,0);
  lcd.print("Waiting");
  lcd.setCursor(0,1);
  lcd.print("Press select");

  int Wait = 1;
  while (Wait == 1) {
    // Button definition
    uint8_t buttons = lcd.readButtons();
    if (buttons) {
      if (buttons & BUTTON_SELECT) {
        Wait = 0;
      }
    }
  }

  // Enable the pullup resistor and attach the interrupt 
  pinMode(interrupt2Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interrupt2Pin), tach_interrupt2, FALLING);
  pinMode(interrupt1Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interrupt1Pin), tach_interrupt1, FALLING);
  

  // Set the timers and variables for the motor control
  Timer1.initialize(targetSpeed1[i]);
  Timer3.initialize(targetSpeed2[i]);
  motor1Speed = targetSpeed1[i];
  motor2Speed = targetSpeed2[i];
  Timer1.pwm(motor1Pin, 512);
  Timer3.pwm(motor2Pin, 512);
}

void loop() {
  //rpm calculation
  if (millis() - lastUpdate > 10000) { // update every 10 seconds 
    unsigned int rpmB = 0;
    unsigned int rpmF = 0;
    // divide number of microseconds in a minute, by the average interval.
    if (revCountF > 0){
      rpmF = 60000000 / (accumulatorF>>3);
    }
    if (revCountB > 0){
      rpmB = 60000000 / (accumulatorB>>3);
    }
    if (screen == 0) {
      lcd.clear();
      lcd.setCursor(8,0); lcd.print(rpmF);
      lcd.setCursor(8,1); lcd.print(rpmB);
      }
    lastUpdate = millis();
    revCountF = 0;
    revCountB = 0;
  }
  
  //Speed control
  if (motor1Speed > targetSpeed1[i]) {motor1Speed--; Timer1.setPeriod(motor1Speed); Timer1.pwm(motor1Pin, 512);}
  else if (motor1Speed < targetSpeed1[i]) {motor1Speed++; Timer1.setPeriod(motor1Speed); Timer3.pwm(motor2Pin, 512);}
  
  if (motor2Speed > targetSpeed2[i]) {motor2Speed--; Timer3.setPeriod(motor2Speed); Timer1.pwm(motor1Pin, 512);}
  else if (motor2Speed < targetSpeed2[i]) {motor2Speed++; Timer3.setPeriod(motor2Speed); Timer3.pwm(motor2Pin, 512);}

  uint8_t buttons = lcd.readButtons();
  if (buttons) {
    if (buttons & BUTTON_UP) {i++;}
    if (buttons & BUTTON_DOWN) {i--;}
    if (buttons & BUTTON_LEFT) {old_i  = i; i = 31;}
    if (buttons & BUTTON_RIGHT) {i = old_i;}
  }

  switch (screen) {
    case 0:
      lcd.setCursor(0,0); lcd.print("Flyer:");
      lcd.setCursor(13,0); lcd.print("rpm");
      lcd.setCursor(0,1); lcd.print("Bobbin:");
      lcd.setCursor(13,1); lcd.print("rpm");
      if (buttons) {
        if (buttons & BUTTON_SELECT) {screen = 1; lcd.clear();}
      }
      break;
    case 1:
      lcd.setCursor(0,0); lcd.print(StateUp[i]);
      lcd.setCursor(0,1); lcd.print(StateDown[i]);
      if (buttons) {
        if (buttons & BUTTON_SELECT) {screen = 0; lcd.clear();} 
      }
      break;
  }
  
}

//-------------------------------------
// Interrupt Handler
// IR reflective sensor - target passed
// Calculate revolution time
//-------------------------------------

unsigned long lastInterrupt1;

void tach_interrupt1()
{
    // calculate the microseconds since the last interrupt
    long usNowF = micros();
    long elapsedF = usNowF - startTimeF;
    startTimeF = usNowF;  // reset the clock
  
    // Accumulate the last 8 interrupt intervals
    accumulatorF -= (accumulatorF >> 3);
    accumulatorF += elapsedF;
    revCountF++;
}

unsigned long lastInterrupt2;

void tach_interrupt2()
{
    // calculate the microseconds since the last interrupt
    long usNowB = micros();
    long elapsedB = usNowB - startTimeB;
    startTimeB = usNowB;  // reset the clock
  
    // Accumulate the last 8 interrupt intervals
    accumulatorB -= (accumulatorB >> 3);
    accumulatorB += elapsedB;
    revCountB++;
}

int targetSpeed1[32] = {2500};
int targetSpeed2[32] = {2500};
// For clarity define the name of the states here
String StateUp[32] = {"Test IR"};
String StateDown[32] = {"Flyer at 60rpm"};

Why arrays with 32 items? Do you really need them?

As you can see, it seems to behave as wanted but for every falling edge I get the interrupt routine running 1,2 or even 3 times (randomly).

Most probably a hardware problem. Post a wiring diagram!

I tried to look at the signal of one of the interrupt pin in the serial plotter (see attached picture) to see if I can observe any issues.

I don’t know how fast that signal is, but if the serial plotter is enough to show it, you don’t need interrupts for it.

Take a look at the signal with a scope to see if it’s clean enough for the purpose.

The correct hardware measure to avoid duplicate interrupts is called a Schmitt trigger.

I did implement that in software and got very accurate rpm readings:

P.S:
Lately I used ESP32-CAM CameraWebServer.ino sketch with some added features that allowed me to measure 27272rpm of mini drone propeller by just capturing a frame with a precisely timed (550µs length) flash in darkness (not what I want to do with the ov2640 camera, and not as precise as counting interrupts):
https://www.esp32.com/viewtopic.php?f=19&t=11126&p=45841#p45780