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.

11 thoughts on “Borrowing an Arduino Timer”

  1. which arduino tiny core did you use for this borrowing?
    micros() it is based on Timer1 or Timer 0?There are cores with different settings.

  2. declaring extern those arduino core variable not working,
    Working only if I include.
    #include
    Also if I have a call of micros() function in borrowed time,I get some problem with other timer.

    1. Sorry to hear you are having difficulties Miki. I didn’t have any problems with extern using Arduino 1.6.6 and the hilowtech attiny core. If you are using a different core then you will have to look at the code for millis() and micros() in that core. Which core are you using?

  3. I used the same core as you from damellis but extern not working.
    This is not a major problem as I used
    #include
    amd working like that.Btw I used arduino 1.6.5.
    I needed to use precision software serial code I made for 100 Kbaud(10us interrupt).
    The code is working fine with Timer1 but I needed to suspend micros() interrupt (Timer0) in background for 1600us(time to finish full serial frame).The problem is that in the same time I have polling micros() and is affecting my serial timing on Timer1 and idk why. If I suspend interrupt and don’t call micros()in this borrowed time(1600us)the code is working fine. But this is not how it is supposed to work.If you have some idea please let me know.

    1. #include “wiring.c” seems very wrong!
      I wrote the following sketch in Arduino 1.6.5 targeting Processer ATtiny85 and Board ATTiny (attiny by David A. Mellis version 1.0.1 as reported by Boards Manager) and it compiled fine:

      extern volatile unsigned long timer0_millis;
      extern volatile unsigned long timer0_overflow_count;
      void setup() {}
      void loop() {
      timer0_millis += 1;
      }

      If you look at the code for micros() in wiring.c you will see a call to cli(), which disables interrupts and then it re-enables interrupts by restoring the saved value of SREG. If your timer triggered an interrupt during that time window when global interrupts were disabled then the processing of that interrupt would be delayed by a number of clock cycles.

      You could work around this by setting a volatile (if setting in an interrupt) state variable at the beginning of your serial read and checking the value of it in your main loop and avoid calling micro when in a serial read state.

  4. I was was using extern on wiring.c so for this reason was not working extern.The tutorial led to idea the change is there in wiring.c
    However I have error MILLIS_INC was not declared in this scope.

    void enable_micros(){
    //recovering lost time
    TCCR0A = oldTCCR0A;
    TCCR0B = oldTCCR0B;
    //TCNT0 = oldTCNT0;
    uint16_t d_time = 1600;
    unsigned int newCounter = oldTCNT0 + d_time / (64 / clockCyclesPerMicrosecond() );
    timer0_overflow_count += (newCounter>>8);
    TCNT0 = (byte)newCounter;
    //
    unsigned long m = timer0_millis;
    m += MILLIS_INC * (newCounter>>8);
    timer0_millis = m;
    //TIFR |=(1<<TOV0);
    TIMSK |= (1<<TOIE0);
    }
    The enable_micros() routine I added at the end of serial frame in intrerupt. You idea is good.I can add a flag at the end of serial frame and avoid calling micros till the serial frame end.

    1. Thanks for the clarification, I see the issue now. You can use extern to gain visibility of a non-static variable or function, but you will still not have visibility of the #defines. Thus you must redefine MICROSECONDS_PER_TIMER0_OVERFLOW and MILLIS_INC in your code as follows:
      #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
      #define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
      I will make this clearer in the blog post, thanks for the feedback.

  5. I assume there is standard a timer counter running some where in the processor, without me having to initialize it. How can I read the current value?

    1. There is no timer dedicated to time in these microcontrollers. For example the ATtiny25/45/85 have two 8 bit (0-255) timers which have to be configured and initialized. To keep track of time the firmware needs to store the time in a variable and use a timer to indicate when the value in that variable should be incremented. Arduino makes the number of microseconds since the microcontroller program started available in micros(). and the number of milliseconds available in millis().

Comments are closed.