Arduino R2R DAC: Timer Interrupts

Up to this point I’ve been handling all of the timing in my Arduino projects using the delay (or delayMicroseconds) function. This has worked pretty well but isn’t without it’s issues. Primarily there are two problems that need to be addressed. First, when using the delay function my timing doesn’t take into account the time it takes to process the rest of the code. And second when using delay my Arduino isn’t able to do anything else. If I wanted to, for instance, run two oscillators simultaneously at different speeds everything would break down.

The solution to these problems is to leverage timer interrupts. This essentially sets up a clock that calls a function at regular intervals. This means my timing will have much higher accuracy and I can have the Arduino do whatever I want in between updates.

Arduino Timers

The Arduino Uno has three timers appropriately named Timer0, Timer1 and Timer2. each of these runs off the 16MHz clock oscillator within the Arduino.

Each timer has a couple important parameters we can control. The first is something known as a prescaler. The prescaler allows us to slow down the clock speed from 16MHz to a more manageable speed. The prescaler values available are 1 (no change), 8 (sets timer to 16MHZ/8 = 2MHz), 64 (slows the timer to 250kHz), 256 (62.5kHz) and 1024 (15.625kHz). These prescaler are set by writing ones different combinations of three registers (CSx0, CSx1 and CSx2) as seen in the following grid.

Clock Selector Bits

Note: the x in these register names should be replaced with the timer number.

The second parameter that will impact our timing is the value in the output compare register (OCR). The timer will count up based on the clock frequency and prescaler. Each time it reaches the value in the OCR register it will reset and send an interrupt. On the Arduino Uno Timers 0 and 2 have an 8 bit counter meaning the OCR can be any value between 1 and 256. Timer 1 is a 16 bit timer meaning it can count as high as 65536.

Math Time

So how do we put this all together? Lets say we want our saw wave to run at 1kHz. The first thing we have to correct for is the steps in the saw itself. Right now we’ve got the saw wave set up to cycle through 16 voltage levels, so right away our 1kHz signal will require the timer to fire at 16Hz. No problem!

So to get the 16MHz clock input down to 16Hz we first need to apply a prescaler. Lets divide the 16MHz by a prescaler of 8 (16MHz/8 = 2MHz). Now to get that 2MHz down to 16kHz we need to set our OCR. if we divide the timer speed by the desired speed we’ll find the value we need (2MHz/16kHz = 125).

Lets Write It

Enough theory, let’s write some code! First things first, I define a function to initiate the timer and call it in the setup function.

void timer1_init();

void setup() {
  DDRB = B1111;      //Set Pins 8-11 as Inputs

  timer1_init();    //Initiate timer
}

For the timer function we want to start by disabling all interrupts and setting the following three registers to zero. This initiates the timer.

  noInterrupts();        

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

Next we set the OCR value.

  OCR1A = 125;                //Compare Value

We set the timer into CTC mode using the following command. CTC mode allows us to trigger our interrupts each time the counter hits the OCR value. This is immediately followed by a command setting the prescaler and a final command enabling the interrupt itself. Last we reenable the interrupts.

  TCCR1B |= (1 << WGM12);       // CTC mode
  TCCR1B |= (1<<CS11);         // 8 prescaler 
  TIMSK1 |= (1 << OCIE1A);    // enable timer compare interrupt

  interrupts();     

So that sets up the interrupt but how does it get called? Essentially what happens is each time the interrupt triggers it calls a specific function known as ISR. We can set up the ISR function as follows.

ISR(TIMER1_COMPA_vect){
  PORTB = v_level;         
  v_level = (v_level + 1) % 16;
}

And there we have it. We’ve done away with the evil delay function and replaced it with a timer interrupt. I’ll finish off with the complete code and I’ll see you all soon.

#define PIN_8 8
#define PIN_9 9
#define PIN_10 10
#define PIN_11 11

#define N 15

int DAC_bus[] = {PIN_8, PIN_9, PIN_10, PIN_11};

int v_level = 0;

void timer1_init();

void draw_saw();

void setup() {
  DDRB = B1111;      //Set Pins 8-11 as Inputs

  timer1_init();    //Initiate timer
}

void loop(){
  }

void timer1_init(){
  noInterrupts();        
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 125;                //Compare Value
  TCCR1B |= (1 << WGM12);      // CTC mode
  TCCR1B |= (1<<CS11);    // 8 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt

  interrupts();     
}

ISR(TIMER1_COMPA_vect){
  PORTB = v_level;         
  v_level = (v_level + 1) % 16;
}