Interfacing Ultrasonic Sensor HC-SR04 with PIC Microcontroller

For any project to come alive, we need to use sensors. Sensors acts as the eyes and ears for all embedded application, it helps the digital Microcontroller to understand what is actually happening in this real Analog world. In this tutorial we will be learning how to Interface Ultrasonic Sensor HC-SR04 with PIC microcontroller. The HC-SR04 is an ultrasonic sensor which can be used to measure distance anywhere between 2cm to 450cm (theoretically). This sensor has proved itself worthy by fitting into many projects which involves obstacles detection, distance measuring, environment mapping etc. At the end of this article you will learn how this sensor works and how to interface it with PIC16F877A microcontroller to measure the distance and display it on the LCD screen. Sounds interesting right!! So let’s get started…

Materials Required:

1. PIC16F877A MCU with programming set-up
2. LCD 16*2 display
3. Ultrasonic sensor (HC-SR04)
4. Connecting wires

How does an Ultrasonic Sensor work?

Before we get any further, we should know how an Ultrasonic sensor works so that we can understand this tutorial much better. The ultrasonic sensor used in this project is shown below. As you can see it has two circular eyes like projections and four pins coming out of it. The two eye like projections are the Ultrasonic wave (hereafter referred as US wave) Transmitter and receiver. The transmitter emits an US wave at a frequency of 40Hz, this wave travels through the air and gets reflected back when it senses an object. The returning waves are observed by the receiver. Now we know the time taken for this wave to get reflected and come back and the speed of the US wave is also universal (3400cm/s). Using this information and the below high school formulae we can calculate the distance covered.

Distance = Speed × Time

Now that we know how an US sensor works, let us how it can be interfaced with any MCU/CPU using the four pins. These four pins are Vcc, Trigger, Echo and Ground respectively. The module works on +5V and hence the Vcc and ground pin is used to power the module. The other two pins are the I/O pins using which we communicate to our MCU. The trigger pin should be declared as an output pin and made high for a 10uS, this will transmit the US wave into the air as 8 cycle sonic burst. Once the wave is observed the Echo pin will go high for the exact interval of time which was taken by the US wave to return back to the sensor module. Hence this Echo pin will be declared as inputand a timer will be used to measure how long the pin was high. This could further be understood by the timing diagram below. Circuit Diagram:

The complete circuit diagram for interfacing Ultrasonic Sensor with PIC16F877A is shown below: As shown, the circuit involves nothing more than a LCD display and the Ultrasonic sensor itself. The US sensor can be powered by +5V and hence it is directly powered by the 7805 voltage regulator. The sensor has one output pin (Trigger pin) which is connected to pin 34 (RB1) and the input pin (Echo pin) is connected to pin 35 (RB2). The complete pin connection is illustrated in the table below.

READ  PIC microcontroller ATA library
 S.No: PIC Pin Number Pin Name Connected to 1 21 RD2 RS of LCD 2 22 RD3 E of LCD 3 27 RD4 D4 of LCD 4 28 RD5 D5 of LCD 5 29 RD6 D6 of LCD 6 30 RD7 D7 of LCD 7 34 RB1 Trigger of US 8 35 RB2 Echo of US

Programming your PIC Microcontroller:

The complete program for this tutorial is given at the end of this page, further below I have explained the code into small meaning full chunks for you to understand. As said earlier the program involves the concept of LCD interfacing and Timer which will not explained in details in this tutorial since we have already covered them in the previous tutorials.

Inside, the main function we start with initializing the IO pins and other registers as usual. We define the IO pins for LCD and US sensor and also initiate the Timer 1 register by setting it to work on 1:4 pre-scalar and to use internal clock (Fosc/4)

TRISD = 0x00; //PORTD declared as output for interfacing LCD
TRISB0 = 1;        //Define the RB0 pin as input to use as interrupt pin
TRISB1 = 0; //Trigger pin of US sensor is sent as output pin
TRISB2 = 1; //Echo pin of US sensor is set as input pin
TRISB3 = 0; //RB3 is output pin for LED
T1CON=0x20; //4 pres-scalar and internal clock

