Timer overflow not handled
Closed this issue · 7 comments
This is a nice idea. Thanks for putting it out there.
There's a problem with timer overflow. This is nothing new -- anybody who compares timers (millis or micros) in Arduino is exposed to this.
millis() overflows every 50 days or so and for micros it is about 71 minutes. The problem results from comparing two unsigned 32-bit values, like this:
if((*timerFunctionPtr)() >= timeOut)
Say that millis() returns 0xFFFFFFF0 when you call the start function with a delay of 32 (0x20). The value stored in the timeOut variable is (0xFFFFFFF0 + 0x00000020) = 0x00000010 because the 32-bit timer value has overflowed.
Now, say you test for timeout 8 timer ticks later by calling elapsed(). At this time, millis() will return 0xFFFFFFF8, and elapsed() will effectively perform this test:
if ( 0xFFFFFFF8 > 0x00000010 )
which will of course be true and you will think the time has elapsed.
This only happens every 71 minutes (micros) or 50 days (millis) -- and will create extremely hard to find bugs. Imagine you have a system that only crashes once every 71 minutes or 50 days an you have to figure out where the problem is! These are the kind of bugs that make you look for another line of work.
Here's a proposed fix -- a new function to compare two timer values. There are other ways to do this but this is perhaps less code space than some.
It compares two 32-bit timer values, and returns one of three values:
-1 if a is earlier than b
0 if and and be are equal
+1 if a is later than b
The statement in elapsed() would then look like this:
if( TimerCompare((*timerFunctionPtr)(), timeOut) > 0 )
Here's the proposed TimerCompare() function:
`
int8_t TimerCompare(uint32_t a, uint32_t b)
{
int8_t c;
__asm__ __volatile__ (
"; form (a-b) \n\t"
"eor %0,%0 \n\t"
"sub %A1,%A2 \n\t"
"sbc %B1,%B2 \n\t"
"sbc %C1,%C2 \n\t"
"sbc %D1,%D2 \n\t"
"breq .+8 \n\t"
"brmi .+4 \n\t"
"ldi %0,1 \n\t"
"rjmp .+2 \n\t"
"ldi %0,0xff \n\t"
: "=&r" (c)
: "r" (a), "r" (b)
);
return c;
}
`
Simple fix:
--- avdweb_VirtualDelay.cpp.orig 2017-10-22 16:07:11.488732486 +0200
+++ avdweb_VirtualDelay.cpp 2017-10-22 16:09:30.880156256 +0200
@@ -37,7 +37,7 @@
bool VirtualDelay::elapsed()
{ bool pulse = 0;
if(running)
- { if((*timerFunctionPtr)() >= timeOut)
+ { if((signed long)((*timerFunctionPtr)() - timeOut) >= 0)
{ running = 0;
pulse = 1; // return 1 just one time
}
C.f. How can I handle the millis() rollover?, first answer, section “What if I really need to compare timestamps?”
Hi Edgar,
You helped me a lot.
I changed the library and tested to whole day. See the Test_rollover folder.
It seems to work now for delay values from 0 to 2^31-1, including timer rollover.
But, now there is a strange problem: It works only when the 2 library files (cpp, h) are in the project folder "Test_rollover". When in the library folder it doesn't work properly when the setMillis() is used.
I have never seen such an issue.
It works only when the 2 library files (cpp, h) are in the project folder
This is not a problem with the code. It looks more like a problem with the Arduino IDE.
I can only guess, but maybe it's still using cached object files from a previous build.