Unable to record Motor RPM greater than 16k

This code works well up to around 16000 RPM and then it breaks :smiling_face_with_tear: . After a bit of playing around I found that the elapsed variable (time between two consecutive detection signals from the optical sensor) isn't able to go below 3500-3600 microseconds. This roughly translates to 16,500-17,000 RPM. Here's the code:

/* This program calculates RPM by taking signal inputs from an
   optical IR sensor module. The signal OUT pin from the IR sensor
   module is connected to DigitalPin 2 of Uno/Nano. Whenever a signal
   is detected HIGH, the intererupt routine is executed where the elapsed 
   time between the current and last detection is calculated. This data is
   then convereted to RPM in setup().
*/


unsigned long printInterval = 100; // time interval to serial.Print (in milliseconds)

//set minimum speed as 1 RPS, speed < 1 RPS is counted as zero
unsigned long senseThreshold = 1000000; // in microseconds
unsigned long lastSense = 0; //

float RPM = 0.0;
unsigned long printTimer_start = 0;
unsigned long timerStart = 0;
unsigned long timerStop = 0;
unsigned long elapsed = 0;

void update_timer()
{
  timerStop = micros();
  elapsed = timerStop - timerStart; //calcultate time between two consecutive pulses
}

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


void loop()
{
  if (elapsed != 0) {
    RPM = 60000000.0 / elapsed;
  }
 
  if (elapsed  == 0 || lastSense > senseThreshold ) {
    RPM = 0;
  }

  //  Printing RPM to serial port at the specified rate (in printInterval)
  unsigned long printTimer_stop = millis();
  if (printTimer_stop - printTimer_start >= printInterval) {
    Serial.println(RPM);
    printTimer_start = printTimer_stop;
  }

  lastSense = micros() - timerStart;
  timerStart = timerStop;
  attachInterrupt(0, update_timer, RISING); // interrupt when a signal is detected

}

you probably need volatile variables and critical sections as you deal with an ISR

I have tried volatile variables. No luck. By critical section, I guess you are talking about the detachInterrupt (). Something like this?

void loop()
{
  detachInterrupt(0);

  if (elapsed != 0) {
    RPM = 60000000.0 / elapsed;
  }
 
  if (elapsed  == 0 || lastSense > senseThreshold ) {
    RPM = 0;
  }

  //  Printing RPM to serial port at the specified rate (in printInterval)
  unsigned long printTimer_stop = millis();
  if (printTimer_stop - printTimer_start >= printInterval) {
    Serial.println(RPM);
    printTimer_start = printTimer_stop;
  }

  lastSense = micros() - timerStart;
  timerStart = timerStop;
  
  attachInterrupt(0, update_timer, RISING); // interrupt when a signal is detected

}

But again, I was not able get past the 16k mark. By the way, I am relatively new to Arduino, so apologies if I have misunderstood anything here.

/* This program calculates RPM by taking signal inputs from an
   optical IR sensor module. The signal OUT pin from the IR sensor
   module is connected to DigitalPin 2 of Uno/Nano. Whenever a signal
   is detected HIGH, the intererupt routine is executed where the Count
   time between the current and last detection is calculated. This data is
   then convereted to RPM in setup().
*/
float RPM = 0.0;
unsigned long Period = 100000;
unsigned long timerStart = 0;
volatile unsigned long Count = 0;


void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(2), update_timer, RISING); // interrupt when a signal is detected
  timerStart = micros();
}

void update_timer() {
  Count++; //calcultate time between two consecutive pulses
}

void loop() {
  if (micros() - timerStart >= Period ) {
    noInterrupts();
    RPM = 60000000.0 * (float)Count / (float)timerStart;
    Count = 0;
    Serial.println(RPM, 1);
    timerStart += Period;
    interrupts();
  }
}

Perhaps you are overrunning the frequency response of your optical sensor module. Have you looked at it on your scope to ensure you're getting a clean signal? The other thing that makes me go "Hmmm" is the baud rate you are using. Perhaps 9600 baud is not getting the data printed fast enough to catch the next pulse correctly.

1 Like

What is your RPM goal?

20,000 is a checkpoint for now. 50,000 is the goal.

mark as solution

Always try to minimise the time that interrupts are disabled:

