Timer Interrupts

 

In this section we will write the SOS program using timer interrupts instead of delay()

We did some work with hardware interrupts back in the LCD tutorial, now we are going to work with timer interrupts. Think of a timer interrupt as, “call this function once a second”. This is done by attaching an interrupt function to one of the Arduino’s internal timers. I was able to find a library that supported the use of timer interrupts for the Arduinos that use the Atmega 128/328 here . But not one for the Arduino Due, rather there was a very useful forum post on using timer interrupts for the Due. Some of this code will look different from the code that you usually see when using one of the libraries provided with the Arduino IDE. Most of it comes directly from the Atmel ASF ( Atmel Software Framework ), which ultimately is used to write the Arduino libraries. You don’t need to do anything extra to your build environment to use these, the only problem is that they are not well documented, at least not for a beginner. You can find the reference docs at http://asf.atmel.com/docs/latest/api.html.

Unfortunately this is not the easiest documentation to navigate. It can be hard to find what you want if don’t know a particular acronym, or specific term to describe what you want. The Arduino Due uses the SAM3X8E processor. The documentation is not organized by processor type, (though you can sort by device type) but by the type of functionality you are interested in. For example, if you wanted to read about timers, you would navigate to one of several entries, then look for your processor. For this example there is a Timer entry, followed by RTC ( real time clock) with a “sam3” in the supported device column, when you expand that entry you see sam3x, which are the docs that refer to the Sam3x8E. I say all this just to point out that reference material is usually written for a subset of devices, not a single device. You may also find the datasheet for the SAM3X8E to be helpful.

To learn about timer interrupts lets build the SOS program, but using timer interrupts instead of calls to delay(). There are 9 timer channels that we can use, some of them are used by other libraries, for example the Servo library uses several, if you get a “multiple definition” error for one of the TC_handler() functions, that is probably what is going on, you’ll just need to use a different interrupt. There are 4 different clock frequencies we can use when we set how often the interrupt should trigger. They simply divide the main processor frequency by /2, /8, /32, or /128. Since the main processor has a frequency of 84Mhz, and we are trying to flash a LED a couple times per second at most, we want the /128.

How fast is our timer if we hook it up to the /128 clock?

84 Mhz / 128 = 656,250 Hz (or 656 kHz).

Then how many timer “clicks” do we want to wait to turn the LED on or off? Lets assume the S will blink every .2 seconds. Then we want

(0.2) * (656250) = 131,250.

So from the view of the timer we are saying, turn the LED on then count to 131,250, then turn it off.

For the O, lets flash the LED once every second, we want (1)*(656250) = 656,250.

Lets not get bogged down in details just yet, here is the code to run the SOS program with timer interrupts, go ahead and upload it, then we’ll cover what the code does.

// These are the clock frequencies available to the timers /2,/8,/32,/128
// 84Mhz/2 = 42.000 MHz
// 84Mhz/8 = 10.500 MHz
// 84Mhz/32 = 2.625 MHz
// 84Mhz/128 = 656.250 KHz
//
// 42Mhz/44.1Khz = 952.38
// 10.5Mhz/44.1Khz = 238.09 
// 2.625Hmz/44.1Khz = 59.5
// 656Khz/44.1Khz = 14.88 // 131200 / 656000 = .2 (.2 seconds)

// 84Mhz/44.1Khz = 1904 instructions per tick
const int led_pin = 13;
int state = false;
int interruptCtr = 1;
int S = 0;

void setup()
{
  pinMode(led_pin, OUTPUT);
  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);		 // disable write protection for pmc registers
  pmc_enable_periph_clk(ID_TC7);	 // enable peripheral clock TC7

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC2,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4); 
  TC_SetRC(TC2, 1, 131200);
  TC_Start(TC2, 1);

  // enable timer interrupts on the timer
  TC2->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;   // IER = interrupt enable register
  TC2->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;  // IDR = interrupt disable register

  /* Enable the interrupt in the nested vector interrupt controller */
  /* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
  NVIC_EnableIRQ(TC7_IRQn);
}

void loop()
{
  // do nothing timer interrupts will handle the blinking;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT HANDLERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TC7_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 1);
  state = !state;
  digitalWrite(led_pin, state);

  if( interruptCtr++ >= 6 )
    {
     interruptCtr = 1;
     S = !S; // are we flashing S or O
     if( S ) // set time till next interrupt
        TC_SetRC(TC2, 1, 131200); // 131200 / 656000 = .2 seconds
     else
        TC_SetRC(TC2, 1, 656000); // 656000/ 656000 = 1 second
    }
}

That’s quite a bit more complicated than the first time we demonstrated this program. Lets go line by line, and explain what is going on. The first 4 lines should look familiar.

const int led_pin = 13;    // use port 13 for to power the LED.
int state = false;         // 'state' will mean on or off
int interruptCtr = 1;
int S = 0;

The first line just assigns our LED to a port. The ‘state’ variable will be used to keep track of whether the LED is on or off. The variable ‘interruptCtr’ is used to keep track of how many interrupts have occurred since the last time we made a change, and the ‘S’ variable will keep track of whether we are currently flashing ‘S’ or ‘O’

In the setup() function we have to do some work to get the timers set up, First we need to disable write protection the pmc registers ( power management controllers). In other words, enable the pmc registers. These are turned off by default, because power consumption is very important for some users of these chips, by leaving them off you are less likely to unknowingly waste power.

  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);		 // disable write protection for pmc registers
  pmc_enable_periph_clk(ID_TC7);	 // enable peripheral clock TC7

the first line disables write protection for the power management controller registers. The second line enables the peripheral clock, we will use TC7. If you look at the sites I started with for this, RC Arduino you’ll see they’re not using TC7, the problem I ran into is that the servo library uses the same timers, so I had to use a different timer.

The next line is a call to TC_Configure(). The first parameter is choosing TC2 as the timer we want to use, the second parameter chooses which channel of the TC2 to use, and the third argument is using the bit wise OR operator, in effect it is saying to use the 3 settings, TC_CMR_WAVE means to choose waveform mode ( as opposed to Capture Mode), TC_CMR_WAVESEL_UP_RC means to set the counter to run up, and reset when it equals RC, TC_CMR_TCCLKS_TIMER_CLOCK4 means we want to run the clock at MCR/128. For more detail on these settings, see section 37.7 of the datasheet.

/* we want wavesel 01 with RC */
TC_Configure(/* clock */TC2,/* channel */0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);

