The ATmega32 has three timers called timer 0, timer 1 and timer 2 but the input capture is only available with timer 1. Simple example of using Atmega32A input capture was illustrated in the previous tutorials ATmega32A Input Capture Example and ATmega32 Input Capture Interrupt Example. Here it is illustrated how ATmega32 microcontroller input capture can be used for measuring external signal frequency and period.
Circuit Diagram for Frequency and Period Measurement
The following shows the hardware circuit diagram of measuring frequency and period of external signal and displaying them on LCD.
The above circuit diagram shows a function generator generating 12KHz signal which is connected to the PD6, the input capture pin. The LCD shows the measured frequency and period by the ATmega32 microcontroller.Program code for Frequency and Period Measurement
The following is the program code for frequency and period measurement.
#include <avr/io.h>
#include <stdlib.h>
#include "lcd.h"
#define icPin (1<<PD6)
int main(void)
{
DDRD |= icPin; //make PD7 pin an output for LED
PORTD |= icPin;
float t1,t2;
char period[16], freq[16];
float p,F,T;
float Fcpu = 16000000;
lcdinit();
lcdprint("Period Calc..");
//Timer 1 config: no noise canceller, rising edge, normal mode, no prescalar
TCCR1A = 0b00000000; //COM1A1 COM1A0 COM1B1 COM1B0 FOC1A FOC1B WGM11 WGM10
TCCR1B = 0b01000001; //ICNC1 ICES1 – WGM13 WGM12 CS12 CS11 CS10
while(1){
TCNT1 = 0; //clear TCNT1 register
TIFR = (1<<ICF1); //clear the ICF1
while((TIFR &(1<<ICF1))==0);
t1 = ICR1;
TIFR = (1<<ICF1);
while((TIFR &(1<<ICF1))==0);
t2 = ICR1;
p = t2 - t1;
F = Fcpu/p;
T = 1/F;
lcdclear();
if(F >= 1000){
F = F/1000;
dtostrf(F, 3, 2, freq);
lcdprint("F:");
lcdprint(freq);
lcdprint(" KHz");
T = 1000*T;
dtostrf(T, 3, 2, period);
lcdsetcursor(1,2);
lcdprint("T:");
lcdprint(period);
lcdprint(" ms");
_delay_ms(100);
}
else if(F >= 1000000){
F = F/1000000;
dtostrf(F, 3, 2, freq);
lcdsetcursor(1,2);
lcdprint("F:");
lcdprint(freq);
lcdprint(" MHz");
T = 1000000*T;
dtostrf(T, 3, 2, period);
lcdsetcursor(1,2);
lcdprint("T:");
lcdprint(period);
lcdprint(" us");
_delay_ms(10);
}
else{
dtostrf(F, 3, 2, freq);
lcdprint("F:");
lcdprint(freq);
lcdprint(" Hz");
T = T*1000;
dtostrf(T, 4, 2, period);
lcdsetcursor(1,2);
lcdprint("T:");
lcdprint(period);
lcdprint(" ms");
_delay_ms(400);
}
}
return(0);
}
In the above we have used lcd library for the 16x2 LCD which are as follows.
lcd.h
#include <avr/io.h>
#include <util/delay.h>
#define RS (1<<PD2)
#define E (1<<PD3)
#define D4 (1<<PB4)
#define D5 (1<<PB5)
#define D6 (1<<PB6)
#define D7 (1<<PB7)
#define DATAPORT PORTB
#define CTRLPORT PORTD
void lcdinit();
void lcdcmd(char);
void lcdchar(char);
void lcdprint(char *);
void latch(void);
void lcdsetcursor(unsigned char, unsigned char);
void lcdclear();
lcd.c
#include "lcd.h"
void lcdinit(){
//initialize PORTs for LCD
DDRD |= RS | E;
DDRB |= D4 | D5 | D6| D7; //make output for E,RW,RS and 4 bit Data
_delay_ms(20);
CTRLPORT &= ~E; //send low
_delay_ms(20); //delay for stable power
lcdcmd(0x33);
//_delay_us(100);
lcdcmd(0x32);
//_delay_us(100);
lcdcmd(0x28); // 2 lines 5x7 matrix dot
// _delay_us(100);
lcdcmd(0x0C); // display ON, Cursor OFF
// _delay_us(100);
lcdcmd(0x01); //clear LCD
// _delay_us(2000);
lcdcmd(0x06); //shift cursor to right
_delay_us(1000);
}
void lcdcmd(char cmd){
DATAPORT = (DATAPORT & 0x0F) | (cmd & 0xF0); // send high nibble
// PORTD &= ~RW; //send 0 for write operation
CTRLPORT &= ~RS; //send 0 to select command register
CTRLPORT |= E; //send high
_delay_ms(5); //wait
CTRLPORT &= ~E; //send low
_delay_ms(5); //wait
DATAPORT = (DATAPORT & 0x0F) | (cmd<<4); //send low nibble
CTRLPORT |= E; //send high
_delay_ms(5); //wait
CTRLPORT &= ~E; //send low
_delay_ms(5); //wait
}
void lcdchar(char data){
DATAPORT = (DATAPORT & 0x0F) | (data & 0xF0); // send high nibble
// PORTD &= ~RW; //send 0 for write operation
CTRLPORT |= RS; //send 1 to select data register
CTRLPORT |= E; //send high
_delay_ms(5); //wait
CTRLPORT &= ~E; //send low
_delay_ms(5); //wait
DATAPORT = (DATAPORT & 0x0F) | (data<<4); // send low nibble
CTRLPORT |= E; //send high
_delay_ms(5); //wait
CTRLPORT &= ~E; //send low
_delay_ms(5); //wait
}
void lcdprint(char *str){
unsigned char k=0;
while(str[k] != 0){
lcdchar(str[k]);
k++;
}
}
void lcdsetcursor(unsigned char x, unsigned char y){
unsigned char firstcharadr[] = {0x80, 0xC0, 0x94, 0xD4};
lcdcmd(firstcharadr[y-1] + x-1);
_delay_ms(10);
}
void lcdclear(){
lcdcmd(0x01);
_delay_ms(10);
}
In the above program code, first we have included the necessary libraries. One of them is the stdlib.h library which is needed for using dtostr() function later in the program.
#include <avr/io.h>
#include <stdlib.h>
#include <util/delay.h>
#include "lcd.h"
Alias name for the input capture pin ICP1 or PD6 is created using the following statement.
#define icPin (1<<PD6)
In the main() function we setup the direction and internal pull resistor for the input capture pin.
DDRD |= icPin; //make PD7 pin an output for LED
PORTD |= icPin;
Variables required in the program are created.
float t1,t2;
char period[16], freq[16];
float p,F,T;
float Fcpu = 16000000;
And the LCD library is initialized which sets up the pins and LCD functions. Then a message is displayed on the LCD.
lcdinit();
lcdprint("Period Calc..");
Then we setup the Timer 1 in normal mode, no-prescalar, no noise canceller and rising edge by configuring the TCCR1A and TCCR1B registers.
//Timer 1 config: no noise canceller, rising edge, normal mode, no prescalar
TCCR1A = 0b00000000; //COM1A1 COM1A0 COM1B1 COM1B0 FOC1A FOC1B WGM11 WGM10
TCCR1B = 0b01000001; //ICNC1 ICES1 – WGM13 WGM12 CS12 CS11 CS10
In the while() loop we clear the TCNT1 register and then clear the ICF1 flag in the TIFR register.
TCNT1 = 0; //clear TCNT1 register
TIFR = (1<<ICF1); //clear the ICF1
Then we take input capture time stamp and store them in variables. This is done by waiting and first detecting the first rising edge, store the time variable in t1, clearing the ICF1 flag and then waiting and detecting the second consecutive rising edge and store the time variable in t2.
while((TIFR &(1<<ICF1))==0);
t1 = ICR1;
TIFR = (1<<ICF1);
while((TIFR &(1<<ICF1))==0);
t2 = ICR1;
Once we have the time stamps in variables t1 and t2 we can then measure the period, p, and calculate the frequency F and the period T of the external signal.
p = t2 - t1;
F = Fcpu/p;
T = 1/F;
Next before displaying the results we clear the LCD.
lcdclear();
The next if else statements are used to arrange the frequency measured and put them in different frequency ranges for display.
if(F >= 1000){
F = F/1000;
dtostrf(F, 3, 2, freq);
lcdprint("F:");
lcdprint(freq);
lcdprint(" KHz");
T = 1000*T;
dtostrf(T, 3, 2, period);
lcdsetcursor(1,2);
lcdprint("T:");
lcdprint(period);
lcdprint(" ms");
_delay_ms(100);
}
else if(F >= 1000000){
F = F/1000000;
dtostrf(F, 3, 2, freq);
lcdsetcursor(1,2);
lcdprint("F:");
lcdprint(freq);
lcdprint(" MHz");
T = 1000000*T;
dtostrf(T, 3, 2, period);
lcdsetcursor(1,2);
lcdprint("T:");
lcdprint(period);
lcdprint(" us");
_delay_ms(10);
}
else{
dtostrf(F, 3, 2, freq);
lcdprint("F:");
lcdprint(freq);
lcdprint(" Hz");
T = T*1000;
dtostrf(T, 4, 2, period);
lcdsetcursor(1,2);
lcdprint("T:");
lcdprint(period);
lcdprint(" ms");
_delay_ms(400);
}
In this way we can measure the frequency and period of external signal using the input capture feature of ATmega32 and ATmega32A.
See other example of input capture for measuring frequency and period.