Introduction to Arduino timer interrupts

I’ve been working on a project that involves driving a 4-digit 7-segment LED display. I could do it with a separate chip, but I want to do it with the Arduino. Each digit has a line to each segment, and a separate common connector. To drive all 7 segments would take 7 x 4 = 28 digital output lines, which is more than the Arduino has available.

The classical solution for this problem is multiplexing. We connect the 7 segment lines for all of the digits together, and then connect them to the Arduino output pin through a dropping resister. Which leads to a bit of an issue – if we turn on the segments to show a “3”, it will show on all of the digits.

So… We connect each common line to an output line of the arduino, and then work our ways through the digits. If we do it fast enough, all of the digits appear to be lit. The one downside – other than the extra complexity of the code – is that since each digit is only on for 1/4 of the time, it will only be 1/4 as bright. We work around this by increasing the amount of current we send through the LEDs. *Generally*, if you only light up an led for 1/4 of the time, you can use 4 times the current, but a) you should check the datasheet for the led you’re using to be sure, and b) you should only exceed the normal current when you’re sure everything is working, because if your code stops running with one digit on too bright, you will quickly fry the display.

Generally, you need the the refresh rate of the display to be greater than 100 Hz or the display will flicker. I’d aim for something like 250Hz.

The code that we would write looks pretty much like this:

void loop()
{
    for (int digit = 0; digit < 4; digit++)
    {
        /// turn all digits off
        /// set the values for the current digit to the 7 segments
        /// turn on the current digit
        /// delay a bit of time (say, 3 milliseconds)
    }
}

That will work fine if that is all that we want to do, but it has some disadvantages:

  1. If I need to do other things (such as read from the serial port), any code I write has to fit in the loop where the “delay” section goes. This will make the code more complex.
  2. If my code spends a variable amount of time, the digits will update at a variable rate, and they could flicker if things get too slow.

What I really need is a way to separate the functions – a way for the display to update at a constant rate and not get in the way of everything else that I want to do.

This is a perfect place to use a timer interrupt.

An interrupt is something that interrupts the code that is running to perform another task. Timer interrupts happen periodically, and are perfectly suited to running tasks repeatedly at a set interval. A timer is just a configurable counter that counts the whole time the microcontroller is running.  We make use of it by enabling a timer interrupt, which can be configured to generate an interrupt in the following conditions:

  1. When the counter overflows. Timer 0 and timer 2 are 8-bit timers, which means they overflow after the timer counts to 255. Timer 1 is a 16-bit timer, and it overflows after the timer counts to 65535.
  2. When the timer equals a specific value. This works only on the 8-bit timers, and each of them have two values that can generate the interrupt (named “A” and “B”).

Getting the proper interval

The clock on the arduino runs at 16 MHz, or 16,000,000 counts per second (note that running the AVR at 16 MHz requires an external crystal, as on the arduino board. AVR processes can run without a crystal, but they are limited to 8 MHz).  I want my update to happen at 1000 Hz (4 * 250Hz per digit). That means we need to have a timer that divides the clock rate by 16,000.

We could do this by using the timer overflow function of timer1. It overflows when it his 65,536, and I need it to do it every 16,000 counts, and I can get this to happen by setting the clock value to (65536-16000 = 49536). 16,000 counts later it will overflow, and then I can reset the counter to that value in the interrupt handler. 

In this case, I want to save timer 1 in case I need it for something else, so I’ll be using timer 2. But timer 2 is only an 8-bit timer, and I need to count to 16,000. What I need is a way to slow down the count…

Enter the prescaler…

The prescaler is a programmable divider that sits between the clock and the timer. For timer2 (it is different for different timers), it can be set to the following values, which give us the following counts to get 1,000 Hz:

Divisor Resulting rate Count to get 1000 Hz
1 16,000,000 Hz 16,000
8 2,000,000 Hz 2,000
32 1,000,000 Hz 1,000
64 250,000 Hz 250
128 125,000 Hz 125
256 31,250 Hz 31
1024 3906 Hz 39

We should pick the smallest divisor that lets us get a count that is less than 255, so we’ll use a divisor of 64.

For timer 2, we can use either method of generating the interrupt. We can set the counter to 6, so it overflows and generates an interrupt 250 counts later, or we can set “compare A” to 250, so it will generate an interrupt when the count gets to 250. Either will work.

Configuring the timer registers

To figure this out, you need to refer to table 17-9 in the datasheet for the Atmel ATMega328, the microcontroller used in the arduino. When we see that, we can figure out that we need to set the TCCR2B register to 0x04.

TCCR2B=0x04;

We will need to configuration the timer so that it will generate an interrupt when it overflows. A look at section 17.11.6 shows that we do this by setting bit 0 (value=1) in the TIMSK2 register.

TIMSK2=0x01;

And then finally, we need to set the initial count of the timer to 6 (though 250 is close enough to 256 that is really wouldn’t matter if we didn’t do this.

 

Here’s our initial sketch:

#include <pins_arduino.h>

void setup()
{
  Serial.begin(9600); 
 
  initTimer2();
}

void initTimer2()
{
  noInterrupts();
 
  ASSR=0x00;
  TCCR2A=0x00;
  TCCR2B=0x04;
  TCNT2=0x06;
  OCR2A=0x20;
  OCR2B=0x00;
  TIMSK2=0x01; 

  interrupts();
}

int count = 0;

ISR(TIMER2_OVF_vect)
{
  TCNT2=0x06;
 
  count++;
 
  if (count % 1000 == 0)
  {
    Serial.println("a");
    count = 0;
  }
}

void loop()
{
}

If this was working correctly, every second it will send an “a” out the serial port. We can check this by choosing Tools->Serial Monitor from the Arduino IDE. And it did.

So, now we can develop the rest of the timer code, and use the main() loop to handle the rest of the functions.


So, what do you think ?