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.

Tags:

Millis

Timers

Pwm