Borrowing an Arduino Timer

TimerIf you want to use a microcontroller timer for your own purposes and you are using Arduino you might find that you need to borrow a timer from the Arduino core and then give it back.

The ATtiny85 (and lower memory ATtiny45, ATtiny25 variants) only have two timers and one is used by Arduino to keep track of time. This post explains how you can borrow that timer from Arduino and give it back.

Microcontroller timer counters

Microcontrollers contain a fixed number of timer-counter modules which can be used for various tasks such as driving a PWM output on a pin, driving a Universal Serial Interface (USI), or calling an interrupt to keep track of time passing. The ATTiny85 has two such timers Timer/Counter0 and Timer/Counter1.

The ATtiny85 Timer/Count0 has two Timer Counter Control Registers called TCCR0A and TCCR0B. These registers control the clock source, the prescaler, the output compare units and the PWM waveform generator.

The counter value for Timer/Counter0 is stored in the Timer Counter Register TCNT0.

Timer counter interrupts

The ATtiny Arduino core is using Timer/Counter0 and the Timer/Counter0 Overflow interrupt, TIMER0_OVF, to keep track of time for millis() and micros(). You can verify this by looking at the wiring.c source code in your Arduino core.

Timer/Counter0 can trigger two other interrupts, Compare Match A and Compare Match B (TIMER0_COMPA and TIMER0_COMPB) which are still available to use for your purposes. These trigger when Timer/Counter0 hits the value specified in Output Compare Registers OCR0A or OCR0B.

You can avoid triggering the TIMER0_OVF interrupt, used by Arduino, from being inadvertently called by disabling the overflow interrupt vector.

TIMSK &= ~(1<<TOIE0);

And enabling it when you are finished.

TIMSK |= 1<<TOIE0;

Or by using a Compare Match and never letting Timer/Counter0 overflow.

When we borrow Timer/Count0 for a little while, say to do accurate timing for a serial or IR input, we will probably need to change the timer counter control registers TCCR0A and TCCR0B.

So we can save these values along with the current counter value, TCNT0, before we borrow Timer/Counter0:

// Save Timer/Counter0 values
oldTCCR0B = TCCR0B;
oldTCCR0A = TCCR0A;
oldTCNT0 = TCNT0;

And restore them when we are finished:

//Restore old Timer/Counter0 values
TCCR0A = oldTCCR0A;
TCCR0B = oldTCCR0B;
TCNT0 = oldTCNT0;

Lost Time

This simple approach is perfectly acceptable, but there will be a small amount of time lost. For example to receive a byte of serial at 9600 baud takes approximately one millisecond, so every time we borrowed Timer/Counter0 to receive a byte, Arduino millis() would lose a millisecond.

If you were using millis() for something like button debouncing, where you want to ensure a button stays down for 50 milliseconds before registering a click, then losing a millisecond here and there isn’t going to make much difference to your application.

However if you really want Arduino millis() and micros() to remain accurate then we will need to fix up Arduino’s internal record of time before returning control to Arduino.

Arduino millis and micros

Looking at wiring.c there are two variables used to record the passage of time that we can gain access to using extern.

extern volatile unsigned long timer0_millis;
extern volatile unsigned long timer0_overflow_count;

The first variable, timer0_millis is the number that millis() returns, it is incremented by MILLIS_INC every time Timer/Counter0 overflows.

MILLIS_INC is defined in wiring.c as follows, you will need to redefine these in your code.

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

The second variable, timer0_overflow_count, is used in combination with the Timer/Counter0 counter value, TCNT0, to calculate the value returned by micros(), which we can summarize as:

((timer0_overflow_count<<8) + TCNT0) * (64 / clockCyclesPerMicrosecond())

Here timer0_overflow_count*256 + TCNT0 gives us the recorded number of Timer/Counter0 increments.

The Arduino core sets the prescaler to 64 which causes Timer/Counter0 to increment once every 64 clock cycles, so this is multiplied by 64 to get the number of clock cycles and divided by clockCyclesPerMicrosecond() to get the number of microseconds.

Recovering lost time

To adjust the Arduino time we need to adjust Arduino’s record of the number of Timer/Counter increments represented by timer0_overflow_count*256 + TCNT0. We need to increment this by the number of microseconds borrowed, delta, multiplied by clockCyclesPerMicrosecond(), to get the number of clock cycles, divided by 64, to get the number of timer increments.

unsigned int newCounter = oldTCNT0 +
    delta / (64 / clockCyclesPerMicrosecond() );
timer0_overflow_count += (newCounter>>8);
TCNT0 = (byte)newCounter;

And for each time that Timer/Counter0 would have overflowed, we need to increment timer0_millis by MILLIS_INC.

unsigned long m = timer0_millis;
m += MILLIS_INC * (newCounter>>8);
timer0_millis = m;

This approach will allow you to fix up Ardunio time to the nearest Timer/Counter0 tick which is 8 microseconds for an ATTiny85 running at 8Mhz.