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?
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);"
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.
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().
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
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
}
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++;
}
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!!
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
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.