Skip to main content

Generating Delay Using Timer/Counter of Atmega328

     Delay is required in most of our micro-controller applications, for example to blink a LED for certain duration or just to pause the micro-controller for short time. The avr-gcc compiler in Atmel Studio comes with delay.h library to make things easy for us, but do we know how it work. No, not until we do some digging inside of the library. In this post we will learn to generate short delay without using library, this will give some basic idea for how the  _delay_ms() or _delay_us() works.
     In this tutorial a Logic Analyzer is used to observe the output of Atmega328 on pin PB5. Circuit configuration for Atmega328 is shown in diagram below.
Circuit Configuration
     Atmega328 has three individual timer/counters identified as TC0, TC1, TC2. TC0 is an 8 bit general purpose timer/counter which can also be used for generating PWM waves, TC1 on other hand is 16 bit timer and can be used for generating PWM. Timer/Counter 2 (TC2) is 8 bit timer with support for PWM generation and asynchronous operations. We will be learning to use TC0 to generate some delay. Before we write our program we need to know registers that are related to TC0 and do some simple calculations.
     There 8 individual registers that are related to Timer/Counter 0; those are TCCR0A, TCCR0B, TIMSK0, GTCCR, TCNT0, OCR0A, OCR0B and TIFR0. For our purpose of this tutorial we won't be studying all of those register the ones we required are TCCR0A, TCCR0B, TCNT0, and TIFR0.
     We will write a function to generate a delay of around 8ms using TC0. The registers used will be described as we move further on writing the code function. First, the function prototype is to be declared after adding header file. The function prototype is a declaration for the type of value that will be returned upon on executing the function or any argument as input for execution of function; for example integer, character, null, etc. Since our function won't be returning anything we will declare it as a void function.
Declaring Function Prototype
     We need to initialize a physical port pin to toggle after certain duration to observe the generated waveform on DSO or Logic Analyzer. I will be using one of the port pins from PORTB of Atmega328, you can can change the pin as per needs but make sure the DDR and PORT register are configured properly. Once that is done we need to toggle the pin after interval (8ms) in endless while loop. This steps will be carried out in the main() function. The interval will be generated by calling our delay function.
Main Function
     Since the main function is ready, it is time to move on to make the delay function. In delay function first we need to configure the TCCR0A and TCCR0B registers in order to initialize the Timer0.
TCCR0A
TCCR0B
     Now that we now how to configure timer 0 for simple delay, let's set the bits accordingly.

TCCR0A = 0x00; //Configure Timer0 for normal mode and no waveform generation
TCCR0B = 0x05; //Set prescaler value to clk/1024 and turn off forced output comparator

Now that timer0 is configured to count to generate delay, we need to load value for timer to count to so that delay can be generated. For this purpose we need to access TCNT0 register in which we will load the value to count. TCNT0 is an 8-bit register, loading a value in this register will make the timer to count up to that value to generate delay. To generate specific delay we need to perform some math to load proper value in TCNT0. In following image I have shown the math which is simple.
Calculation for value to load in TCNT0
 Once the TCNT0 is loaded with calculated valued it will raise the overflow flag after that many cycles. In our case 131 cycles i.e. approximate 8ms delay.

TCNT0 = 0x83; //load value to TCNT0 for counting
Once the timer starts counting it is necessary to check if it has reached a desired value for that micro-controller needs to stay on hold till the overflow flag is raised. For this microcontroller just needs to check the status of Timer Over Flow(TOV) bit in TIFR0 register.

while((TIFR0 & 0x01) == 0); //check TOV bit in TIFR0 for overflow

Once the flag is raised TCCR0A, TCCR0B and TIFR0 need to be cleared for proper function at next call back.
//Clear the registers
TCCR0A = 0x00;
TCCR0B = 0x00;
TIFR0 = 0x1; 

Delay Function

 Code:

#include <avr/io.h>

void delay(); //declare function prototype for null type

int main(void)
{
    DDRB = 0x20; //setting a pin on PORTB as output
    PORTB = 0x22; //setting initial state of the port pins

    while (1) //endless while loop
    {
        PORTB = 0x02; //set PB5 to LOW while keeping internal pullup of PB1 active
        delay(); //giving a call back to Delay function to generate delay
        PORTB = 0x22; //set PB5 to HIGH while keeping internal pullup of PB1 active
        delay();//giving a call back to Delay function to generate delay
    }
}

void delay()
{

  //configure timer0 with prescaler value 1024 in non pwm mode
  TCCR0A = 0x00;
  TCCR0B = 0x05;

    TCNT0 = 0x83; //calculated value

    while((TIFR0 & 0x01) == 0);//wait till timer overflow flag is raised
    

    //reset the registers
    TCCR0A = 0x00;
    TCCR0B = 0x00;

    TIFR0 = 0x1;

}

     The output of this code will be a square wave in form of port pin PB5 (Pin 19) toggling from HIGH to LOW and vice-versa at interval of approx 8 ms. This waveform can be observed using DSO or Logic Analyzer.
Output on screen using Logic Analyzer
     The above screenshot shows square-wave that is generated by turning PB5 on & off every 8ms. In this method we are not using interrupt. The delay van be generated using interrupts as well. Interrupt method is very useful where micro-controller is not required to be on hold while timer counts the time. Interrupt method will be covered in a dedicated post at later point.
     Along with solution there's a simple excel sheet attachment that can be used to quickly calculate value of TCNT0.
Download Now!

Comments