How to use a circular queue in conjunction with interrupt to determine motor rpm

As a preface, this is my first “major” Arduino project so I apologise in advance if I am unclear.

For my project, I need to be able to accurately set the RPM of small DC motor (somewhere in the range of 1500-5500RPM). I plan to do this using a PID controller. The RPM is being determined using a LED, phototransistor and op amp connected to an interrupt pin on an Arduino UNO. The issue with the current setup is that the measured RPM fluctuates quite a bit due to what I presume to be interference and noise affecting the interrupt pin. Someone suggested that I use a circular queue and average the last ~20 time measurements and filter out the outliers so that the measured RPM calculated in the loop is as accurate as possible.

My question is: Does this sound like a good solution and how and where should I run this circular queue? The only place I can think of is within the interrupt function but I realise this may cost a lot of time/memory.

I’ve attached my current sketch below (it doesn’t include any attempts at circular queuing). Please ignore the comments about LED internal pull-ups, I was experimenting with reducing noise around the interrupt pin.

PD_motor_control_PI.ino (2.08 KB)

Welcome to the forum. Please read the instructions for how to post so that your code is inline and in code tags. It should look like below. You will get more people to assist you if the code is posted this way instead of as an attachment.

//set the PWM pin number
int motorPwmPin =10;
//int RedLed =8;

//set desired RPM
float desire =6500.0;

float piIntegral=0;

unsigned long timeIntegral1=0;
unsigned long timeIntegral2=0;

//variable for error
float error=0;

//variable for real RPM
float rpmspeed=0;

//p control value
//to change
float Kp=0.1;
float Ki=0.05;


unsigned long time1=1;
unsigned long time2=1;


void setup() {

//pinMode(2, INPUT);
//digitalWrite(2, HIGH); 

//pinMode(3, INPUT);
  //digitalWrite(3, LOW); // pull up resistor
  //pinMode(4, INPUT);
  //digitalWrite(4, LOW); // pull up resistor

  
  //turn LED on
  //pinMode(RedLed,OUTPUT);
  //digitalWrite(RedLed,HIGH);
  // set up motor pwm pin
  
  pinMode(motorPwmPin,OUTPUT);

  //setup the serial bandwidth
  Serial.begin(9600);

  //interupt 0 will trigger function rpm at Pin 2
  attachInterrupt(0,rpm,RISING);
  //initial power
  analogWrite(motorPwmPin,255);
delay(1000);
}

void loop() {

  //calc rpm and check encoder pattern
  rpmspeed=(30000000.0)/(time2-time1);

  //verify if RPM is valid
if (rpmspeed>12000){delay(50);return;}
  //find the error

  error=desire-rpmspeed;

  //power to motor by Kp, (p control)
  //output is error multiplied by Kp
timeIntegral1=timeIntegral2;
timeIntegral2=micros();

piIntegral=piIntegral+((timeIntegral2-timeIntegral1)*error/1000000.0);


  float power = Kp*error+Ki*piIntegral;

  //PWM power between 60 and 255
  power = constrain(power,100,255);

  //export to serial monitor
 Serial.println("error:");
  Serial.println(error);
  Serial.print(" ");
  Serial.print("power:");
  Serial.print(power);
  Serial.print(" ");
  Serial.print("rpmspeed:");
  Serial.print(rpmspeed);
   Serial.print(" ");
   Serial.print("time1:");
  Serial.print(time1);
   Serial.print(" ");
   Serial.print("time2:");
  Serial.print(time2);
   Serial.print(" ");
  Serial.print("piIntegral:");
  Serial.print(piIntegral);

  //drive the motor with new value

 analogWrite(motorPwmPin,power);

 delay(200);
}

//this function is used for the interrupt
void rpm()
{
  //remember last rising time
  time1=time2;
  //record current rising time
  time2=micros();
}

There is a very simple way to get some averaging, and that is to count the interval between more than one pulse.

There are also issues with how you handle the interrupt. Time1 and Time2 need to be declared as volatile, and protected when they are read. See Nick Gammon's interrupt tutorial at Gammon Forum : Electronics : Microprocessors : Interrupts

Here's some example code of how to time the interval for more than one pulse.

volatile byte  count = 0;
byte numCount = 8; //number of pulse intervals to measure
volatile unsigned long startTime;
volatile unsigned long endTime;
unsigned long copy_startTime;
unsigned long copy_endTime;

volatile boolean finishCount = false;
float period;

unsigned int rpm = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println("start...");

  attachInterrupt(digitalPinToInterrupt(2), isrCount, RISING);//interrupt on pin 2
}

void loop()
{
  if (finishCount == true)
  {
    finishCount = false;//reset flag
    // disable interrupts, make protected copy of time values
    noInterrupts();
    copy_startTime = startTime;
    copy_endTime = endTime;
    count = 0;
    interrupts();

    period = (copy_endTime - copy_startTime) / 1000.0; //micros to millis
    //debug prints
    Serial.print(period); //total time for numCount
    Serial.print('\t');
    Serial.println(period/numCount);//time between individual pulses
    
    rpm = numCount * 60.0 * (1000.0 / period);//one counts per revolution
    //rpm = numCount * 30.0 * (1000.0 / period);//two counts per revolution
  
    Serial.print("RPM = ");
    Serial.println(rpm);
  }
}


void isrCount()
{
  if (count == 0)//first entry to isr
  {
    startTime = micros();
  }

  if (count == numCount)
  {
    endTime = micros();
    finishCount = true;    
  }
  count++; //increment after test for numCount
}

Noted and thank you for your reply. I will take a look at the interrupt tutorial.