Lately I’ve become more and more fan of the Arduino microcontroller. I really like how easy it is to work with; the accompanying IDE plus the huge amount of community support and libraries available.

However, for a project I needed to be able to sample input audio at a certain frequency and this required me to understand a bit more deeply on how to configure the ATMEGA328 timer registers. In this post I’ll show you how to call a user function at a period of one millisecond.

I decided to use the 8-bit Timer2 which is one of the timers in the ATMEGA328. The principle is to let the timer counter register increment it’s value at a specific clock rate and eventually trigger an interrupt when it overflows (FF:s -> 0:s). Then we just need to load it with an appropriate value to make it overflow every one millisecond, and install our interrupt handler routine in the interrupt vector table. Sounds complicated? It’s really not when you’ve found all the necessary registers to play with. Keep on reading.

After some tedious reading in the ATMEGA328 datasheet (and googling!) I came up with this code:

/*
 * author: Sebastian Wallin
 * description:
 * Example on how to configure the periodical execution of a user
 * defined function (Interrupt service routine) using Timer2. This
 * example will run the function every 1ms.
 *
 * For detailed information on Timer2 configuration see chapter 17 in
 * ATMEGA328 datasheet.
 */

/* Timer2 reload value, globally available */
unsigned int tcnt2;

/* Toggle HIGH or LOW digital write */
int toggle = 0;

/* Setup phase: configure and enable timer2 overflow interrupt */
void setup() {
  /* Configure the test pin as output */
  pinMode(2, OUTPUT); 

   /* First disable the timer overflow interrupt while we're configuring */
  TIMSK2 &= ~(1<<TOIE2);

  /* Configure timer2 in normal mode (pure counting, no PWM etc.) */
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
  TCCR2B &= ~(1<<WGM22);

  /* Select clock source: internal I/O clock */
  ASSR &= ~(1<<AS2);

  /* Disable Compare Match A interrupt enable (only want overflow) */
  TIMSK2 &= ~(1<<OCIE2A);

  /* Now configure the prescaler to CPU clock divided by 128 */
  TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits
  TCCR2B &= ~(1<<CS21);             // Clear bit

  /* We need to calculate a proper value to load the timer counter.
   * The following loads the value 131 into the Timer 2 counter register
   * The math behind this is:
   * (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
   * (desired period) / 8us = 125.
   * MAX(uint8) + 1 - 125 = 131;
   */
  /* Save value globally for later reload in ISR */
  tcnt2 = 131; 

  /* Finally load end enable the timer */
  TCNT2 = tcnt2;
  TIMSK2 |= (1<<TOIE2);
}

/*
 * Install the Interrupt Service Routine (ISR) for Timer2 overflow.
 * This is normally done by writing the address of the ISR in the
 * interrupt vector table but conveniently done by using ISR()  */
ISR(TIMER2_OVF_vect) {
  /* Reload the timer */
  TCNT2 = tcnt2;
  /* Write to a digital pin so that we can confirm our timer */
  digitalWrite(2, toggle == 0 ? HIGH : LOW);
  toggle = ~toggle;
}

/* Main loop. Empty, but needed to avoid linker errors */
void loop() {
}

The code should be pretty clear from the comments but let me just elaborate a bit on the calculations:

First of all, the observant reader will notice that the above code will only be accurate for a CPU running at 16MHz since the calculations are based on this frequency. By using the prescaler feature we can increase the time between our interrupts a bit, and by using a prescaler value of 128 we simply tell
the timer to increase it’s value every 128:th clock cycle (or every 8:th microsecond if the CPU frequency is 16MHz). Now that we now this timer interval , we can calculate the number of steps for the timer register by dividing our target value 1ms, by 8us, which gives us the value 125.
Finally, since the timer counter will generate an interrupt on overflow, we simply subtract 125 from the maximum counter value, 256, which gives us the final value 131.

To prove that this is actually working I attached my chinese pocket oscilloscope from Jyetech to the digital output that I’m writing to in the code. Enjoy:

Joy oh joy! one interrupt every one millisecond

Well, what’s the use of this, you might ask? I’ll tell you! You could do a number of cool things, like your own synthesizer, music sampling (with limited frequency response though…) or perhaps a small scheduler for light weight threading! Impressive, huh? =)

