Rpm measure using millis() vs micros() give different results

I am measuring engine rpm, from 1000 to 12000rpm, using a Arduino IOT 33 Sense.
If i use millis() function, the result is fine. But if i use micros() for having more resolution at high rpm, i have an error offset that increases with rpm.

Real 3000rpm = 3000rpm (using millis()) = 2959rpm (using micros())
Real 6000rpm = 6000rpm (using millis()) = 5930rpm (using micros())
Real 12000rpm = 12000rpm (using millis()) = 11840rpm (using micros())

Am i doing something wrong?

int cont=0;
float rpm;
int tiempoinicial=0;
int tiempo;
int interruptPin = D2;

void isr() //interrupt service routine
if (cont>=10) //average of 10 cycles
tiempo=millis()-tiempoinicial; //or micros()
tiempoinicial=millis(); //or micros()

void setup()
pinMode(interruptPin, INPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin),isr,FALLING); //attaching the interrupt

void loop()
rpm = (600000 / float(tiempo)); //calculation set up for millis()
Serial.print("rpm: ");

You should be using unsigned long when dealing with micros() and millis()

. . . and variables used in an ISR should be defined as volatile.
. . . and, depending on the MPU, variables larger than a byte, used in the loop, should be protected against asynchronous update (non atomic operations).
. . . and do you really want to print something every loop iteration ?

Hi. I understand that it is mandatory to use it with a 8 bit processor, but i don't think it is needed with a 32 bit one, as the Arduino IOT 33 Sense. Thanks!

i will investigate about volatile variables...yes, i think i read something about.
Don't pay attention to the Serial.print. I just tried to do the simplest program just for this post.

The error does not increase with RPM, at least not percentage-wise

3000  /  2959    = 1.0138560324

6000  /  5930    = 1.0118043845

12000 / 11840    = 1.0135135135

could it just be you aren't running at whatever, 16 Mhz, exactly?

How are you measuring the "real" RPM?

Just having fun with the numbers here.


I don’t think both functions return something other than an unsigned long on 32 bit, why would you want to assign result to a type that might not hold it?

ouch! the % of error is constant, you are right.
The "Arduino 33 IOT Sense" run at 64 MHz. Plenty of power!!
The "real" RPM are generated by a multimeter. And i checked it using a small oscilloscope.

According arduino manuals, the micros() overflow after 70 minutes. That gives a max value of An int variable in a 32bit processor has the same lenght that a unsigned long of a 8 bit processor (4.294.967.296). So it should work. I will wait 70 minutes to check it :wink:

Just in case, i updated the program with "volatile int tiempo;" as it is used inside and outside the ISR. But not sure if it is necesary in this processor, as it is not an Atmel. The issue is still present.

This is not the length this is max value and no, it is not the same. it is true for unsigned but not for signed as its max value only half of that. If you assign unsigned value to signed after 2147483647 it will become negative. But it is true both int and long int are 4 bytes on 32 bit core, doesn’t mean you can just assign unsigned long to an int and expect no trouble.

This is not correct. An int in this case has only 31 bits plus a sign bit. An unsigned int has 32 bits to represent the number. Did you get any compiler warnings about this ?

The possible issue of non-atomic operations mentioned in post #3 is not relevant here since a 32 bit processor does the entire operation in a single unit. I missed the part about IOT sense being a 32 bit processor.

Totally right! I corrected it. Thanks

Did you get any compiler warnings about this ? no, but i modifiend to "unsigned int". I will check it waiting 70min!

Well, i did a few corrections due to comments received, but they were nor really related to the issue. The results are still different if i use micros() vs millis(). It has not sense for me.

I'd try it a little differently?

#include <timeObj.h>
#include <mapper.h>
#include <runningAvg.h>

#define  INT_PIN     D2
#define  READING_MS  500      // How long to let the counts add up.
#define  MAX_COUNTS  1000     // <<- CHANGE THIS The max number of counts we should ever see during READING_MS
#define  MAX_RPM     1000     // <<- CHANGE THIS Given this max amunt of counts, this is the max RPM that vallue gives.

timeObj        readingTimer(READING_MS);           // Set up a timer for grabbing the result.
mapper         RPMmapper(0,MAX_COUNTS,0,MAX_RPM);  // Set up a float mapper to do the math.
runningAvg     smoother(10);                       // We'll avarage over 10 readings.
volatile long  count;
long           savedCount;
float          RPM;

void isr() { count++; } //interrupt service routine

void setup(void) {
   count = 0;
   pinMode(INT_PIN, INPUT);
   attachInterrupt(digitalPinToInterrupt(INT_PIN),isr,FALLING); //attaching the interrupt

void loop (void) {
   if (readingTimer.ding()) {                               // If the count timer has expired..
      savedCount = count;                                   // FIRST thing we do is save the counts. (Could change)
      count = 0;                                            // NEXT is to clear the original counter for the next set.
      readingTimer.stepTime();                              // Set up the timer for the next reading..
      RPM = smoother.addData(RPMmapper.map(savedCount));    // WHEWH, now we have some time to play with. DO the math.
      Serial.print("rpm: ");                                // Show the results to the user.

I don't have hardware to test this, But at least, if it doesn't work out of the box you can see the approach. Maybe it'll help?

Before compiling you will need to calcualte a couple things..

I set it up for a reading refresh every 1/2 second..

What is the MAX number of counts you should ever see during that amount of time?


What is the maximum RPM that this max number of counts would result it?

Plug in those two numbers, grab LC_baseTools from the IDE library manager and you should be good to go.

Fingers crossed!

Good luck!

-jim lee

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.