In the context of AVR micro-controllers, and the  Atmega family in particular, Timers and Timer interrupts is a topic that is often puzzles makers.


Although the principle behind Timers and Interrupts is straight forward (as I will explain next), their use in an Arduino sketch, and the differences between different Atmega models seems to be confusing to many of us.


In this article, I will try to explain and clear up the confusion once and for all!


First of all, what is a Timer?


The Timer


A Timer is simply a counter. You can set it to a valid value and enable it. The counter will start counting up, and when it reaches the maximum value it can hold, it will raise a flag. In “real life”, the equivalent of the Atmega timer is… the timer.



In Atmega micro-controllers, and in processors in general, a counter is implemented as an 8 or 16 bit register. In an 8 bit register, the programmer will set a valid value between 0 at 255 (total of 256 values), and enable it. Enabling the timer means to start it.


The processor keeps track of time by using its system clock. This clock can “tick” in rates that are measured in Megahertz (MHz) or Gigahertz (GHz). The Atmega328p typically uses a system clock that ticks at 16 million timers per second. Every time the clock ticks, the processors will increment the value of the timer register by 1. When it reaches its maximum, it will overflow and “raise” a flag, which is an event that we can use programmatically to detect that the set time has elapsed.


Let’s say that you set the Atmega 328p 8-bit timer register to its minimum value, “00000000”, or decimal 0, and then enable it. It will take 256*1/16000000 = 0.000016 sec or 16μsec for the timer to reach “11111111” (decimal 255), and it’s flag to rise.


Prescaling

In human terms, this time is not noticeable, so this kind of timer does not seem to be very useful. To get the timer to count at human-relevant time scales, there is a feature called the “prescaler”. The prescaler allows us to “magnify” time. Instead of incrementing the register by one unit at each tick of the system clock, we can increment every, say, 256 ticks. If the prescaler was set to 256, then a timer with value “00000000” in its register would require 256 * 16μsec = 4096μsec = 4.096msec.


The Atmega’s Timers can be set to a prescaler of 1 (no prescaling), 8, 64, 256, and 1024.


Detecting timer events

Once you set a Timer, how do you know when the set time has elapsed?


The Atmega offers three ways to do this.


First, you can constantly check the state of the Timer’s status flag. This is not very practical because it ties up much of the controller’s processing capacity, and should probably be avoided.


Second, you can setup an Interrupt Service Routine, which is a function in your program with a specific name that depends on the Timer you are working with. When the timer overflows, the program will automatically call this routine and execute the code in it.


Third, you can configure an output pin to change its state when the Timer overflows.


What Timers are available on Atmegas?

The ATmega168 and ATmega328 have three timers:

  • Timer0: 8-bit (used by the Arduino delay() and millis() functions),
  • Timer1: 16-bit (used by the Arduino servo library),
  • and Timer2: 8-bit (used by the Arduino tone())

The  ATmega1280 and ATmega2560 have three additional timer:

  • Timer3: 16-bit,
  • Timer4: 16-bit,
  • Timer5: 16-bit

Using the Atmega timers

Arduino developers have created several libraries that simplify the use of the Atmega timers.


Three popular libraries are Timer, Timer1, msTimer2, and Timer3 (scroll down to find Timer3). There’s more, of course.


Looking at each libraries source code, you can see that the Timer library is essentially a wrapper for the millis() function, so its using Timer0. Timer1 is a wrapper for Timer1 (ahem…) and provides an easy way to setup an interrupt request service function with a name of your choosing. Timer2 provides an interface for Timer2, and Timer3 is designed for the Arduino Mega.


Of course, there is nothing stopping you from programming these timers directly, instead of using the libraries. Even though these libraries are easy on resources, there is a mysterious satisfaction when interacting with the hardware at a low level.


Have a look at the Atmega datasheet on timers.


In pages 134 and 136 you will find these register descriptions:

and  TCCR1B register:

