Here we explain and provide example codes for ATmega32A Timer/Counter for various mode of operation. There are basically four modes in which we can use Timer/Counter of ATmega microcontrollers. They are Normal mode, CTC mode, Fast PWM mode and Phase Correct PWM mode. Atmega32 microcontroller has three Timer/Counter know as Timer/Counter0, Timer/Counter1 and Timer/Counter2. Timer0 and Timer2 are 8 bits and Timer2 is 16 bit.
For purpose of illustration we show the following examples using Timer/Counter0. The other timers work in very similar way so the examples for timer0 can be easily extended for timer1 and timer2.
1. Programming Timer/Counter in Normal Mode
a. Timer0 in Normal Mode with Polling
b. Timer0 in Normal Mode with Interrupt
2. Programing Timer/Counter in CTC mode
a. Timer0 in CTC Mode with Polling
b. Timer0 in CTC Mode with Interrupt
3. Programming Timer/Counter in Fast PMW mode
4. Programming Timer/Counter in Phase Correct PWM mode
1. Programming Timer/Counter in Normal Mode
In normal mode of operation, the Timer/Counter0 counts from the count value loaded into TCNT0 register to the top value 0xFF or 256 in decimal since Timer/counter0 is 8 bit timer. For example if you load 156 into TCNT0 register then the timer will count from 156 to 256, that is counts 100(256-156). When the Timer has completed the count it sets up the TOV0 flag. This TOV0 flag resides in the TIFR register.
This TOV0 flag can be polled to take any further action, for example, turning on or off a LED or something else. In case of polling we have to clear the TOV0 flag by writing one to the corresponding TOV0 flag bit in the TIFR register. Also we have stop the Timer0 by writing 000 to the CS02, CS01, CS00 bits in the TCCR0 register.
Instead of polling TOV0 flag, we can also use timer/counter overflow interrupt once the TOV0 flag is raised by calling the ISR(Timer0_OVF_vect) routine and write code to take some action like turning on or off a LED within the ISR routine. Using interrupt instead of polling saves the CPU load and time since the CPU is then not tied to monitor the TOV0 flag periodically.
So here will illustrate how to use Timer/Counter0 in Normal mode with polling and interrupt methods. The question remains what value of count should be loaded into the Timer/Counter 0 TCNT0 register. This depends upon your application.
For example, say you want to generate a square wave on PORTD pin 7 with frequency of 20KHz. Assume your are using 4MHz external crystal oscillator with ATmega32A micrcontroller. We then can use the Timer0 as a delay timer, that produces high pulse for that certain delay and produces low pulse for the same amount of delay hence producing the square wave.
Derivation of Formula to Calculate Count Value for TCNT0
Below is the formula to calculate count value(C) to be loaded into the TCNT0 register.
(256-C)*Tt = Td ---------------->(1)
Where Tt is the Timer period and Td is the delay time amount and C is the count value to be loaded into the TCNT0 register.
Tt can be calculated as follows,
Tt = 1/Ft ------------------->(2)
where Ft is the Timer frequency
but, Ft = Fosc/N ------------------>(3)
where Fosc is the oscillator frequency and N is the prescalar 1, 8, 64, 1024
substituting Ft into eqn.(2) we get,
Tt = N/Fosc ----------------->(4)
again substituting Tt from (4) into eqn(1) we get,
(256-C)*N/Fosc = Td ------------------->(5)
Now,
Td = Tw/2 ---------------------->(6)
where Tw is the time period of the wave we want to produce.
The equation (6) just means one period of square wave, Tw, consist of high and low pulse of same time delay Td
Substituting value of Td from eqn(6) into eqn(5) we get,
(256-C)*N/Fosc = Tw/2 ---------------------(7)
Since,
Tw = 1/Fw ---------------------->(8)
where Fw is the frequency of square wave
Subtituting Tw from eqn(8) into eqn(7) we get,
(256-C)*N/Fosc = 1/2*Fw
Rearraning,
C = 256 - Fosc/2*N*Fw ----------------->(9)
This equation (9) can be used to solve for count value to be loaded into the TCNT0 register.
For example, if we do not use pre-scalar then N = 1. We know Fw = 20Khz, Fosc = 4MHz so placing these values in (9) we get,
C = 256 - 4MHz/2 * 1 * 20Khz
or,
C = 256 - 4*10^6/2*20*10^3
That is C = 156
So to generate 20KHz square wave with equal delay between high and low pulse we need to load TCNT0 with count value of 156. The value 156 corresponds to 25us time delay since 20Khz corresponds to 50us time period and half of it is 25us.
You can also use the ATmega32 online calculator to calculate the value of TCNT0.
a. Timer in Normal Mode with Polling method
Below is C code example to produce square wave of 20KHz using Timer0 to create time delay of 25us.
#ifndef F_CPU
#define F_CPU 4000000UL
#endif
#include <avr/io.h>
void delayT0(void);
int main()
{
DDRD |= (1<<PD7);
while (1){
PORTD ^= (1<<PD7);
delayT0();
}
return 0;
}
void delayT0(void){
TCNT0 = 156; //load value to count
TCCR0 = 0b00000001;
while(!(TIFR & (1<<TOV0)));
TCCR0 = 0;
TIFR |= (1<<TOV0);
}
In the code, we have first declared the PORT D pin 7 as output using the statement DDRD |= (1<<PD7). Then in the while loop, we make port D pin 7 high using PORTD |= (1<<PD7). After this we wait for certain amount of time using the delayT0() function. When this function is called, first we load the TCNT0 with the count value calculated as 156 for 20Hz square wave above. Then we write to the TCCR0 register appropriate value (0x01 here), to configure the Timer/Counter0 in normal mode and no prescalar. After that we use a while loop to regularly check if the TOV0 flag in the TIFR register is set or not. Until it is not set, the statements below this while statement are not executed. Once there the flag is set, we turn off the timer0 by writing 0 to the TCCR0 register and clear the TOV0 flag by setting that TOV0 flag bit high which is inside the TIFR register.
b. Timer in Normal Mode with Interrupt
In the following example code, we use the PORTD pin 7 to output square wave of frequency 20KHz as before. Here whenever the TOV0 flag is set because the count value is completed, we call the overflow interrupt routine. In the following code, to illustrate the concept of interrupt, we will be reading the state of a switch connected to PORT B pin 2, and according to whether it is pressed or not pressed we will turn on/off a LED which connected to the PORT C pin 0.
#ifndef F_CPU
#define F_CPU 4000000UL
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
DDRD |= (1<<PD7);
DDRB &= ~(1<<PB2);
DDRC |= (1<<PC0);
PORTB |= (1<<PB2);
TCNT0 = 156;
TCCR0 = 0x01;
TIMSK |= (1<<TOIE0);
sei();
while (1){
if((PINB & (1<<PB2)) == 0)
PORTC |= (1<<PC0);
else
PORTC &= ~(1<<PC0);
}
return 0;
}
ISR(TIMER0_OVF_vect){
TCNT0 = 156;
PORTD ^= (1<<PD7);
}
In the code example above, because we are dealing with interrupt we have to add the interrupt header file interrupt.h which resides in the avr folder so that is the reason for the include statement at the top. Then inside the main loop we first set up the ports. Here PORT D pin 7 and PORT C pin 0 are outputs and the PORT B pin 2 is setup as input. We have used here the statement PORTB |= (1<<PB2) to activate the pull resistor at that input pin. The thing we have to do is to setup the Timer/Counter0. First we put into the TCNT0 the count value which is 156 as before. Then we configured the Timer/Counter0 in normal mode without any prescalar by writing 0x01 into the TCCR0 register. Since we want to use interrupt feature with timer overflow we set up that by setting the TOIE0 bit in the TIMSK register. We then have to enable the global interrupt feature using the sei() instruction. We then reach the while loop inside which we perform the task of monitoring the switch state at PORTB pin2 and according turn on or off the LED connected to the PORTC pin 0.
Whenever there is timer overflow, the TOV0 flag is raised and since we have enabled the timer overflow interrupt bit TOIE0, the ISR(TIMER0_OVF_vect) function will be called and executed without interfering the task inside the main function which is that of monitoring the switch and taking action. Once the ISR() is called, the TCNT0 is loaded with the count value 156 and the PORTD pin 7 is toggled to high or low pulse according to previous state. This time delay between the toggle is according to the time delay required for produce square wave of 20KHz frequency.
2. Programing Timer/Counter in CTC mode
In the CTC(Clear Timer on Compare Match) mode of operation, the count value is loaded into the OCR0 register. The timer counts from 0 to the content of the OCR0 register and when there is a match, the OCF0 is set. The OCF0 is within the TIFR register. We can either monitor this OCF0 flag to take action which is called polling method or we can use interrupt to take action automatically by enabling the OCIE0 flag in the TIMSK register. Both these methods, polling and interrupt example are provided down below.
Here we will illustrate example of producing 20KHz square wave using Timer/Counter0 in CTC mode. The formula for the count value to be loaded into the OCR0 is given by the following equation.
C = Fosc/2*N*Fw - 1 ----------------->(10)
Again, no prescalar is used(N=1) and the crystal oscillator frequency is 4MHz. Then using the above equation we can calculate count value for generating 20KHz signal.
C = 4MHz/2*1*20KHz - 1
C = 99
You can also use the ATmega32 Timer/Counter online calculator to calculate the value of OCR0 for CTC mode.
a. Timer0 in CTC Mode with Polling
Below is the code example of Timer0 in CTC mode with polling method to create time delay.
#ifndef F_CPU
#define F_CPU 4000000UL
#endif
#include <avr/io.h>
void delayCTC(void);
int main()
{
DDRD |= (1<<PD7);
while (1){
PORTD ^= (1<<PD7);
delayCTC();
}
return 0;
}
void delayCTC(void){
OCR0 = 99; //load value to count
TCCR0 = 0b00001001;
while(!(TIFR & (1<<OCF0)));
TIFR |= (1<<OCF0);
}
This code is similar to the above code for Timer/Counter0 in normal mode with polling example which was explained in details above. Basically we set the PORT D pin 7 as output where we will send our square wave. In the while loop we use toggle method using ^= operator to output high and low pulse. In between the state toggle, we call the delayCTC() function which is responsible to create time delay between the high and low pulse. The time delay 25us. So high pulse stays high for 25us and then low pulse stays low for 25us. This gives us total period of square of 50us which is 20Khz frequency. Once the delayCTC() function is called, we load 156 count value into the OCR0 register. Then configure the Timer/Counter0 to have no pre-scalar value and in CTC mode by writing 0b00001001 to TCCR0 register. Once done we monitor the OCF0 flag to see whether it is set or not in the TIFR register using while() function. If it is then we exit from the while() function and stop the Timer0 by writing 0 to the TCCR0 and then clearing the OCF0 flag in the TIFR register.
b. Timer0 in CTC Mode with Interrupt
Example code of using Timer0 in CTC mode with interrupt is below.
#ifndef F_CPU
#define F_CPU 4000000UL
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
int main()
{
DDRD |= (1<<PD7);
DDRB &= ~(1<<PB2);
DDRC |= (1<<PC0);
PORTB |= (1<<PB2);
OCR0 = 99;
TCCR0 = 0b00001001;
TIMSK |= (1<<OCIE0);
sei();
while (1){
if((PINB & (1<<PB2)) == 0)
PORTC |= (1<<PC0);
else
PORTC &= ~(1<<PC0);
}
return 0;
}
ISR(TIMER0_COMP_vect){
PORTD ^= (1<<PD7);
}
In the above code, in order to use the Timer0 compare output match interrupt service routine(ISR) we have used the interrupt.h header file using #include statement. To illustrate the concept of interrupt we have made use of switch connected to PORT B pin 2 and a LED connected to PORT C pin 0. When the switch is pressed, the LED turns on. The Timer0 is here to used to create a delay of 25us. This delay is used between the on and off pulse sent to PORTD pin7. This on/off with delay of 25us or 50us time period gives a square wave of frequency 20KHz.