Micros vs. Milis: Accuracy of interrupt measurements

I've been implementing a speedometer for an old vehicle and I've noticed a small (around 1-2%) inaccuracy vs. a reference signal.

The speedometer generates 16,000 pulses per mile, thus Speed(in mph) = #_of_pulses/Time*Scaling_Constant, where the # of pulses are counted by an interrupt over a defined period of time.

My code counts the number of interrupts over a delay, in this case a delay(100). It calculates Time by subtracting milis() before the delay from milis() after the delay. Time should be exactly 100 ms each period (assuming the milis() instructions take an infinitesimal amount of time to execute) and indeed when I print Time in the serial monitor I get exactly 100 most of the time, with the occasional 99 and 101 mixed in.

On the other hand, even though I'm artificially feeding a signal corresponding to a defined speed (for example, at 80 mph I would need 80*16000/3600 pulses per second) my arduino UNO outputs a measured speed of 77-78 mph.

The error seems to grow non-linearly as the speed increases (i.e., at low speeds it is less than 1% and at high speeds it's closer to 2%). The small inaccuracy does not go away if I use a constant 100 ms for Time rather than calculate the time period in each loop.

At this point my hypothesis is that I'm either (a) "missing" pulses that fall in between cycles (but this would not explain why the inaccuracy seems to grow non-linearly with speed; it should in fact decrease with speed), or (b) somehow the delay or real time calculation is somehow affected by the interrupts.

I read somewhere that milis() depends on interrupts and is therefore not too reliable to use when multiple interrupts are in use. I wonder if delay(time) also suffers from the same issue. If so, would using micros() (which I also read somewhere does NOT depend on interrupts) help?

Anyone seen a similar issue?

Code is below...

SpeedometerNoSend.ino (988 Bytes)

Why use delay at all? Just go into a busy-wait until the current millis() is 100 greater than the old one:

  time1 = millis();
  while(millis() < time1+100);
  time = 100;

You might get better resolution if you use micros() instead, but be careful that it will require long integer arithmetic - e.g. "while(micros() < time1+100000L);"

Pete

BTW. The variables which are used in the interrupt routine must be declared volatile, especially "counter".

Pete

Keep in mind, micros() will overflow after approx ~70 min, so if you intend to use micros() in a program that you expect to run for longer periods you need to keep that in mind.

Ugh. Yes, don't use delay()!

I agree about using micros() rather than millis(). And use unsigned long for the datatypes. And use subtraction to avoid rollover problems.

Look in my signature under "Multitasking".

azarur:
Anyone seen a similar issue?

There is no issue. The millis() function is just not counting each single millisecond.

The millis() function is based on fractions of 1/1024 second.

So in 1000 milliseconds this will happen:
millis() counts up by '1' ==> 977 times
millis() counts up by '2' ==> 23 times
==> total count of millis() counter after 1000 milliseconds ==> 1000

So millis() can always be 1 millisecond inaccurate.

If you need precise timing for small amounts of time, you better use micros() instead of millis().

(nightsd01+polymorph): Yup, I was composing a reply about rollover when you replied. The correct test (I hope!) should be:

while(millis() - time1 < 100);

Pete

el_supremo:
Why use delay at all? Just go into a busy-wait until the current millis() is 100 greater than the old one:

  time1 = millis();

while(millis() < time1+100);
 time = 100;




You might get better resolution if you use micros() instead, but be careful that it will require long integer arithmetic - e.g. "while(micros() < time1+100000L);"

Pete

Hey Pete,

How's that counter any different than a delay()?
It's still waiting around for 100 ms and not doing anything in between...
Thanks fo ryour help!
A

This specifically talks about avoiding cumulative errors:

http://forum.arduino.cc/index.php?topic=223286.0

Specifically this code fragment illustrates it:

void updateOnBoardLedState() {

 if (onBoardLedState == LOW) {
         // if the Led is off, we must wait for the interval to expire before turning it on
   if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
         // time is up, so change the state to HIGH
      onBoardLedState = HIGH;
         // and save the time when we made the change
      previousOnBoardLedMillis += onBoardLedInterval;
         // NOTE: The previous line could alternatively be
         //              previousOnBoardLedMillis = currentMillis
         //        which is the style used in the BlinkWithoutDelay example sketch
         //        Adding on the interval is a better way to ensure that succesive periods are identical

   }

Specifically, I have to stop repeating certain words, specifically specifically. Actually.

All,
Thanks for your help. I implemented the while() busy-wait instead of the delay() method, and still get the exact same problem. I think this means the inaccuracy does not have to do with the measurement of time but rather with the counting of the interrupts. I connected the signal generator to my o'scope and the frequency and shape of the function is correct... I think something else is going on in here!!

azarur:
I think something else is going on in here!!

Your code has some logical problems

  • creating accurate intervals
  • handling variables correctly if they are used in interrupt handling and normal code

My try fixing it:

int pbIn = 0;                  // Interrupt 0 is on DIGITAL PIN 2!
int ledOut = 4;                // The output LED pin
double rpm = 0;
volatile int counter_isr = 0; // counter in isr must be volatile too
int counter=0;
int displaycounter = 0;

volatile int state = LOW;      // The input state toggle
 
void setup()
{                
  // Set up the digital pin 2 to an Interrupt and Pin 4 to an Output
  pinMode(ledOut, OUTPUT);
  Serial.begin(9600);
  //Attach the interrupt to the input pin and monitor for ANY Change 
  attachInterrupt(pbIn, stateChange, CHANGE);
}
 
unsigned long lastCycleStart;
void loop()                     
{
  while (micros()-lastCycleStart<100000L);
  lastCycleStart+=100000L;

  // now let's read the volatile variable and copy it into a normal variable
  noInterrupts(); // disable interrupts
  counter=counter_isr; // copy variable for further usage
  counter_isr = 0;  // reset variable
  interrupts();  // enable interrupts
  
  rpm = 1.11375*counter;
  Serial.println();
  Serial.print(rpm);
  Serial.print("\t");
  Serial.print(lastCycleStart/1000000.0,6);
  Serial.print("\t");
  Serial.print(counter);
  Serial.print("\t");
}
 
void stateChange()
{
  state = !state;
  digitalWrite(ledOut, state);
  counter_isr++;  
  displaycounter++;
}

azarur: now that you mention it, I am not sure it is any different.

Pete

jurs:
Your code has some logical problems

  • creating accurate intervals
  • handling variables correctly if they are used in interrupt handling and normal code

Thanks Jurs. I did correct the volatile variable issue. However no luck... have not tried disabling interrupts while I do the calculation. That may indeed be the culprit as it would change the counter after the time period has been calculated.... will try that next!
Thanks!!

AZ

I know with the ATmega328P if you manipulate Timer0 it can effect those various time keeping functions your using along with Interrupts.

16,000 pulses per mile. So 1mph is 4.44 pulses per second, 10mph is 44.4 pps, 50mph is 222.2 pps. I'm not surprised that it would shift around at lower speeds using milliseconds to count pulses.

Or to look at it another way, 0.225 seconds per period at 1mph, 0.0225 spp at 10mph, and 0.0045 spp at 50mph.

Yeah, at only 4.5ms per period at 50mph, millis() is far too coarse to count the period.

eRCaGuy_Timer2_Counter - [Update: 17 May 2014 - Timer2_Counter has been turned into a true Arduino Library. New example added.] A generic Arduino micros()-equivalent timing function with 0.5us precision (rather than the 4us precision of the built-in micros() function). --This allows, as one example, a very precise reading of Radio-Control (RC) PPM and PWM signals, from an RC transmitter and receiver, respectively, using external interrupts, without using the Atmega Timer1 which would disable use of the Servo library. Tested only on Arduinos with an Atmega328

Frequency Counter - using Timer1
http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-frequency-counter-library/

FreqPeriodCounter – Versatile frequency counter which measures frequency, period, pulse width etc. Can be used interrupt triggered or polled.
http://www.avdweb.nl/arduino/hardware-interfacing/frequency-period-counter.html

How about pulseIn()? Times to microseconds. You might want to use a divide-by-two in hardware first, to avoid errors due to changes in duty cycle. Just don't forget to take account of that in software.

At 100mph, that would still be 450us per period, or 900us if you divide by two first.

A great resource:

http://playground.arduino.cc/Main/LibraryList

My page Gammon Forum : Electronics : Microprocessors : millis() overflow ... a bad thing? discusses the inaccuracy of millis().

Also on my Gammon Forum : Electronics : Microprocessors : Interrupts page I advise against counting things using delay (specifically http://www.gammon.com.au/forum/?id=11488&reply=10#reply10 ).

Also see Gammon Forum : Electronics : Microprocessors : Timers and counters where I have various frequency-counting sketches using the hardware timer. Using the hardware eliminates jitter caused by slight delays in interrupts firing.

The OP will have to let us know if any of this helps him.