Timer Interrupts

Don't call us, we'll call you

In part 1 of this series, we learned how to use millis() for timing.  But in order to make that work, we had to call millis() every time through the loop to see if it was time to do something.  It is kind of a waste to be calling millis() more than once a millisecond, only to find out that the time hasn't changed.  Wouldn't it be nice if we only had to check once per millisecond?

Timers and timer interrupts let us do exactly that.  We can set up a timer to interrupt us once per millisecond.  The timer will actually call us to let us know it is time to check the clock!

Arduino Timers

The Arduino Uno has 3 timers:  Timer0, Timer1 and Timer2.  Timer0 is already set up to generate a millisecond interrupt to update the millisecond counter reported by millis().  Since that is what we are looking for, we'll get Timer0 to generate an interrupt for us too!

Frequency and Counts

Timers are simple counters that count at some frequency derived from the 16MHz system clock.  You can configure the clock divisor to alter the frequency and various different counting modes.  You can also configure them to generate interrupts when the timer reaches a specific count.

Timer0 is an 8-bit that counts from 0 to 255 and generates an interrupt whenever it overflows. It uses a clock divisor of 64 by default to give us an interrupt rate of 976.5625 Hz (close enough to a 1KHz for our purposes).  We won't mess with the freqency of Timer0, because that would break millis()!   

Comparison Registers

Arduino timers have a number of configuration registers.  These can be read or written to using special symbols defined in the Arduino IDE.  For comprehensive description of all these registers and their functions, see the links in "For further reading" below.

We'll set up a up a comparison register for Timer 0 (this register is known as OCR0A) to generate another interrupt somewhere in the middle of that count.  On every tick, the timer counter is compared with the comparison register and when they are equal an interrupt will be generated.

The code below will generate a 'TIMER0_COMPA' interrupt whenever the counter value passes 0xAF.

  // Timer0 is already used for millis() - we'll just interrupt somewhere
  // in the middle and call the "Compare A" function below
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);

Then we'll define an interrupt handler for the timer interrupt vector known as "TIMER0_COMPA_vect".  In this interrupt handler we'll do all the stuff we used to do in the loop.

// Interrupt is called once a millisecond, 
SIGNAL(TIMER0_COMPA_vect) 
{
  unsigned long currentMillis = millis();
  sweeper1.Update(currentMillis);
  
  //if(digitalRead(2) == HIGH)
  {
     sweeper2.Update(currentMillis);
     led1.Update(currentMillis);
  }
  
  led2.Update(currentMillis);
  led3.Update(currentMillis);
}

Which leaves us with a completely empty loop.

void loop()
{
}

You can do anything you want in your loop now.  You can even be decadent and use a delay()!  The flashers and sweepers won't care.  They will still get called once per millisecond regardless!

For further reading:

This is just a simple example of what timers can do.  For more in-depth information on the different types of timers and ways to configure them, check out the "Libraries and Links" page.

The Source Code:

Here's the whole code, including the flashers and sweepers:

#include <Servo.h> 

class Flasher
{
	// Class Member Variables
	// These are initialized at startup
	int ledPin;      // the number of the LED pin
	long OnTime;     // milliseconds of on-time
	long OffTime;    // milliseconds of off-time

	// These maintain the current state
	int ledState;             		// ledState used to set the LED
	unsigned long previousMillis;  	// will store last time LED was updated

  // Constructor - creates a Flasher 
  // and initializes the member variables and state
  public:
  Flasher(int pin, long on, long off)
  {
	ledPin = pin;
	pinMode(ledPin, OUTPUT);     
	  
	OnTime = on;
	OffTime = off;
	
	ledState = LOW; 
	previousMillis = 0;
  }

  void Update(unsigned long currentMillis)
  {
    if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
    {
    	ledState = LOW;  // Turn it off
      previousMillis = currentMillis;  // Remember the time
      digitalWrite(ledPin, ledState);  // Update the actual LED
    }
    else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
    {
      ledState = HIGH;  // turn it on
      previousMillis = currentMillis;   // Remember the time
      digitalWrite(ledPin, ledState);	  // Update the actual LED
    }
  }
};

class Sweeper
{
  Servo servo;              // the servo
  int pos;              // current servo position 
  int increment;        // increment to move for each interval
  int  updateInterval;      // interval between updates
  unsigned long lastUpdate; // last update of position

public: 
  Sweeper(int interval)
  {
    updateInterval = interval;
    increment = 1;
  }
  
  void Attach(int pin)
  {
    servo.attach(pin);
  }
  
  void Detach()
  {
    servo.detach();
  }
  
  void Update(unsigned long currentMillis)
  {
    if((currentMillis - lastUpdate) > updateInterval)  // time to update
    {
      lastUpdate = millis();
      pos += increment;
      servo.write(pos);
      if ((pos >= 180) || (pos <= 0)) // end of sweep
      {
        // reverse direction
        increment = -increment;
      }
    }
  }
};
 
 
Flasher led1(11, 123, 400);
Flasher led2(12, 350, 350);
Flasher led3(13, 200, 222);

Sweeper sweeper1(25);
Sweeper sweeper2(35);
 
void setup() 
{ 
  sweeper1.Attach(9);
  sweeper2.Attach(10);
  
  // Timer0 is already used for millis() - we'll just interrupt somewhere
  // in the middle and call the "Compare A" function below
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
} 
 
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) 
{
  unsigned long currentMillis = millis();
  sweeper1.Update(currentMillis);
  
  if(digitalRead(2) == HIGH)
  {
     sweeper2.Update(currentMillis);
     led1.Update(currentMillis);
  }
  
  led2.Update(currentMillis);
  led3.Update(currentMillis);
} 

void loop()
{
}
Last updated on 2015-05-04 at 04.27.27 PM Published on 2014-12-01 at 01.47.57 PM