I'm not sure if this has been done before, but anyway I have a custom breadboard made up here and I wanna torture test it for a very long time -- maybe even longer than 50 days. So I've written a 64-Bit millis() function that won't overflow until the microcontroller has been powered on for half a billion years:
inline long long unsigned millis64(void) noexcept
{
static thread_local long unsigned timestamp[2u] = { 0u, 0u };
static_assert( sizeof(timestamp) == sizeof(long long unsigned) );
register long unsigned const tmp = millis();
#if BYTE_ORDER == BIG_ENDIAN
if ( tmp < timestamp[1u] ) ++timestamp[0u];
timestamp[1u] = tmp;
#else
if ( tmp < timestamp[0u] ) ++timestamp[1u];
timestamp[0u] = tmp;
#endif
return *static_cast<long long unsigned*>(static_cast<void*>(timestamp));
}
On my ES32 using Arduino IDE with all the default compiler options, this gets compiled to:
400d2c10 <_Z8millis64v>:
400d2c10: 004136 entry a1, 32
400d2c13: 15e1e5 call8 400e8a30 <millis>
400d2c16: e3ce70 rur.threadptr a12
400d2c19: f51e81 l32r a8, 400d0094 <_stext+0x74>
400d2c1c: 809c80 add a9, a12, a8
400d2c1f: 0129b2 l32i a11, a9, 4
400d2c22: 06bab7 bgeu a10, a11, 400d2c2c <_Z8millis64v+0x1c>
400d2c25: 0029b2 l32i a11, a9, 0
400d2c28: bb1b addi.n a11, a11, 1
400d2c2a: 09b9 s32i.n a11, a9, 0
400d2c2c: 88ca add.n a8, a8, a12
400d2c2e: 18a9 s32i.n a10, a8, 4
400d2c30: 0828 l32i.n a2, a8, 0
400d2c32: 0a3d mov.n a3, a10
400d2c34: f01d retw.n
400d2c36: 00 .byte 00
400d2c37: 00 .byte 00
So we're talking around about 15 more instructions than if we had just called the 32-Bit millis(). The CPU in the ESP32 runs at 240 MHz and therefore has a CPU cycle of 4 nanoseconds, and so 15 instructions should take less than a hundred nanoseconds.
Of course if this function gets expanded inline then you can take away two or three of those instructions (entry, retw.n, and also maybe the the final mov). Also on a single-core microcontroller, you can take out the rur.threadptr instruction.