Distance Measurement using HC-SR04 and AVR Microcontroller

Published  July 5, 2015   25
User Avatar Dilip Raja
Author
Distance Measurement using Ultrasonic Sensor and AVR Microcontroller

In this tutorial we are going to discuss and design a circuit for measuring distance. This circuit is developed by interfacing ultrasonic sensor“HC-SR04” with AVR microcontroller. This sensor uses a technique called “ECHO” which is something you get when sound reflects back after striking with a surface.

 

We know that sound vibrations can not penetrate through solids. So what happens is, when a source of sound generates vibrations they travel through air at a speed of 220 meters per second. These vibrations when they meet our ear we describe them as sound. As said earlier these vibrations can not go through solid, so when they strike with a surface like wall, they are reflected back at the same speed to the source, which is called echo.

 

Ultrasonic sensor “HC-SR04” provides an output signal proportional to distance based on the echo. The sensor here generates a sound vibration in ultrasonic range upon giving a trigger, after that it waits for the sound vibration to return. Now based on the parameters, sound speed (220m/s) and time taken for the echo to reach the source, it provides output pulse proportional to distance.

Ultrasonic Sensor Timing Diagram

As shown in figure, at first we need to initiate the sensor for measuring distance, that is a HIGH logic signal at trigger pin of sensor for more than 10uS, after that a sound vibration is sent by sensor, after a echo, the sensor provides a signal at the output pin whose width is proportional to distance between source and obstacle.

This distance is calculate as, distance (in cm) = width of pulse output (in uS) / 58.

Here the width of the signal must be taken in multiple of uS(micro second or 10^-6).

 

Components Required

Hardware: ATMEGA32, Power supply (5v), AVR-ISP PROGRAMMER, JHD_162ALCD (16x2LCD), 1000uF capacitor, 10KΩ resistor (2 pieces) , HC-SR04 sensor.

Software: Atmel studio 6.1, progisp or flash magic.

 

Circuit Diagram and Working Explanation

Circuit Diagram for Distance Measurement using Ultrasonic Sensor and AVR

Here we are using PORTB to connect to LCD data port (D0-D7). Anyone who does not want to work with FUSE BITS of ATMEGA32A can not use PORTC, as PORTC contains a special type of communication which can only disabled by changing FUSEBITS.

 

In the circuit, you observe I have only took two control pins, this give the flexibility of better understanding. The contrast bit and READ/WRITE are not often used so they can be shorted to ground. This puts LCD in highest contrast and read mode. We just need to control ENABLE and RS pins to send characters and data accordingly.

 

The connections which are done for LCD are given below:

PIN1 or VSS to ground

PIN2 or VDD or VCC to +5v power

PIN3 or VEE to ground (gives maximum contrast best for a beginner)

PIN4 or RS (Register Selection) to PD6 of uC

PIN5 or RW (Read/Write) to ground (puts LCD in read mode eases the communication for user)

PIN6 or E (Enable) to PD5 of uC

PIN7 or D0 to PB0 of uC

PIN8 or D1 to PB1 of uC

PIN9 or D2 to PB2 of uC

PIN10 or D3 to PB3 of uC

PIN11 or D4 to PB4 of uC

PIN12 or D5 to PB5 of uC

PIN13 or D6 to PB6 of uC

PIN14 or D7 to PB7 of uC

 

In the circuit you can see we have used 8bit communication (D0-D7) however this is not a compulsory and we can use 4bit communication (D4-D7) but with 4 bit communication program becomes a bit complex. So as shown in the above table we are connecting 10 pins of LCD to controller in which 8 pins are data pins and 2 pins for control.

 

The ultrasonic sensor is a four pin device, PIN1- VCC or +5V; PIN2-TRIGGER; PIN3- ECHO; PIN4- GROUND. Trigger pin is where we give trigger to tell the sensor to measure the distance. Echo is output pin where we get the distance in the form of width of pulse. The echo pin here is connected to controller as an external interrupt source. So to get the width of the signal output, the echo pin of sensor is connected to INT0 (interrupt 0) or PD2.

 

Now for getting the distance we have to program the controller for the following:

1. Triggering the sensor by pulling up the trigger pin for atleast 12uS.

2. Once echo goes high we get an external interrupt and we are going to start a counter (enabling a counter) in the ISR (Interrupt Service Routine) which is executed right after an interrupt triggered.

3. Once echo goes low again an interrupt is generated, this time we are going to stop the counter (disabling the counter).

4. So for a pulse high to low at echo pin, we have started a counter and stopped it. This count is updated to memory for getting the distance, as we have the width of echo in count now.

5. We are going to do further calculations in the memory to get the distance in cm

6. The distance is displayed on 16x2 LCD display.

 

For setting up the above features we are going to set the following registers:

MCU Control Register

General Interrupt Control Register

Timer Counter Control Register

The above three registers are to be set accordingly for the setup to work and we are going to discuss them briefly,

BLUE (INT0): this bit must be set high to enable the external interrupt0, once this pin is set we get to sense the logic changes at the PIND2 pin.

BROWN (ISC00, ISC01): these two bits are adjusted for the appropriate logic change at PD2, which to be considered as interrupt.

Interrupt 0 Sense Control

So as said earlier we need an interrupt to start a count and to stop it. So we set ISC00 as one and we get an interrupt when there is a logic LOW to HIGH at INT0 ; another interrupt when there is a logic HIGH to LOW.

RED(CS10): This bit is simply to enable and to disable counter. Although it works along with other bits CS10, CS12. We are not doing any prescaling here, so we need not worry about them.

 

Some important things to remember here are:

We are using internal clock of ATMEGA32A which is 1MHz. No prescaling here, we are not doing compare match interrupt generate routine ,so no complex register settings.

The count value after counting is stored in 16bit TCNT1 register. 

Also check this project with arduino: Distance measurement using Arduino

 

Programming Explanation

Working of Distance Measurement sensor is explained step by step in the below C program.

#include <avr/io.h>

//header to enable data flow control over pins

#define F_CPU 1000000      

//telling controller crystal frequency attached

#include <util/delay.h>

//header to enable delay function in program

#define    E   5

//giving name “enable”  to 5th pin of PORTD, since it Is connected to LCD enable pin

#define RS  6

//giving name “registerselection” to 6th pin of PORTD, since is connected to LCD RS pin

void send_a_command(unsigned char command);

void send_a_character(unsigned char character);

void send_a_string(char *string_of_characters);    

static volatile int pulse = 0;//interger  to access all though the program

static volatile int i = 0;// interger  to access all though the program

int main(void)

{

DDRB = 0xFF;

//putting portB output pins

DDRD = 00b11111011;

_delay_ms(50);//giving delay of 50ms

DDRA = 00FF;//Taking portA as output.

GICR|=(1<<INT0);//enabling interrupt0

MCUCR|=(1<<ISC00);//setting interrupt triggering logic change

int16_t COUNTA = 0;//storing digital output

char SHOWA [3];//displaying digital output as temperature in 16*2 lcd

send_a_command(0x01); //Clear Screen 0x01 = 00000001

_delay_ms(50);

send_a_command(0x38);//telling lcd we are using 8bit command /data mode

_delay_ms(50);

send_a_command(0b00001111);//LCD SCREEN ON and courser blinking

sei();// enabling global interrupts

while(1)

{

PORTD|=(1<<PIND0);

_delay_us(15);///triggering the sensor for 15usec

PORTD &=~(1<<PIND0);

COUNTA = pulse/58;//getting the distance based on formula on introduction

send_a_string ("CIRCUIT DIGEST ");//displaying name

send_a_command(0x80 + 0x40 + 0); // shifting cursor  to 1st  shell  of second line

send_a_string ("DISTANCE=");// displaying name

itoa(COUNTA,SHOWA,10); //command for putting variable number in LCD(variable number, in which character to replace, which base is variable(ten here as we are counting number in base10))

send_a_string(SHOWA); //telling the display to show character(replaced by variable number) after positioning the courser on LCD

send_a_string ("cm       ");

send_a_command(0x80 + 0); //retuning to first line first shell

}

}

