Saturday, May 07, 2011

Timer2 and Overflow Interrupt: Let's Get Cooking

Anybody can open up the example code and make an LED blink, but do you know how to do this with timers and interrupts? It's important to know your microprocessor fundamentals or you're going to be spending a lot of time on the forums and wading through Google searches.

If you read the previous blog post you might remember the problem I mentioned when using the delay function. Using the function locks up the processor and prevents any other code from running until it is done waiting. One simple way to get around using the delay function is to use timers instead.

There are a six available timers in the Arduino Mega. The tricky part is that they are already tied to specific peripherals and functions. Using a timer for an interrupt will interfere with using the pins it's tied to for PWM. The Arduino website says the Mega2560 has 14 PWM pins available. But, if I read the schematic correctly, it appears there are possibly three more pins PWM ready: Pins 44, 45, and 46. I'll have to test this when I start messing with PWM.

Looking at the Arduino schematic, the Timers are tied to the following Pins:
Timer0:  Pins 4 and 13
Timer1:  Pins 11 and 12
Timer2:  Pins 9 and 10
Timer3:  Pins 2, 3, and 5
Timer4:  Pins 6, 7, and 8
Timer5:  Pins 44, 45, and 46

When using the timers you need to check which pins they are connected to so that you know what functionality you are losing in regards to PWM.

The following program can be used to replace the delay(1000) from the earlier project. By configuring the Timer2 Overflow Interrupt to fire every 1ms, we can increment a counter in the Interrupt Vector and wait until 1000 interrupts have occurred to toggle the LED.


/*=======================================================================================
                                         Noah Stahl
                                          5/7/2011
                                       Arduino Mega 2560

This program will blink an LED on Port 53 once a second using a Timer2 Overflow Interrupt. The timer overflow interrupt fires every 1ms by setting the prescaler to 128 and preloading Timer2's counter with 130. Timer2 is 8-bits so the max number it can count to is 255, this leaves 125 cycles left to count. The Arduino has a clock of 16MHz so:
(1/16E6) * 125 * 128 = 1ms
The interrupt flag will fire every 1ms. Once the interupt fires 1000 times (i.e. one second) the LED on pin 53 will toggle.
=======================================================================================*/
#include <avr/interrupt.h>
#include <avr/io.h>

unsigned int toggle = 0;  //used to keep the state of the LED
unsigned int count = 0;   //used to keep count of how many interrupts were fired

//Timer2 Overflow Interrupt Vector, called every 1ms
ISR(TIMER2_OVF_vect) {
  count++;               //Increments the interrupt counter
  if(count > 999){
    toggle = !toggle;    //toggles the LED state
    count = 0;           //Resets the interrupt counter
  }
  digitalWrite(53,toggle);
  TCNT2 = 130;           //Reset Timer to 130 out of 255
  TIFR2 = 0x00;          //Timer2 INT Flag Reg: Clear Timer Overflow Flag
};

void setup() {
  pinMode(53,OUTPUT);

  //Setup Timer2 to fire every 1ms
  TCCR2B = 0x00;        //Disbale Timer2 while we set it up
  TCNT2  = 130;         //Reset Timer Count to 130 out of 255
  TIFR2  = 0x00;        //Timer2 INT Flag Reg: Clear Timer Overflow Flag
  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  TCCR2A = 0x00;        //Timer2 Control Reg A: Wave Gen Mode normal
  TCCR2B = 0x05;        //Timer2 Control Reg B: Timer Prescaler set to 128
}

void loop() {
}


A few important things to note about the code:
  • Disable the Timer while you configure your interrupt and timer by setting TCNT2 = 0x00.
  • Timer2 is 8-bits wide, so it can count up to 255. By preloading the counter with 130, TCNT2 = 130, this leaves 125 cycles left to count.
  • (125 cycles) * (128 prescaler) / (16MHz clock speed) = 1ms
  • Clear the Timer2 INT Flag by setting TIFR2 = 0x00.
  • Enable the Timer2 Overflow Interrupt by setting TIMSK2 = 0x01.
  • When your timer and interrupt are configured, you can then set the prescaler which will restart the timer. In this example the prescaler is set to 128 by setting TCCR2B = 0x05. The timer will count 128 clock cycles before it increments its counter.
  • Once inside the Interrupt vector, be sure to reset your timer count (TCNT2 = 130) and clear the interrupt flag (TIFR2 = 0x00) so that you can reuse the timer and interrupt function.
You can use this basic structure as a substitute for the delay() function in any project that has time sensitive signals. By changing the prescaler value and the preloaded count you can set the period at which the interrupt fires to suit your project's needs. Also, if you wanted a larger interrupt period, you can experiment with Timers 1,3,4, and 5 which have a 16-bit width and can count up to 0xFFFF, 65535 decimal. Just be sure to note which pins they are tied to.

3 comments:

  1. Hi Noah,

    it seems you truly master the management of timers. I realized two projects that work great independently but I can not run simultaneously and I guess it is due to a timer conflict. Unfortunately I'm really lost with it. If you have time maybe you can have a look ? The libraries that seem to be in conflicts are VirtualWire.h and DigitShield.h. The code here is compiling without error but nothing works. Hope you can help...

    ReplyDelete
  2. Good explaination, this is a modest example using pin 13:
    < BEGINCODE >
    unsigned int toggle = 0; //used to keep the state of the LED
    unsigned int count = 0; //used to keep count of how many interrupts were fired
    byte ledpin = 13; //for testing - onboard pin
    unsigned int blinkms = 0; //duration of blink

    ISR(TIMER2_OVF_vect) //Timer2 Overflow Interrupt Vector, called every blinkms
    {
    count++; //Increments the interrupt counter
    if(count > (blinkms - 1))
    {
    toggle = !toggle; //toggles the LED state
    count = 0; //Resets the interrupt counter
    }
    digitalWrite(ledpin,toggle);
    TCNT2 = 130; //Reset Timer to 130 out of 255 - 130 1 sec - 192.5 0.5 sec
    TIFR2 = 0x00; //Timer2 INT Flag Reg: Clear Timer Overflow Flag
    }

    void setup()
    {
    Serial.begin(57600);
    TIMSK2 = 0x00; //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
    TCCR2A = 0x00; //Timer2 Control Reg A: Normal port operation, Wave Gen Mode normal
    }

    void loop()
    {
    if(Serial.available())
    {
    byte c = Serial.read();
    if (c==65) //A
    {
    Serial.println("Blink 500 ms");
    blinkled(13,500);
    }
    if (c==66) //B
    {
    Serial.println("Blink 1 sec.");
    blinkled(13,1000);
    }
    if (c==67) //C
    {
    Serial.println("Disabled");
    TIMSK2 = 0x00; //Timer2 INT Reg: Timer2 Overflow Interrupt Disable
    digitalWrite(ledpin,LOW);
    }
    if (c==68) //D
    {
    Serial.println("Enabled");
    TIMSK2 = 0x00;
    digitalWrite(ledpin,HIGH);
    }
    if (c==69) //E
    {
    for (unsigned int i = 0; i<1000; i++) Serial.println("TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTEST");
    }
    }
    }

    void blinkled(byte setpin,unsigned int microseconds)
    {
    pinMode(ledpin,OUTPUT);
    ledpin = setpin;
    blinkms = microseconds;
    TIMSK2 = 0x01; //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
    }

    To blink use the command: blinkled(pin,duration);

    I would like to test 16bit timers...

    p.s.: the captcha doesn't works in google chrome, works in IE

    ReplyDelete