Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Using Timers and Interrupts in AVR Atmega-328

February 15, 2024

Using Timers and Interrupts in AVR Atmega-328

Introduction

In embedded systems, timers and interrupts play a crucial role in precise timing and event-based control. The AVR Atmega-328 microcontroller, commonly used in Arduino boards, provides several timers and interrupt options to facilitate these operations. In this blog post, we will explore how to effectively utilize timers and interrupts in AVR Atmega-328 using C programming language. We will cover the basics of timers, their configurations, and how to handle interrupts for efficient event handling.

Timers in AVR Atmega-328

Timers in AVR Atmega-328 are hardware modules that can generate precise timing intervals. These timers are driven by an internal clock source and can be configured to generate interrupts or perform specific actions when a defined interval is reached. The microcontroller supports three timers: Timer/Counter0, Timer/Counter1, and Timer/Counter2. Each timer has its own set of registers and functionality.

To use a timer, we need to configure its registers, including the control registers (TCCRnA, TCCRnB, etc.), output compare registers (OCRnA, OCRnB, etc.), and timer counter register (TCNTn). Following is a high-level example of configuring Timer/Counter0 to create a 1ms interrupt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void init_timer0() {
  // Set CTC mode (Clear Timer on Compare Match) and set prescaler to 64
  TCCR0A |= (1 << WGM01);
  TCCR0B |= (1 << CS01) | (1 << CS00);

  // Set the desired compare value for 1ms (8MHz clock, prescaler 64)
  OCR0A = 124;

  // Enable compare A interrupt
  TIMSK0 |= (1 << OCIE0A);

  // Enable global interrupts
  sei();
}

In the code above, we have configured Timer/Counter0 for CTC mode and prescaler of 64. The compare value is set to achieve a 1ms interrupt. The TIMSK0 register is used to enable the compare A interrupt, and sei() is called to enable global interrupts. Now, whenever Timer/Counter0 reaches the defined compare value, an interrupt will be triggered, and the corresponding interrupt service routine (ISR) will be executed.

Interrupts in AVR Atmega-328

Interrupts are hardware or software events that can pause the normal program flow and transfer control to a specific routine. AVR Atmega-328 has a dedicated Interrupt Vector Table (IVT) that holds addresses of the corresponding ISRs for different interrupt sources. When an interrupt occurs, the microcontroller looks up the table and jumps to the respective ISR to handle the event.

To setup an interrupt, we need to specify the interrupt source, enable it, and define its ISR. Here is an example of defining the ISR for Timer/Counter0 compare A interrupt:

1
2
3
ISR(TIMER0_COMPA_vect) {
  // Handle the compare A interrupt
}

The ISR() macro is used to define the ISR, where TIMER0_COMPA_vect is the interrupt vector associated with Timer/Counter0 compare A interrupt. Inside the ISR, we can write our code to handle the interrupt, such as updating variables, toggling pins, or triggering other events.

To enable the corresponding interrupt source, we need to set the corresponding bit in the interrupt mask register (TIMSKn, where n represents the timer number). For example, to enable Timer/Counter0 compare A interrupt:

1
TIMSK0 |= (1 << OCIE0A);

Putting It All Together

Now that we understand the basics of timers and interrupts, let’s see how we can combine them to achieve more complex functionalities. Consider the following example where we want to toggle an LED every 500ms using Timer/Counter0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <avr/io.h>
#include <avr/interrupt.h>

void init_timer0() {
  // Set CTC mode (Clear Timer on Compare Match) and set prescaler to 64
  TCCR0A |= (1 << WGM01);
  TCCR0B |= (1 << CS01) | (1 << CS00);

  // Set the desired compare value for 500ms (8MHz clock, prescaler 64)
  OCR0A = 124;

  // Enable compare A interrupt
  TIMSK0 |= (1 << OCIE0A);

  // Enable global interrupts
  sei();
}

ISR(TIMER0_COMPA_vect) {
  // Toggle the LED pin
  PORTB ^= (1 << PB5);
}

int main() {
  // Set PB5 as output for the LED
  DDRB |= (1 << PB5);

  // Initialize Timer/Counter0
  init_timer0();

  while (1) {
    // Do other tasks while the interrupt handles LED toggling
  }
}

In this example, Timer/Counter0 is configured to generate a 500ms interrupt. Inside the ISR, we toggle pin PB5, which is connected to an LED. In the main() function, we set PB5 as an output and initialize Timer/Counter0. The while loop allows us to perform other tasks while the interrupt takes care of toggling the LED.

Conclusion

Using timers and interrupts in AVR Atmega-328 allows us to achieve precise timing and efficient event handling in embedded systems. Understanding the basics of timer configuration, interrupt handling, and their integration is essential for developing robust and responsive applications. By leveraging timers and interrupts effectively, we can significantly enhance the capabilities of our AVR Atmega-328 microcontroller-based projects.

I hope this blog post has provided you with comprehensive insights into using timers and interrupts in AVR Atmega-328. Feel free to experiment and explore more advanced features and functionalities offered by timers and interrupts in AVR microcontrollers.


➡️ Flask blueprints


⬅️ A Flask primer


Go back to Posts.