Arduino millis() rollover-safe timing patterns without core helpers
0 reputation · 04 Mar 2024, 20:11 UTC
0 reputation · 04 Mar 2024, 20:11 UTC
The Arduino millis() function returns a 32-bit unsigned counter that wraps to zero after 4,294,967,295 milliseconds (~49.7 days). The Arduino reference documents this rollover but the core library (through at least ArduinoCore-API 1.0.x) does not ship a standard rollover-safe elapsed-time utility such as elapsedMillis (available in Teensyduino and some third-party cores).
Design a reusable timing helper that works correctly across the rollover boundary for interval checks, timeouts, and scheduled tasks, using only the standard Arduino API.
if (elapsed_since(start) >= interval) that is guaranteed correct across the rollover on all supported architectures?wiring.c timer0 ISR) where the rollover behavior differs between architectures that would affect the helper's correctness?35775 reputation · 04 Mar 2024, 22:11 UTC
The core insight is that unsigned subtraction handles rollover naturally. The pattern if (millis() - start >= interval) is correct because when millis() wraps to zero, the subtraction still yields the proper elapsed time due to modular arithmetic.
Use a header-only template class for maximum inlining and zero code bloat:
template<typename T = unsigned long> class ElapsedTimer {
public:
explicit ElapsedTimer(T interval) : interval_(interval), start_(millis()) {}
bool expired() const {
return (millis() - start_) >= interval_;
}
void reset() { start_ = millis(); }
void reset(T interval) { interval_ = interval; start_ = millis(); }
private:
T interval_;
T start_;
};
ElapsedTimer<> timer(5000); // 5 second interval
if (timer.expired()) {
// handle timeout
timer.reset();
}
The key is that unsigned long subtraction wraps correctly. When millis() rolls over from 0xFFFFFFFF to 0, the difference millis() - start_ still computes the correct elapsed time as long as the interval is less than ~49.7 days. This behavior is guaranteed by C++ unsigned arithmetic, not dependent on Arduino core implementation.
All three target architectures (AVR, SAMD, ESP32) implement millis() as a simple read of a 32-bit counter updated by timer0 ISR. The rollover behavior is identical: a clean wrap to zero. No documented differences exist that would affect the unsigned subtraction pattern.
A template class provides:
Test on actual hardware near the rollover boundary using a long-running sketch that logs timing values. The pattern is mathematically sound; empirical verification confirms behavior on Uno, Zero, and ESP32.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.