As an example, these two registers control the operation of Timer 1 (hence the 1 in the names of the registers, TCCR1A and TCCR1B).


In TCCR1B, the last three bits, CS12, CS11, and CS10, control the timer prescaler, and hence the the speed that the counter counts up. Have a look at the datasheet in page 137 for the prescaler settings:

Notice that if you like, you can connect an external clock to pin T1 (that is pin 11 on the Atmega328P) instead of using the system clock.

The following code will setup Timer1, enable it, and define the service interrupt routine that will be called when the timer overflows (credit):

// avr-libc library includes
#include <avr/io.h>
#include <avr/interrupt.h>
 
#define LEDPIN 13
 
void setup()
{
    pinMode(LEDPIN, OUTPUT);
 
    // initialize Timer1
    cli();             // disable global interrupts
    TCCR1A = 0;        // set entire TCCR1A register to 0
    TCCR1B = 0;
 
    // enable Timer1 overflow interrupt:
    TIMSK1 = (1 << TOIE1);
    // Set CS10 bit so timer runs at clock speed:
    TCCR1B |= (1 << CS10);
    // enable global interrupts:
    sei();
}
ISR(TIMER1_OVF_vect)
{
    digitalWrite(LEDPIN, !digitalRead(LEDPIN));
}


Things to note:

  • We first clear the Timer1 registers by setting them to 0 (binary “00000000”)
  • Then, we enable the timer’s overflow interrupt by left shifting (“<<“) the constant “TOIE1” into the TIMSK1 register.
  • Next, we set the timer to run at system clock speed, i.e. no pre-scaling, by using the compound bitwise operator on the result of the left shift operation on “11111111” and the “CS10” constant, which is a 1. So, whatever happens to be in the TCCR1B register, we end up with a “1” in the CS10 bit.
  • All this happens inside cli() and sei() so that any interrupts are ignored during the timer setup.
  • Every time the Timer overflows, the interrupt service routine is called, which results in to toggling the state of an LED connected to pin 13.


Another way to use the timer is to set the OCR register, and have the timer reset (or trigger the interrupt service routine) when the timer register is equal (matches) the OCR register. The program that follows sets the OCR and the prescaler for Timer1:

// Arduino timer CTC interrupt example
// www.engblaze.com
// avr-libc library includes
#include <avr/io.h>
#include <avr/interrupt.h>
  
#define LEDPIN 2
  
void setup()
{
    pinMode(LEDPIN, OUTPUT);
    // initialize Timer1
    cli();          // disable global interrupts
    TCCR1A = 0;     // set entire TCCR1A register to 0
    TCCR1B = 0;     // same for TCCR1B
  
    // set compare match register to desired timer count:
    OCR1A = 15624;
    // turn on CTC mode:
    TCCR1B |= (1 << WGM12);
    // Set CS10 and CS12 bits for 1024 prescaler:
    TCCR1B |= (1 << CS10);
    TCCR1B |= (1 << CS12);
    // enable timer compare interrupt:
    TIMSK1 |= (1 << OCIE1A);
    // enable global interrupts:
    sei();
}
  
void loop()
{
    // do some crazy stuff while my LED keeps blinking
}
  
ISR(TIMER1_COMPA_vect)
{
    digitalWrite(LEDPIN, !digitalRead(LEDPIN));
}


Differences between this and the first program:

  • In “OCR1A = 15624” we set the value to be matched by the timer in the OCR register for Timer1.
  • In “TCCR1B |= (1 << WGM12)” we enable CTC mode (Clear timer on compare match”). When the value in the timer register matches the value in the TCCR register, it (the timer) will generate a timer event.
  • We set the prescaler to 1024 by setting bits CS10 and CS12.
  • Finally, we enable the timer compare interrupt, since this is the event that we want to trigger the call to the interrupt service routine.


You can see that using the timers at a low level is fairly challenging at first. The available libraries do make life easier. But investing a bit of time to learn at least the basics will allow you a level of control that libraries do not provide.