We have 5 different cock speeds we can use.

TIMER_CLOCK1: 84Mhz/2 = 42.000 MHz
TIMER_CLOCK2: 84Mhz/8 = 10.500 MHz
TIMER_CLOCK3: 84Mhz/32 = 2.625 MHz
TIMER_CLOCK4: 84Mhz/128 = 656.250 KHz
TIMER_CLOCK5: SLCK ( slow clock )


next is TC_SetRC(), this is where we actually set the rate that we want the timer to interrupt at, we use timer clock 4 in the configure statement, which runs at 656.250 MHz. If we take 656.250 MHz and multiply it by .2 seconds we get 131200. In other words this statement is saying we want the timer interrupt to trigger every 131200 ticks of TIMER4. Next we need to call TC_Start(), this will actually start the timer.

 
  TC_SetRC(TC2, 0, 131200); // sets interrupt rate 
  TC_Start(TC2, 0);

Next we need to enable the timer interrupts, this is done with the following two statements.

  // enable timer interrupts on the timer
  TC2->TC_CHANNEL[0].TC_IER=TC_IER_CPCS;   // IER = interrupt enable register
  TC2->TC_CHANNEL[0].TC_IDR=~TC_IER_CPCS;  // IDR = interrupt disable register

Next we need to enable the nested vector interrupt controller, which handles all interrupts.

  /* Enable the interrupt in the nested vector interrupt controller */
  /* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
  NVIC_EnableIRQ(TC7_IRQn);

We don’t need to do anything in the loop() section, our interrupt routines do all the work. Every time the interrupt occurs, we toggle the ‘state’ variable. This is done with the line.


state = !state;

Then use digitalWrite() to set the led pin.

Next we check ‘interruptCtr’. We want to change the letter we are flashing every 6 interrupts, on-off-on-off-on-off is a total of 6 interrupts. If it has been six times, then we toggle the ‘S’ variable and call TC_SetRC() to change the interrupt interval.

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT HANDLERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TC7_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 1);
  state = !state;
  digitalWrite(led_pin, state);

  if( interruptCtr++ >= 6 )
    {
     interruptCtr = 1;
     S = !S; // are we flashing S or O
     if( S ) // set time till next interrupt
        TC_SetRC(TC2, 1, 131200); // 131200 / 656000 = .2 seconds
     else
        TC_SetRC(TC2, 1, 656000); // 656000/ 656000 = 1 second
    }
}


Intro->SOS->LCD Tutorial->Servo Tutorial->Timer Interrupts->Hacking Servos->Tone generator->DDS Tone Generator->Theremin 1->Theremin 2->RTTL Songs

 Posted by at 3:55 pm

  7 Responses to “Timer Interrupts”

  1. i would like to use an external clock for this example. do you know how i could rout the external PIA4 pin to the timer/counter? i know i need to use PIO_configure() but i can’t figure out how!

  2. Great tutorial and explanation on line-by-line of your code. I am new to this … so I have noob question – I want to generate a continuous 18.3 Mhz clock (square pulses) using the Due. How can I do that? Do I reduce the interrupt rate?

    Thank you.

  3. Hi,
    Since we need to set an interrupt when it hits RC, wont it make sense to use Wavesel 10 instead of Wavesel 01? I’m a newbie so forgive me if this is a silly question…

  4. Hi,

    Thanks for the tutorial, it helps a lot as it is quite difficult to find documentation about timer interrupts on the DUE board.

    I am still a bit confused about something, you use “enable peripheral clock TC7” and then use TC2 clock, why two different clocks? What is the difference?

    Best,

    Sebastien

  5. Hello Danad,

    I have basic understanding of Timer Interrupt. Now when I was referring the tutorial many doubts came into my mind

    1) In the above project why it has selected /128 (I know that 84Mhz/128=656Khz)and why not /2,/8,/32?

    2)”// 656Khz/44.1Khz = 14.88 // 131200 / 656000 = .2 (.2 seconds)” I mean how does he got this calculation what does this means?

    3) In TC_Configure Which conditions are taken into consideration selection of TC0,TC1,TC2 and their respective channels and what are the criterias for Clocks are assigned to Timer Counters?

    Please Help.

  6. Hi mate, thanks for the fantastic tutorial! With a bit of modification I used this code to control the velocity of a stepper motor, entirely with interrupts and a function which configured the period (in ticks) of the timer!

    Just as an aside, your link in “For more detail on these settings, see section 37.7 of the datasheet.” is dead. I think it’s moved to: http://www.atmel.com/images/doc11057s.pdf

    Also a quick question – what links the function TC7_Handler() to the interrupt? I can’t see any explicit mention of it when you configure the timer in setup().

    • Thanks, I updated the link. TC7_Handler is just a built in name, you don’t need to specify it, just enable TC7.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)


Time limit is exhausted. Please reload CAPTCHA.