The Timer 1 is a 16-bit timer used in PIC16F877A, the T1CON register control the parameters of the timer module and the result will be stored in TMR1H and TMR1L since it a 16-bit result the first 8 will be stored in TMR1H and the next 8 in TMR1L. This timer can be turned on or off using TMR1ON=0 and TMR1ON=1 respectively.

Now, the timer is ready to use, but we have to send the US waves out of the sensor, to do this we have to keep the Trigger pin high for 10uS, this is done by the following code.

Trigger = 1;
__delay_us(10);
Trigger = 0;

As shown in timing diagram above, the Echo pin will stay low till the wave return back and will then go high and stay high for the exact time taken for the waves to return back. This time has to be measured by the Timer 1 module, which can be done by the below line

while (Echo==0);
TMR1ON = 1;
while (Echo==1);
TMR1ON = 0;

Once the time is measured the resulting value will be saved in the registers TMR1H and TMR1L, these registers have to be clubbed to gather to get the 16-bit value. This is done by using the line below

time_taken = (TMR1L | (TMR1H<<8));

This time_taken will be in form bytes, to get the actual time value we have to use the below formula.

Time = (16-bit register value) * (1/Internal Clock) * (Pre-scale)
Internal Clock = Fosc/4

Where in our case,
Fosc = 20000000Mhz and Pre-scale = 4

Hence the value of Internal Clock will be 5000000Mhz and the value of time will be

Time = (16-bit register value) * (1/5000000) * (4)
= (16-bit register value) * (4/5000000)
= (16-bit register value) * 0.0000008 seconds (OR)
Time = (16-bit register value) * 0.8 micro seconds

In our program the value of the 16-bit register is stored in the variable time_taken and hence the below line is used to calculate the time_taken in micro seconds

time_taken = time_taken * 0.8;

Next we have to find how to calculate the distance. As we know distance = speed * time. But here the result should be divided by 2 since the wave is covering both the transmitting distance and receiving distance. The speed of us wave (sound) is 34000cm/s.

Distance = (Speed*Time)/2
= (34000 * (16-bit register value) * 0.0000008) /2
Distance = (0.0272 * 16-bit register value)/2

So the distance can be calculated in centimeters like below:

distance= (0.0272*time_taken)/2;

After calculating the value of distance and time taken we simply have to display them on the LCD screen.

Measuring distance using PIC and Ultrasonic Sensor:

After making the connections and uploading the code, your experimental set-up should look something like this shown in the below picture. Now place an object before the sensor and it should display how far the object is from the sensor. You can also notice the time taken being displayed in micro seconds for the wave to transmit and return back.

You can move the object at your preferred distance and check the value that is displayed on the LCD. I was able to measure distance from 2cm to 350cm with an accuracy of 0.5cm. This is quite a satisfactory result! Hope you enjoyed the tutorial and learnt how to make something on your own. If you have any doubts drop them in the comment section below or use the forums.

Code

/*
Interfacing Ultrasonic sensor with PIC16F877A
* Code by: B.Aswinth Raj
* Dated: 19-07-2017
* More details at: www.CircuitDigest.com
*/

#define _XTAL_FREQ 20000000
#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7
#define Trigger RB1 //34 is Trigger
#define Echo RB2//35 is Echo
#include <xc.h>

#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

//LCD Functions Developed by Circuit Digest.
void Lcd_SetBit(char data_bit) //Based on the Hex value Set the Bits of the Data Lines
{
if(data_bit& 1)
D4 = 1;
else
D4 = 0;

if(data_bit& 2)
D5 = 1;
else
D5 = 0;

if(data_bit& 4)
D6 = 1;
else
D6 = 0;

if(data_bit& 8)
D7 = 1;
else
D7 = 0;
}

void Lcd_Cmd(char a)
{
RS = 0;
Lcd_SetBit(a); //Incoming Hex value
EN  = 1;
__delay_ms(4);
EN  = 0;
}

void Lcd_Clear()
{
Lcd_Cmd(0); //Clear the LCD
Lcd_Cmd(1); //Move the curser to first position
}

