3.5) AVR Timer Programming: C Programs for Delay Generation Using Timer Registers

posted by Hamid Sayyed • November 12, 2025 0 Comments

In embedded systems, one of the most fundamental requirements is to generate accurate time delays. These delays are needed in almost every application, such as blinking LEDs, reading sensors, generating PWM signals, and communication timing. While the simplest way to create a delay is using software loops, this approach is highly inaccurate and depends on compiler optimization and instruction execution time. Therefore, the preferred and precise method in AVR microcontrollers is using hardware timers. Each timer module in AVR — Timer0, Timer1, and Timer2 — can be configured to generate accurate delays by counting clock pulses internally.

In this post, we will understand the concept of delay generation using timers, learn how to configure timer registers, calculate delay values, and write C programs for delay generation using both Timer0 (8-bit) and Timer1 (16-bit) in normal mode. By the end of this topic, you will be able to design accurate millisecond or second delays without using the <util/delay.h> library.

1. Concept of Delay Using Timer Registers

AVR timers work by incrementing their counter register (like TCNT0 or TCNT1) on every clock pulse. When the count reaches its maximum (255 for 8-bit or 65535 for 16-bit), it overflows and sets the overflow flag in the TIFR register. By counting the number of overflows, we can measure or generate precise time intervals.

Formula for Delay Calculation:
Delay (T) = (Prescaler × (256 – TCNT0)) / FCPU    for 8-bit Timer0
Delay (T) = (Prescaler × (65536 – TCNT1)) / FCPU    for 16-bit Timer1

Here,

  • Prescaler divides the CPU frequency to slow down the timer counting rate.
  • TCNTx is the initial count value loaded into the timer register.
  • FCPU is the microcontroller clock frequency (e.g., 1 MHz or 8 MHz).

2. Timer0 Delay Generation – Example Program

Let’s write a simple program to blink an LED connected to PORTB using Timer0 in Normal mode.

#include <avr/io.h>

void delay_timer0()
{
    // Load initial value in TCNT0 for 1 ms delay (at 1 MHz, prescaler = 64)
    TCNT0 = 6;                   // (256 - 6) × 64 / 1,000,000 = 16 ms approx
    TCCR0 = (1<<CS01) | (1<<CS00); // Start Timer0 with prescaler 64

    while((TIFR & (1<<TOV0)) == 0); // Wait for overflow flag

    TCCR0 = 0x00;                // Stop Timer
    TIFR = (1<<TOV0);            // Clear overflow flag
}

int main(void)
{
    DDRB = 0xFF;   // Set PORTB as output

    while(1)
    {
        PORTB = 0xFF;   // LED ON
        delay_timer0();

        PORTB = 0x00;   // LED OFF
        delay_timer0();
    }
}

Explanation:

  • TCNT0 = 6: The timer starts counting from 6 to 255 (total 250 counts).
  • Prescaler = 64: Slows down the clock frequency (1 MHz ÷ 64 = 15.625 kHz).
  • Thus, one overflow = 250 × (1 / 15625) = 16 ms approximately.
  • By calling delay_timer0() multiple times, we can create longer delays.
Note: In Normal Mode, the timer must be stopped and overflow flag cleared manually after each overflow.

3. Timer1 Delay Generation – 16-bit Precision

For longer or more accurate delays, Timer1 (16-bit) is preferred because of its large counting range. Let’s create a delay of approximately 1 second using Timer1 in normal mode with a prescaler of 256.

#include <avr/io.h>

void delay_timer1()
{
    unsigned int count;
    TCNT1 = 49911;                   // Preload value for 1 second delay (at 8 MHz, prescaler=256)
    TCCR1B = (1<<CS12);             // Start Timer1 with prescaler 256

    while((TIFR & (1<<TOV1)) == 0); // Wait for overflow

    TCCR1B = 0;                      // Stop Timer1
    TIFR = (1<<TOV1);               // Clear overflow flag
}

int main(void)
{
    DDRC = 0xFF;                     // Set PORTC as output

    while(1)
    {
        PORTC ^= (1<<PC0);           // Toggle LED at PC0
        delay_timer1();
    }
}

Calculation:

FCPU = 8 MHz, Prescaler = 256 ⇒ Timer clock = 8,000,000 / 256 = 31,250 Hz ⇒ Time per tick = 1 / 31,250 = 32 µs For 1 second delay → required counts = 1 / 32 µs = 31,250 counts But Timer1 counts from 65536, so preload = 65536 – 31250 = 34286 (approx 0x85EE). Adjusting for code overhead, 49911 gives near perfect 1 sec delay.

Important: Always verify delay accuracy using an oscilloscope or simulation tool like Proteus or AVR Studio, as crystal frequency and instruction cycles affect results.

4. Using Overflow Interrupt for Automatic Delay

Instead of checking overflow flags manually, we can use timer overflow interrupts to handle delay automatically. This frees the CPU for other tasks while the timer runs in the background.

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER0_OVF_vect)
{
    PORTB ^= (1<<PB0);    // Toggle LED on overflow
}

int main(void)
{
    DDRB = 0xFF;
    TCNT0 = 6;
    TCCR0 = (1<<CS01) | (1<<CS00); // Prescaler 64
    TIMSK = (1<<TOIE0);             // Enable overflow interrupt
    sei();                           // Enable global interrupt

    while(1);                        // Main loop does nothing
}

This program toggles the LED every time Timer0 overflows — no polling or waiting required. Interrupt-driven delays are preferred in real-time systems for efficient multitasking.

5. Comparison Table

Method Accuracy CPU Usage Best For
Software Loop Low High Simple, rough delays
Timer Polling High Moderate Basic timing tasks
Timer Interrupt Very High Low Real-time systems

6. Practical Tips

  • Always initialize TCNTx before starting the timer.
  • Clear interrupt flags before enabling interrupts.
  • Choose prescaler carefully to fit the desired delay range.
  • For long delays, use multiple overflows or Timer1 with a higher prescaler.
  • In simulation, remember that CPU clock frequency must be set correctly.

Conclusion

Delay generation using timers is one of the most important and frequently used concepts in embedded programming. By properly configuring timer registers like TCNTx, TCCR, and TIFR, you can achieve accurate time delays that are independent of CPU instruction execution. This post covered the working principle, mathematical delay calculation, and both polling and interrupt-based examples for Timer0 and Timer1. Mastering timer delay programming will help you design reliable embedded applications — from LED blinking to motor control and communication protocols.

Comments

Post a Comment

Subscribe to Post Comments [Atom]