Adjust time calculation after Timer0 frequency change
Fixing the timekeeping functions with your PWM settings is not so
simple. You should at least try to rewrite ISR(TIMER0_OVF_vect)
,
micros()
, and probably delay()
. Here is why:
First, there is a rounding problem. Time is kept using two global variables:
volatile unsigned long timer0_millis;
static unsigned char timer0_fract;
The first one is what millis()
returns. The second one keeps track of
how much time has passed since the last full millisecond, and it does so
in units of 8 µs. The two variables are incremented by
ISR(TIMER0_OVF_vect)
like this:
m += MILLIS_INC; // temporary copy of timer0_millis
f += FRACT_INC; // temporary copy of timer0_fract
On a normal Uno configuration, the ISR is called every 1024 µs.
Then MILLIS_INC
is 1 and FRACT_INC
is 3. With your timer
configuration, the ISR is called every 31.875 µs (510 cycles), then
MILLIS_INC
should be 0 and FRACT_INC
should be 3.984375. But since
we are dealing with integers, it will be rounded down to 3, and your
millis()
will tick about 25% too slow.
A simple fix would be to
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512))
in order for FRACT_INC
to be 4 and millis()
to be 0.4% too fast. Or
you could make timer0_fract
a 16-bit variable and have it count clock
cycles, just to avoid this error. Either option should fix millis()
,
but you still have a problem with micros()
.
micros()
works by reading both timer0_overflow_count
(incremented by
1 in the ISR) and the actual counter value. Since your counter is now
going alternatively up and down, it will be harder to compute a
microsecond count from these readings. Maybe you could take two
consecutive readings of the counter, just to know whether it is going up
or down...
And then there is delay()
, which relies on micros()
. If you fix
micros()
, delay()
should work fine. If not, You could rewrite
delay()
to use millis()
instead, which should be easy but you will
loose some accuracy.