void Lcd_Set_Cursor(char a, char b)
{
char temp,z,y;
if(a== 1)
{
temp = 0x80 + b – 1; //80H is used to move the curser
z = temp>>4; //Lower 8-bits
y = temp & 0x0F; //Upper 8-bits
Lcd_Cmd(z); //Set Row
Lcd_Cmd(y); //Set Column
}
else if(a== 2)
{
temp = 0xC0 + b – 1;
z = temp>>4; //Lower 8-bits
y = temp & 0x0F; //Upper 8-bits
Lcd_Cmd(z); //Set Row
Lcd_Cmd(y); //Set Column
}
}

void Lcd_Start()
{
Lcd_SetBit(0x00);
for(int i=1065244; i<=0; i–)  NOP();
Lcd_Cmd(0x03);
__delay_ms(5);
Lcd_Cmd(0x03);
__delay_ms(11);
Lcd_Cmd(0x03);
Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD
Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD
Lcd_Cmd(0x08); //Select Row 1
Lcd_Cmd(0x00); //Clear Row 1 Display
Lcd_Cmd(0x0C); //Select Row 2
Lcd_Cmd(0x00); //Clear Row 2 Display
Lcd_Cmd(0x06);
}

void Lcd_Print_Char(char data)  //Send 8-bits through 4-bit mode
{
char Lower_Nibble,Upper_Nibble;
Lower_Nibble = data&0x0F;
Upper_Nibble = data&0xF0;
RS = 1;             // => RS = 1
Lcd_SetBit(Upper_Nibble>>4);             //Send upper half by shifting by 4
EN = 1;
for(int i=2130483; i<=0; i–)  NOP();
EN = 0;
Lcd_SetBit(Lower_Nibble); //Send Lower half
EN = 1;
for(int i=2130483; i<=0; i–)  NOP();
EN = 0;
}

void Lcd_Print_String(char *a)
{
int i;
for(i=0;a[i]!=’\0′;i++)
Lcd_Print_Char(a[i]);  //Split the string using pointers and call the Char function
}
/*****End of LCD Functions*****/

int time_taken;
int distance;
char t1,t2,t3,t4,t5;
char d1,d2,d3;

int main()
{
TRISD = 0x00; //PORTD declared as output for interfacing LCD
TRISB0 = 1;        //DEfine the RB0 pin as input to use as interrupt pin
TRISB1 = 0; //Trigger pin of US sensor is sent as output pin
TRISB2 = 1; //Echo pin of US sensor is set as input pin
TRISB3 = 0; //RB3 is output pin for LED

T1CON=0x20;

Lcd_Start();

Lcd_Set_Cursor(1,1);
Lcd_Print_String(“Ultrasonic sensor”);
Lcd_Set_Cursor(2,1);
Lcd_Print_String(“with PIC16F877A”);

__delay_ms(2000);
Lcd_Clear();

while(1)
{
TMR1H =0; TMR1L =0; //clear the timer bits

Trigger = 1;
__delay_us(10);
Trigger = 0;

while (Echo==0);
TMR1ON = 1;
while (Echo==1);
TMR1ON = 0;

time_taken = (TMR1L | (TMR1H<<8));
distance= (0.0272*time_taken)/2;

time_taken = time_taken * 0.8;

t1 = (time_taken/1000)%10;
t2 = (time_taken/1000)%10;
t3 = (time_taken/100)%10;
t4 = (time_taken/10)%10;
t5 = (time_taken/1)%10;
d1 = (distance/100)%10;
d2 = (distance/10)%10;
d3 = (distance/1)%10;

Lcd_Set_Cursor(1,1);
Lcd_Print_String(“Time_taken:”);
Lcd_Print_Char(t1+’0′);
Lcd_Print_Char(t2+’0′);
Lcd_Print_Char(t3+’0′);
Lcd_Print_Char(t4+’0′);
Lcd_Print_Char(t5+’0′);

Lcd_Set_Cursor(2,1);
Lcd_Print_String(“distance:”);
Lcd_Print_Char(d1+’0′);
Lcd_Print_Char(d2+’0′);
Lcd_Print_Char(d3+’0′);
}
return 0;
}

Current Project / Post can also be found using:

• lcd gsm and flow sensor((lquid) interface with pic 16f877 microcontroler 