void loop() {
  unsigned long elapsed = micros() - timerStart;
  if (elapsed >= Period ) {
    noInterrupts();
    unsigned long finalCount = Count;
    Count = 0;
    interrupts();
    RPM = 60000000.0 * (float)finalCount / (float)elapsed;
    Serial.println(RPM, 1);
    timerStart += Period;
  }

???

I would also significantly minimize the print requests and definitely not use 9600 bauds which might let the data accumulate in the outgoing buffer and transform print in a blocking call

Hi kolaha,
I did run your version today but I'm afraid it did not solve the problem. I was getting RPMs as single digit numbers even at expected RPM > 10000. I couldn't go through your code and see where the problem exactly was. Will give it a try again tomorrow. Thanks alot btw.

have you tried @PaulRB's solution?

Hi,
Can you post a circuit diagram of your project?
A hand drawn image will be fine, please include ALL power supplies, component names and pin labels.

A couple of images of your project would also help.

What model Arduino are you using?

Thanks... Tom... :smiley: :+1: :coffee: :australia:

Perhaps you could use Timer1 as a counter. You could set an appropriate compare value that will allow you to count revolutions. Once the compare value is reached (or you could use the overflow) you read the elapsed time between enabling the counter and rollover.

This would not only get you to 50k (I think, I've not looked at the numbers) but it will also average your pulses.

John

1 Like

Perhaps it would speed up thing if you wrote your own for(;:wink: inside the loop, not depending on whatever overhead is present outside your code.

Second, instead of relying on the resolution of micros() for what seems from your code to address time between individual sensor ticks, to run a tight loop, counting on and off periods, assuming your sensor doesn't give off noise.

Something like

#define SAMPLES 100
int count=SAMPLES;

unsigned long start=micros(); 
while(count>0) {
	while (sensor==on) ;
    while (sensor==off) ;
    count--;
}
unsigned long end=micros();
// calculate RPM across number of samples
 

Of course you'd like to extend the code with some failsafe against hanging forever. This is just an example :slight_smile:

1 Like

@TomGeorge, Here's the schematic of my setup. I am using Arduino Nano.

And, if it helps, the circuit diagram of the sensor:
(source : IR Sensor Module Circuit)

Everyone, I was finally able to read 20k RPM. The changes are:

  1. Baud rate set to 57,600 (earlier 9600)
  2. detachInterrupt() invoked while transmitting RPM value through serial

@J-M-L @ggutshal @PaulRB your replies have been of great help. Improvements are definitely required in terms of smoothing the output, and I think that's where the suggestions from @JohnRob @Rupert909 should work.

Here's the working version of the code:

/* This program calculates RPM by taking signal inputs from an
   optical IR sensor module. The signal OUT pin from the IR sensor
   module is connected to DigitalPin 2 of Uno/Nano. Whenever a signal
   is detected HIGH, the intererupt routine is executed where the elapsed 
   time between the current and last detection is calculated. This data is
   then convereted to RPM in setup().
*/


unsigned long printInterval = 100; // time interval to serial.Print (in milliseconds)

//set minimum speed as 1 RPS, speed < 1 RPS is counted as zero
unsigned long senseThreshold = 1000000; // in microseconds
unsigned long lastSense = 0; //

float RPM = 0.0;
unsigned long printTimer_start = 0;
unsigned long timerStart = 0;
unsigned long timerStop = 0;
unsigned long elapsed = 0;

void update_timer()
{
  timerStop = micros();
  elapsed = timerStop - timerStart; //calcultate time between two consecutive pulses
}

void setup()
{
  Serial.begin(57600);
  attachInterrupt(0, update_timer, RISING); 
}


void loop()
{
  if (elapsed != 0) {
    RPM = 60000000.0 / elapsed;
  }
 
  if (elapsed  == 0 || lastSense > senseThreshold ) {
    RPM = 0;
  }

  //  Printing RPM to serial port at the specified rate (in printInterval)
  unsigned long printTimer_stop = millis();
  if (printTimer_stop - printTimer_start >= printInterval) {
    detachInterrupt(0);
    Serial.println(RPM);
    printTimer_start = printTimer_stop;
  }

  lastSense = micros() - timerStart;
  timerStart = timerStop; 
  attachInterrupt(0, update_timer, RISING); // interrupt when a signal is detected

}

If a higher baud rate helps, try a faster one still: 115200 is common and some folks have it working at rather more than that.

1 Like

Hi,
The sensor operates on 5V , yours is connected to 3V3?

Also try 115200 Baud.

Tom... :smiley: :+1: :coffee: :australia: