millis() overflow handling improvement

96 views
Skip to first unread message

Lode Van Dyck

unread,
Dec 21, 2023, 4:02:04 AM12/21/23
to Developers
Hi all, 

First time here, I hope I'm doing this the right way. 

First my issue is that I rarely see proper overflow handling when millis() is being used. That is not only the case in external libraries. As reference, I just checked the Ethernet library, it uses millis() 21 times. None of them have overflow handling. 

Just to be clear, this is not a post to criticize the developers. Overflow handling should be something easy that happens in the background so that developers do not have to worry about it every time they use a timer.

I thought about this for a while and I might have a solution that is pretty easy. It involves overloading the millis function with a second option like so: 

unsigned long millis(unsigned long start)
{
unsigned long now = millis();
return (now >= start) ? (now - start) : ((UINT32_MAX - start) + now);
}

I don't think this needs further explanation.

With kind regards,

Lode Van Dyck.

Michel Firholz

unread,
Dec 21, 2023, 4:07:14 AM12/21/23
to devel...@arduino.cc

Overflow handling on millis() is superfetatory.
There is absolutely no problem with the comparison math during an overflow event.

--
You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.
To view this discussion on the web visit https://groups.google.com/a/arduino.cc/d/msgid/developers/19824bb5-4183-47e1-8f8c-9b76e7108c5an%40arduino.cc.

vbextreme vbextreme

unread,
Dec 21, 2023, 4:11:45 AM12/21/23
to devel...@arduino.cc
overflow/underflow on unsigned is defined in standard C/C++

unsigned start = millis();
if( millis()-start > 1000 ) ....

Works Always, not need nothing.


You Need check overflow if you change unsigned to signed 

int start= millis();
if( millis()-start > 1000) //undefined behavior

--

Lode Van Dyck

unread,
Dec 21, 2023, 5:04:11 AM12/21/23
to Developers, vbextreme like C
Ok, 

I learned something new, thanks for clarifying. 

With kind regards,

Op donderdag 21 december 2023 om 10:11:45 UTC+1 schreef vbextreme like C:

TD-er (Gijs Noorlander)

unread,
Dec 21, 2023, 5:20:02 AM12/21/23
to Developers, lodev...@gmail.com, vbextreme like C
In my project (ESPEasy) I have defined several functions to deal with this.
Just to make sure the code is more readable and also whenever you see those functions be used, you don't have to think about whether you need to check them.


// Return the time difference as a signed value, taking into account the timers may overflow.
// Returned timediff is between -24.9 days and +24.9 days.
// Returned value is positive when "next" is after "prev"
inline int32_t timeDiff(const unsigned long prev, const unsigned long next) {
  return ((int32_t) (next - prev));
}

inline int64_t timeDiff64(uint64_t prev, uint64_t next) {
  return ((int64_t) (next - prev));
}

// Compute the number of milliSeconds passed since timestamp given.
// N.B. value can be negative if the timestamp has not yet been reached.
inline long timePassedSince(const uint32_t& timestamp) {
  return timeDiff(timestamp, millis());
}

inline int64_t usecPassedSince(volatile uint64_t& timestamp) {
  return timeDiff64(timestamp, getMicros64());
}

inline int64_t usecPassedSince(const uint64_t& timestamp) {
  return timeDiff64(timestamp, getMicros64());
}

inline int64_t usecPassedSince(uint64_t& timestamp) { //-V669
  return timeDiff64(timestamp, getMicros64());
}

// Check if a certain timeout has been reached.
inline bool timeOutReached(unsigned long timer) {
  return timePassedSince(timer) >= 0;
}

inline bool usecTimeOutReached(const uint64_t& timer) {
  return usecPassedSince(timer) >= 0;
}


So these are just convenience functions and since they are inline, they don't take any more time than just doing the calculations as you wrote them.

Are they really needed? Nope
Do they make reviewing code easier? Absolutely!

Regards,
Gijs (TD-er)

Billy

unread,
Dec 21, 2023, 1:50:06 PM12/21/23
to devel...@arduino.cc
On Thu, Dec 21, 2023 at 4:11 AM vbextreme vbextreme <vbextreme...@gmail.com> wrote:
overflow/underflow on unsigned is defined in standard C/C++

unsigned start = millis();
if( millis()-start > 1000 ) ....

Works Always, not need nothing.

Yes, except it should be "unsigned long".

unsigned long start = millis();

Andrew Kroll

unread,
Dec 22, 2023, 4:07:26 AM12/22/23
to Developers, Billy Donahue
I just avoid the whole problem entirely by using a count down, which decrements instead of increments, but stops at zero.
Yes this means one variable per counter (if you use more than one), BUT checking is super fast and easy, since it is only expired when it is a zero. :-)

Michel Firholz

unread,
Dec 22, 2023, 7:20:16 AM12/22/23
to devel...@arduino.cc, Billy Donahue

There is no problem to be avoided
millis() works across the overflow.

You even can wrap all that stuff in a macro:

//*** Convenience macro for Timing ***

#define runEvery(t) for (static uint16_t _lasttime; (uint16_t)((uint16_t)millis() - _lasttime) >= (t); _lasttime += (t)) 

Then in you code, you just write:
runEvery(125){

<code that should run every 125ms>

}

And it does the magic.

--

You received this message because you are subscribed to the Google Groups "Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to developers+...@arduino.cc.

Andrew Kroll

unread,
Dec 22, 2023, 11:32:51 PM12/22/23
to Developers, Michel Firholz, Billy Donahue
Not saying there is a "problem" with the method, instead it makes the code checking it smaller and faster, with only a speed gain.
millis() still does an addition operation (in the ISR, increment v.s. decrement) , which is the same amount of cycles with addition, however includes the following benefits:

1: once we hit zero, we can stop the operation, it's not needed to free run anymore. we could even trigger an event.
2: there's an overhead of calling millis(), unless it is a macro.
3: eliminates the extra maths in the if statement (another gain)
4: there is no overflow concern (valid or not) and is mentally easier to understand regardless of coder experience
5: if(!variable) is a single instruction unless you are doing a countdown from a very large variable. -- it is the largest gain if the count is short.
6: if we don't exceed 255 counts (the example is 125ms) we are atomic too.

The only loss is that you use another timer source. This could be the timer in the MCU, or, in the case that I do, the external hardware triggers an IRQ every ms, which is connected anyway, and a total win.



Reply all
Reply to author
Forward
0 new messages