ISR(INT0_vect)//interrupt service routine when there is a change in logic level

{

if (i==1)//when logic from HIGH to LOW

{

TCCR1B=0;//disabling counter

pulse=TCNT1;//count memory is updated to integer

TCNT1=0;//resetting the counter memory

i=0;

}

if (i==0)//when logic change from LOW to HIGH

{

TCCR1B|=(1<<CS10);//enabling counter

i=1;

}

}

void send_a_command(unsigned char command)

{

PORTA = command;

PORTD &= ~ (1<<RS); //putting 0 in RS to tell lcd we are sending command

PORTD |= 1<<E; //telling lcd to receive command /data at the port

_delay_ms(50);

PORTD &= ~1<<E;//telling lcd we completed sending data

PORTA= 0;

}

void send_a_character(unsigned char character)

{

PORTA= character;

PORTD |= 1<<RS;//telling LCD we are sending data not commands

PORTD |= 1<<E;//telling LCD to start receiving command/data

_delay_ms(50);

PORTD &= ~1<<E;//telling lcd we completed sending data/command

PORTA = 0;

}

void send_a_string(char *string_of_characters)

{

while(*string_of_characters > 0)

{

send_a_character(*string_of_characters++);

}

}
Code

/*
C Program for Distance Measurement using Ultrasonic Sensor and AVR Microocntroller
 */ 

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000
#include <util/delay.h>
#include <stdlib.h>

#define enable            5
#define registerselection 6

void send_a_command(unsigned char command);
void send_a_character(unsigned char character);
void send_a_string(char *string_of_characters);

static volatile int pulse = 0;
static volatile int i = 0;

int main(void)
{
    DDRA = 0xFF;
    DDRB = 0xFF;
    DDRD = 0b11111011;
    _delay_ms(50);
    
    GICR|=(1<<INT0);
    MCUCR|=(1<<ISC00);
    
    TCCR1A = 0;
    
    int16_t COUNTA = 0;
    char SHOWA [16];
    

    send_a_command(0x01); //Clear Screen 0x01 = 00000001
    _delay_ms(50);
    send_a_command(0x38);
    _delay_ms(50);
    send_a_command(0b00001111);
    _delay_ms(50);
    
    sei();
    
    while(1)
    {
        PORTD|=(1<<PIND0);
        _delay_us(15);
        PORTD &=~(1<<PIND0);
        
        COUNTA = pulse/58;
        send_a_string ("CIRCUIT DIGEST");
        send_a_command(0x80 + 0x40 + 0);
        send_a_string ("DISTANCE=");
        itoa(COUNTA,SHOWA,10);
        send_a_string(SHOWA);
        send_a_string ("cm    ");
        send_a_command(0x80 + 0);

    }
}

ISR(INT0_vect)
{
    if (i==1)
    {
        TCCR1B=0;
        pulse=TCNT1;
        TCNT1=0;
        i=0;
    }
    if (i==0)
    {
        TCCR1B|=(1<<CS10);
        i=1;
    }
}

void send_a_command(unsigned char command)
{
    PORTB = command;
    PORTD &= ~ (1<<registerselection);
    PORTD |= 1<<enable;
    _delay_ms(8);
    PORTD &= ~1<<enable;
    PORTB = 0;
}

void send_a_character(unsigned char character)
{
    PORTB = character;
    PORTD |= 1<<registerselection;
    PORTD |= 1<<enable;
    _delay_ms(8);
    PORTD &= ~1<<enable;
    PORTB = 0;
}
void send_a_string(char *string_of_characters)
{
    while(*string_of_characters > 0)
    {
        send_a_character(*string_of_characters++);
    }
}

Video

Have any question realated to this Article?

Ask Our Community Members

Comments

