millis() and Timer0 interrupt
Arduino uses Timer0 to generate an interrupt every 16384 cpu clock cycles. During this interrupt,
timer0_millis
and timer0_fract
are incremented by amounts derived from F_CPU,
corresponding to the time elapsed between interrupts.
In the normal Arduino build, timer0_millis
is 32 bits. We extend it to 64 bits wide,
so that it will not quickly overflow, and define the time()
function to merely return
timer0_millis / 1000
. Likewise, the set_system_time()
function just
multiplies the argument by 1000, and writes that to timer0_millis
.
To provide clock discipline, discipline_clock()
transforms its argument into an approximate
parts-per-million value suitable for the cpu frequency, F_CPU. Every 1024 Timer0 interrupts, this ppm value
is added to (or subtracted from) the microsecond counter timer0_fract
... about once per second on a 16 Mhz board.
The original Arduino code defines timer0_fract
as an unsigned char, and scales it by a factor of 8,
losing the 3 lowest bits. For our discipline to work at all, timer0_fract
must be able to go
negative. And the loss of those bits means a loss precision of about 8 PPM. So we change timer0_fract
to be a signed 16 bit int, and do away with the scaling.
This scheme imposes minimal extra load on the millis() ISR, while providing for a fairly accurate
time-keeping system, even on boards which use a ceramic resonator instead of a crystal.
At the same time, it does not affect the millis()
or micros()
functions,
which continue to return the same results as before.