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:
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.