If you change the fuse bits of ATMEGA32 already than change the term " #define F_CPU 1000000" to " #define F_CPU 8000000". If you haven't changed the fuse bits, not matter what crystal you attach to the ATMEGA8 , it will work on internal 1MHz crystal.

Submitted by mail on Mon, 04/18/2016 - 20:17

Permalink

Hi!
i am trying to do somithing like your project but i can´t understand this comand "PORTD &=~(1<<PIND0)", why the "~" and why do you use the (1<<PIND0)" isn´t that going to move what is inside PIND0 to the left?? like 00000010 to 00000100
thaks!

This is a trick to set a specific bit to 0. The ~ inverts the binary data. You shift a 1 to the left to the position you want (say 00000100 for the third bit). Then you invert the data (11111011) and then you use this data as mask with the & operator on the PORTD register. This will set all the bits to their original value except the third bit, which will always be 0 (BIT & 0 => 0).

Submitted by Jch on Thu, 04/21/2016 - 14:27

Permalink

Hi,
thank you for the great code, but why don't you use the Input Capturing Pin PD6?

Submitted by Miroslav on Mon, 06/27/2016 - 18:02

Permalink

Hello Dilip, You have done excellent job! Simply your article is containing everything: showing the circuit diagram, source code explanation. I built your project in 10 minutes and it was working smoothly, no errors, no mistakes! Thank you very much, keep going with publishing this way! :o) Miroslav (Czech Republic, Europe)

Submitted by kim on Sun, 12/18/2016 - 21:45

Permalink

'DDRA ' and 'GICR' was not declared in this scope, having this problem during compiling the code.

Submitted by Ana on Tue, 01/10/2017 - 13:42

Permalink

Thank you for the great work on this tutorial. I am however confused by the use of the 1000uF capacitor...I am afraid I do not understand what it is used for from the circuit diagram. Can you please shed some light on this? Thank you in advance.

Submitted by vinutp on Wed, 02/01/2017 - 12:33

Permalink

hi all ,when i compiled this programme GICR undeclared error occurred pls help me
how can i solve this declaration in interrupt lib

Submitted by Absar Ali on Thu, 04/13/2017 - 18:02

Permalink

what is the work of master slave comunication ...
does it wil work if i simply do not atteche the programer port?

Submitted by Prezzie on Thu, 09/28/2017 - 11:15

Permalink

Dilip,
Thanks for a kind explanation.
The speed of a sound appears 340 meter per sec I guess?
For better accuracy,
COUNTA = pulse/58; may be rewritten to COUNTA = pulse/59?

Cheers,

Submitted by Mike on Tue, 03/13/2018 - 15:44

Permalink

In your code you use INT0 pin ,this pin is used for the 8 bit counter TCNT0
Then you use TNTC1 the 16 bit counter to count the pulses
What part of the code connects TCNT1 to the internal oscillator to count?

Please explain how INT0 pin can make TCNT1 count.

Thanks.

Submitted by Bernie Cabero on Sat, 05/05/2018 - 16:58

Permalink

Sir i have a project distance measurement using mcu168.. Can i have the complete code without using arduino..Thanks in advance

Submitted by Peter on Sun, 07/29/2018 - 15:22

Permalink

Hi all

Works fine and helped a lot to get it done faster. However I'm wondering weather the ISR is correct. I've the impression that the if(i==0) should rather be an else clause, otherwise the statement is always true and executed, or not?

Have a great Sunday
Peter

ISR(INT0_vect)
{
if (i==1)
{
TCCR1B=0;
pulse=TCNT1;
TCNT1=0;
i=0;
}
if (i==0) // due to the "i=0" statement above the if statement is always true
{
TCCR1B|=(1<<CS10);
i=1;
}
}

Hi all,

Is there any possible way for this project to connect with buzzer. I have a project using the distance sensor to control the audio tone.

What code should I implement into this code in order to allow it to control buzzer?

My project is about if the distance between distance sensor and object is lesser from certain distance, the audio tone is lower. When the distance is further, the audio tone is louder.