Well, There you have it! Now go and write something useful. You’ll see more related topics from me in the future.


  • http://hackr.se Joel Larsson

    Nice article, probably will need to ask you more about this some time. Perhaps something for “malen”?

  • http://mikaelhalen.com Mikael Halén

    Great post Sebastian! I would like to read more about your chinese pocket oscilloscope from Jyetech in the future :]

  • Terry Hoy

    Thanks Sebastian,
    I have spent a lot of time trying to work out the maths needed to give a desired interrupt time, and at last I found your detailed and well commented example.
    By the way I got exactly the same display on my ancient scope as yours, but if you include a “pinMode(2, OUTPUT); in the setup you will get a decent square wave.
    Regards Terry

  • http://sewa.se Sebastian Wallin

    Hi Terry,
    Glad I could help.
    Thanks for pointing it out! You’re right, at reset the I/O-pins are configured as Hi-Z input which accounts for the sluggish behaviour =)

  • Steve Snead

    I could not get it to work. I copied and pasted you code and I get a error: expected unqualified-id before numbric constant. But I getting closer to know how to use the timer thanks. Maybe I will find some code that works.

  • Steve Snead

    My bad I copied the line numbers too. Thanks it works great now.

  • http://sewa.se Sebastian Wallin

    Great! Don’t forget the pinMode(2, OUTPUT); in the setup function as Terry pointed out (I’ll update the code right away)

  • http://harteware.blogspot.com Jan

    Hey thanks for your excellent writeup!
    I noticed a little misstake in line number 45 you write ” MAX(uint8) – 125 = 131; “.
    To get the same result as you i need to take 256 as MAX(uint8), but in reality its 255.
    Its 8 bits long so 1111 1111 is the biggest possible number and thats 2^8 – 1 = 255

    Just a little error i know, maybe this will be of help if looking for some really strange timing jitters..

    best regards,
    Jan

  • http://sewa.se Sebastian Wallin

    Hi! Thanks for pointing out my sloppy notation. You’re right, it should be MAX(uint8)+1 because it will trigger the interrupt on 0 (overflow), which makes the extra step

  • http://harteware.blogspot.com Jan

    now its clear!
    i just tried your code and it seems as the the timer reload doesn’t work.
    here’s my test code where i just print out the µs since last time the ISR was called

    unsigned long t = micros();
    ISR(TIMER2_OVF_vect) {
    TCNT2 = tcnt2;
    Serial.println(micros() – t); // print time since last execution
    t = micros();
    }

    it prints 8 all the time, no matter what value tcnt2 is.. Any idea would be greatly appreciated!

  • http://sewa.se Sebastian Wallin

    Hmm, maybe this has something to do with it:
    http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1235919899

  • http://harteware.blogspot.com Jan

    i rather don’t think so… :(
    i’m not delaying, only reading out the current value.

    hey heavy snow fall started here in karlsruhe, germany, looks like sweden here ;)

  • http://harteware.blogspot.com Jan

    i found my bug…
    your code is perfect, i just was ending way too much data in my ISR routine + i didn’t declare my time-stamp variable volatile… argh, @#!§ one afternoon gone… for a n00b thing..

  • http://bebop.cc/blog/2011/08/31/programacao-concorrente-com-arduino/ Técnicas de programação [quase] concorrente com Arduino « Bebop – Computação Criativa – Blog

    [...] o uso de timers/contadores na Arduino envolve programação de baixo nível (vide: http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino). Entretanto, há bibliotecas que abstraem a complexidade do uso de timers, trazendo esse recurso [...]

  • Matt

    Thanks! This article is really useful.

    Just wondering – what happens if the ISR takes longer than 1ms to execute?

    Also, can a ISR that has been called but has not yet finished running be interrupted by another ISR?

  • http://sewa.se Sebastian Wallin

    Hi! Glad you liked it.
    In the AVR328, global interrupts are automatically disabled when fired. By using ISR(..) to declare the function you’ll make sure that they are re-enabled at function return (RETI instruction, see data sheet page 11 – bit7 in status register, http://www.atmel.com/dyn/resources/prod_documents/doc8271.pdf). You can also enable/disable interrups yourself by using SEI/CLI-instructions.

    You should always try to keep your interrupt functions as short and deterministic as possible. 

  • frank

    impressive job. Very clear and easy to follow. Thanks!