Interfacing HC-SR04 Ultrasonic Sensor with PIC Microcontroller

Interfacing HC-SR04 Ultrasonic Sensor with PIC Microcontroller

HC-SR04 Ultrasonic Sensor

HC-SR04 Ultrasonic Distance Sensor is a popular and low cost solution for non-contact distance measurement function. It is able to measure distances from 2cm to 400cm with an accuracy of about 3mm. This module includes ultrasonic transmitter, ultrasonic receiver and its control circuit.

HC-SR04 module has 4 pins :

  • VCC – 5V, +ive of the power supply
  • TRIG – Trigger Pin
  • ECHO – Echo Pin
  • GND – -ive of the power supply

TRIG and ECHO pins can be used to interface this module with a microcontroller unit. These are TTL (0 – 5V) input output pins.


HC-SR04 Ultrasonic Module Working

Ultrasonic Module Operation

Ultrasonic Module Operation

  1. Provide TRIGGER signal, atleast 10μS High Level (5V) pulse.
  2. The module will automatically transmit eight 40KHz ultrasonic burst.
  3. If there is an obstacle in-front of the module, it will reflect the ultrasonic burst.
  4. If the signal is back, ECHO output of the sensor will be in HIGH state (5V) for a duration of time taken for sending and receiving ultrasonic burst. Pulse width ranges from about 150μS to 25mS and if no obstacle is detected, the echo pulse width will be about 38ms.
Working of HC-SR04 Ultrasonic Sensor

Working of HC-SR04 Ultrasonic Sensor

Interfacing with PIC Microcontroller

Circuit Diagram

Interfacing HC-SR04 Ultrasonic Distance Sensor with PIC Microcontroller

Interfacing HC-SR04 Ultrasonic Distance Sensor with PIC Microcontroller

PIC 16F877A is the heart of this circuit. VDD and VSS of PIC Microcontroller is connected to +5V and GND respectively which will provide necessary power for its operation. A 8MHz crystal is connected to OSC1 and OSC2 pins of PIC, to provide clock for its operation. 22pF capacitors connected along with the crystal will stabilize the oscillations generated by the crystal. 16×2 LCD is connected to PORTD which is interfaced using 4 bit mode communication. 10KΩ preset is used to adjust the contrast of the LCD. A 100Ω resistor is used to limit current through the LCD back-light LED.

TRIGGER pin of HC-SR04 sensor is connected to RB0 (pin 33) of PIC which is to be configured as an Output PIN (TRIS bit is 0) and ECHO pin is connected to RB4 (pin 37) which is to be configured as an Input PIN (TRIS bit is 1).

Programming

Basic Steps :

  1. Provide TRIGGER to ultrasonic module
  2. Listen for Echo
  3. Start Timer when ECHO HIGH is received
  4. Stop Timer when ECHO goes LOW
  5. Read Timer Value
  6. Convert it to Distance
  7. Display it

Timer1 Module

Timer1 Module can be used as a 16 bit counter or timer. It consists of two 8 bit registers TMR1H and TMR1L which are readable and writable. The register pair, TMR1H:TMR1L increments from 0000H to FFFFH and rolls over to 0000H. If enabled Timer1 Overflow Interrupt is generated during rolls over to 0000H. Here we will use this module as a 16 bit Timer.

Timer1 Control Register

Timer1 Control Register

Timer1 Module Block Diagram

Timer1 Module Block Diagram

Since we are using Timer1 module as a Timer, we should use internal clock (Fosc/4), ie TMR1CS = 0. Prescaler is used to divide the internal clock (Fosc/4). Here we can set Prescaler as 2, ie T1CKPS1 = 0 & T1CKPS0 = 1. T1SYNC bit is ignored when TMR1CS = 0. As we are using internal clock (Fosc/4) we can disable oscillator, ie T1OSEN = 0. TMR1ON bit can be used to ON or OFF timer as per our requirements.



  • Thus we can initialize timer as : T1CON = 0x10
  • To TURN ON the Timer : T1CON.F0 = 1 or TMR1ON = 1
  • To TURN OFF the Timer : T1CON.F0 = 0 or TMR1ON = 0

Fosc is the oscillator frequency, here we are using 8MHz crystal hence Fosc = 8MHz.

Time = (TMR1H:TMR1L)*(1/Internal Clock)*Prescaler

Internal Clock = Fosc/4 = 8MHz/4 = 2MHz

Therefore, Time = (TMR1H:TMR1L)*2/(2000000) = (TMR1H:TMR1L)/1000000



Distance Calculation

  • Distance = Speed * Time
  • Let d be the distance between Ultrasonic Sensor and Target
  • Total distance traveled by the ultrasonic burst : 2d (forward and backward)
  • Speed of Sound in Air : 340 m/s = 34000 cm/s
  • Thus, d = (34000*Time)/2, where Time = (TMR1H:TMR1L)/(1000000)
  • Therefore, d = (TMR1H:TMR1L)/58.82 cm
  • TMR1H:TMR1L = TMR1L | (TMR1H<<8)

Simple Method – MikroC & MPLAB XC8 Program

MikroC Code

// LCD module connections
sbit LCD_RS at RD2_bit;
sbit LCD_EN at RD3_bit;
sbit LCD_D4 at RD4_bit;
sbit LCD_D5 at RD5_bit;
sbit LCD_D6 at RD6_bit;
sbit LCD_D7 at RD7_bit;

sbit LCD_RS_Direction at TRISD2_bit;
sbit LCD_EN_Direction at TRISD3_bit;
sbit LCD_D4_Direction at TRISD4_bit;
sbit LCD_D5_Direction at TRISD5_bit;
sbit LCD_D6_Direction at TRISD6_bit;
sbit LCD_D7_Direction at TRISD7_bit;
// End LCD module connections

void main()
{
  int a;
  char txt[7];
  Lcd_Init();
  Lcd_Cmd(_LCD_CLEAR);          // Clear display
  Lcd_Cmd(_LCD_CURSOR_OFF);     // Cursor off

  TRISB = 0b00010000;           //RB4 as Input PIN (ECHO)

  Lcd_Out(1,1,"Developed By");
  Lcd_Out(2,1,"electroSome");

  Delay_ms(3000);
  Lcd_Cmd(_LCD_CLEAR);

  T1CON = 0x10;                 //Initialize Timer Module

  while(1)
  {
    TMR1H = 0;                  //Sets the Initial Value of Timer
    TMR1L = 0;                  //Sets the Initial Value of Timer

    PORTB.F0 = 1;               //TRIGGER HIGH
    Delay_us(10);               //10uS Delay
    PORTB.F0 = 0;               //TRIGGER LOW

    while(!PORTB.F4);           //Waiting for Echo
    T1CON.F0 = 1;               //Timer Starts
    while(PORTB.F4);            //Waiting for Echo goes LOW
    T1CON.F0 = 0;               //Timer Stops

    a = (TMR1L | (TMR1H<<8));   //Reads Timer Value
    a = a/58.82;                //Converts Time to Distance
    a = a + 1;                  //Distance Calibration\
    if(a>=2 && a<=400)          //Check whether the result is valid or not
    {
      IntToStr(a,txt);
      Ltrim(txt);
      Lcd_Cmd(_LCD_CLEAR);
      Lcd_Out(1,1,"Distance = ");
      Lcd_Out(1,12,txt);
      Lcd_Out(1,15,"cm");
    }
    else
    {
      Lcd_Cmd(_LCD_CLEAR);
      Lcd_Out(1,1,"Out of Range");
    }
    Delay_ms(400);
  }
}

MPLAB XC8 Code

#define _XTAL_FREQ 8000000

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7

#include <xc.h>
#include "lcd.h";
#include <pic16f877a.h>

// BEGIN CONFIG
#pragma config FOSC = HS   
#pragma config WDTE = OFF  
#pragma config PWRTE = OFF 
#pragma config BOREN = ON 
#pragma config LVP = OFF   
#pragma config CPD = OFF   
#pragma config WRT = OFF  
#pragma config CP = OFF    
//END CONFIG
void main()
{ 
  int a;

  TRISB = 0b00010000;         //RB4 as Input PIN (ECHO)
  TRISD = 0x00;               // LCD Pins as Output

  Lcd_Init();

  Lcd_Set_Cursor(1,1);
  Lcd_Write_String("Developed By");
  Lcd_Set_Cursor(2,1);
  Lcd_Write_String("electroSome");

  __delay_ms(3000);
  Lcd_Clear();

  T1CON = 0x10;               //Initialize Timer Module

  while(1)
  { 
    TMR1H = 0;                //Sets the Initial Value of Timer
    TMR1L = 0;                //Sets the Initial Value of Timer

    RB0 = 1;                  //TRIGGER HIGH
    __delay_us(10);           //10uS Delay 
    RB0 = 0;                  //TRIGGER LOW

    while(!RB4);              //Waiting for Echo
    TMR1ON = 1;               //Timer Starts
    while(RB4);               //Waiting for Echo goes LOW
    TMR1ON = 0;               //Timer Stops

    a = (TMR1L | (TMR1H<<8)); //Reads Timer Value
    a = a/58.82;              //Converts Time to Distance
    a = a + 1;                //Distance Calibration
    if(a>=2 && a<=400)        //Check whether the result is valid or not
    { 
      Lcd_Clear();
      Lcd_Set_Cursor(1,1);
      Lcd_Write_String("Distance = ");

      Lcd_Set_Cursor(1,14);
      Lcd_Write_Char(a%10 + 48);

      a = a/10;
      Lcd_Set_Cursor(1,13);
      Lcd_Write_Char(a%10 + 48);

      a = a/10;
      Lcd_Set_Cursor(1,12);
      Lcd_Write_Char(a%10 + 48);

      Lcd_Set_Cursor(1,15);
      Lcd_Write_String("cm");
    }  
    else
    {
      Lcd_Clear();
      Lcd_Set_Cursor(1,1);
      Lcd_Write_String("Out of Range");
    }
    __delay_ms(400);
  }
}

This program uses our LCD Library for MPLAB XC8. You need to download and include the header file “lcd.h”. For more information please read the tutorial, Interfacing LCD with PIC Microcontroller – MPLAB XC8.

I hope that you can understand the working of the above program through comments. If you have any doubts, just comment below.

Distance Calibration

For accurate distance measuring you may calibrate the obtained result. Here for making the displayed distance more accurate, I added 1 to the the measured distance. This constant of calibration can be find using a series of practical experiments with a ruler scale.

In the above program there is a small problem as it infinitely waiting for ECHO and to ECHO goes LOW. If HIGH echo pulse is not received due to any reason, the program may get stuck there. So it is not a good practice to infinitely wait for ECHO and to ECHO goes LOW.  The best solution is to use PORTB On-Change Interrupt feature of PIC Microcontroller.

PORTB On-Change Interrupt

Four pins of PORTB (RB4 – RB7) have interrupt on-change feature. Only those pins configured as INPUT PIN can cause this Interrupt while this interrupt is enabled. If the Logic State of any of these four pin changes (only Input Pins), interrupt will be generated.

PORTB On-Change Interrupt can be enabled by :

  • Set Global Interrupt Enable bit : INTCON.GIE = 1
  • Set PORTB On-Change Interrupt Enable Bit : INTCON.RBIE = 1

Note : PORTB On-Change Interrupt flag (INTCON.RBIF) will be set whenever this interrupt is generated and it should be cleared in software

Using Interrupt – MikroC & MPLAB XC8 Program

MikroC Code

// LCD module connections
sbit LCD_RS at RD2_bit;
sbit LCD_EN at RD3_bit;
sbit LCD_D4 at RD4_bit;
sbit LCD_D5 at RD5_bit;
sbit LCD_D6 at RD6_bit;
sbit LCD_D7 at RD7_bit;

sbit LCD_RS_Direction at TRISD2_bit;
sbit LCD_EN_Direction at TRISD3_bit;
sbit LCD_D4_Direction at TRISD4_bit;
sbit LCD_D5_Direction at TRISD5_bit;
sbit LCD_D6_Direction at TRISD6_bit;
sbit LCD_D7_Direction at TRISD7_bit;
// End LCD module connections

int a;

//Interrupt function will be automatically executed on Interrupt
void interrupt() 
{
  if(INTCON.RBIF == 1)                 //Makes sure that it is PORTB On-Change Interrupt
  {
    INTCON.RBIE = 0;                   //Disable On-Change Interrupt
    if(PORTB.F4 == 1)                  //If ECHO is HIGH
      T1CON.F0 = 1;                    //Start Timer
    if(PORTB.F4 == 0)                  //If ECHO is LOW
    {
      T1CON.F0 = 0;                    //Stop Timer
      a = (TMR1L | (TMR1H<<8))/58.82;  //Calculate Distance
    }
  }
  INTCON.RBIF = 0;                     //Clear PORTB On-Change Interrupt flag
  INTCON.RBIE = 1;                     //Enable PORTB On-Change Interrupt
}
 
void main()
{
  char txt[7];
  Lcd_Init();
  Lcd_Cmd(_LCD_CLEAR);                 // Clear display
  Lcd_Cmd(_LCD_CURSOR_OFF);            // Cursor off

  TRISB = 0b00010000;
  INTCON.GIE = 1;                      //Global Interrupt Enable
  INTCON.RBIF = 0;                     //Clear PORTB On-Change Interrupt Flag
  INTCON.RBIE = 1;                     //Enable PORTB On-Change Interrupt

  Lcd_Out(1,1,"Developed By");
  Lcd_Out(2,1,"electroSome");

  Delay_ms(3000);
  Lcd_Cmd(_LCD_CLEAR);

  T1CON = 0x10;                        //Initializing Timer Module

  while(1)
  { 
    TMR1H = 0;                         //Setting Initial Value of Timer
    TMR1L = 0;                         //Setting Initial Value of Timer

    a = 0;

    PORTB.F0 = 1;                      //TRIGGER HIGH
    Delay_us(10);                      //10uS Delay
    PORTB.F0 = 0;                      //TRIGGER LOW

    Delay_ms(100);                     //Waiting for ECHO
    a = a + 1;                         //Error Correction Constant
    if(a>2 && a<400)                   //Check whether the result is valid or not
    {
      IntToStr(a,txt);
      Ltrim(txt);
      Lcd_Cmd(_LCD_CLEAR);
      Lcd_Out(1,1,"Distance = ");
      Lcd_Out(1,12,txt);
      Lcd_Out(1,15,"cm");
    }
    else
    {
      Lcd_Cmd(_LCD_CLEAR);
      Lcd_Out(1,1,"Out of Range");
    }
    Delay_ms(400);
  }
}

MPLAB XC8 Code

#define _XTAL_FREQ 8000000

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7

#include <xc.h>
#include "lcd.h";
#include <pic16f877a.h>

// BEGIN CONFIG
#pragma config FOSC = HS 
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config BOREN = ON 
#pragma config LVP = OFF 
#pragma config CPD = OFF 
#pragma config WRT = OFF 
#pragma config CP = OFF
//END CONFIG

int a;

void interrupt echo()
{
  if(RBIF == 1)                       //Makes sure that it is PORTB On-Change Interrupt
  {
    RBIE = 0;                         //Disable On-Change Interrupt
    if(RB4 == 1)                      //If ECHO is HIGH
    TMR1ON = 1;                       //Start Timer
    if(RB4 == 0)                      //If ECHO is LOW
    {
      TMR1ON = 0;                     //Stop Timer
      a = (TMR1L | (TMR1H<<8))/58.82; //Calculate Distance
    }
  }
  RBIF = 0;                           //Clear PORTB On-Change Interrupt flag
  RBIE = 1;                           //Enable PORTB On-Change Interrupt
}
void main()
{
  TRISB = 0b00010000;                 //RB4 as Input PIN (ECHO)
  TRISD = 0x00;                       // LCD Pins as Output
  GIE = 1;                            //Global Interrupt Enable
  RBIF = 0;                           //Clear PORTB On-Change Interrupt Flag
  RBIE = 1;                           //Enable PORTB On-Change Interrupt

  Lcd_Init();

  Lcd_Set_Cursor(1,1);
  Lcd_Write_String("Developed By");
  Lcd_Set_Cursor(2,1);
  Lcd_Write_String("electroSome");

  __delay_ms(3000);
  Lcd_Clear();

  T1CON = 0x10;                       //Initialize Timer Module

  while(1)
  {
    TMR1H = 0;                        //Sets the Initial Value of Timer
    TMR1L = 0;                        //Sets the Initial Value of Timer

    RB0 = 1;                          //TRIGGER HIGH
    __delay_us(10);                   //10uS Delay
    RB0 = 0;                          //TRIGGER LOW 

    __delay_ms(100);                  //Waiting for ECHO
    a = a + 1;                        //Error Correction Constant

    if(a>=2 && a<=400)                //Check whether the result is valid or not
    {
      Lcd_Clear();
      Lcd_Set_Cursor(1,1);
      Lcd_Write_String("Distance = ");

      Lcd_Set_Cursor(1,14);
      Lcd_Write_Char(a%10 + 48);

      a = a/10;
      Lcd_Set_Cursor(1,13);
      Lcd_Write_Char(a%10 + 48);

      a = a/10;
      Lcd_Set_Cursor(1,12);
      Lcd_Write_Char(a%10 + 48);

      Lcd_Set_Cursor(1,15);
      Lcd_Write_String("cm");
    }
    else
    {
      Lcd_Clear();
      Lcd_Set_Cursor(1,1);
      Lcd_Write_String("Out of Range");
    }
    __delay_ms(400);
  }
}

This program uses our LCD Library for MPLAB XC8. You need to download and include the header file “lcd.h”. For more information please read the tutorial, Interfacing LCD with PIC Microcontroller – MPLAB XC8.

Download

You can download entire project files here.

Want to See the Output ?

 

Share this post

Leave a Reply

211 Comments on "Interfacing HC-SR04 Ultrasonic Sensor with PIC Microcontroller"

avatar
  Subscribe  
newest oldest most voted
Notify of
Copper Masud
Guest

TMR1L | (TMR1H<<8)) = time , how ??0

Ligo George
Guest

It is explained in the above article itself..

Copper Masud
Guest

i’m sorry , but i’m totally new with timer1..
if this is the way to get count from 16bit timer register ” TMR1L | (TMR1H<<8) " ,
then why " <<8 " used for , Sir ?

Ligo George
Guest

TMR1L and TMR1H are two 8 bit registers…
To get 16 bit count value like TMR1H:TMR1L we need to shift the value of TMR1H left 8 positions and add with the value of TMR1L.

Copper Masud
Guest

thank you sir , very much

Kumar Saikat Halder
Guest

i need 3 sonar in a project. but i am a novice,and i am not good in timer0 and timer2 module. can u help me pls?

Kumar Saikat Halder
Guest

i didn’t understood the part distance calibration a = a+1; can u help me pls sir?

'Saiful Shukery
Guest

i follow this circuit diagram and also micro c code..but i got black bar on first row on lcd(jhd162a)..how to solve?

Ligo George
Guest

You can use other pins of PORTB. Use portb on change interrupt.

Ligo George
Guest

You can adjust the results obtained from Ultrasonic Sensor with respect to actual desired results just by adding or subtracting a constant.
This gives more accurate results, once it is calibrated.

Ligo George
Guest

Try the following :
1. Adjust the Contrast adjusting preset.
2. Verify all connections to LCD display.

Kamal
Guest

How to interface more ultrasonics with pic

Ligo George
Guest

Use RB5, RB6 and RB7 with PORTB on change interrupt.

memo
Guest

I had a very similar problem. When I connect pin 11 to 5V, I get nothing. When I disconnect it, I get only the bottom line of LCD showing, first line is not coming on. Also it is not showing any measurement either.

memo
Guest

As addition to my below note, I just noticed that on the video pin 11 is not connected and I can’t see any capacitors next to the crystal. Am I right?

Kamal
Guest

okay. First one works well, but second one structs at waiting for echo.

Ponty
Guest

Dear Ligo George

I use the second mikroc code but when I try it my lcd write this: Out of range.
What can i do to solve this problem.
Thanks Andrew

Ligo George
Guest

Try the above mentioned solutions……

Make sure that your LCD is working…. using LCD Hello World example. ..

http://electrosome.com/lcd-pic-interfacing/

Ligo George
Guest

Crystal will work without those capacitors…… using capacitors will improve crystal stability…
PIN 11 is connected.. PINS D0 – D3 (7 – 10) may left unconnected..

Ligo George
Guest

You should use PORTB ON Change interrupt to sense echo.. otherwise it will stuck..

Ligo George
Guest

Makes sure that HC-SR04 Ultrasonic sensor is connected to the prescribed pins of PIC Microcontroller..

Ponty
Guest

I check this a lot of time and it’s good but the problem is consist.

Ligo George
Guest

The above code is 100% working…. Try replacing the HC-SR04 or PIC

Guest

Hey, the first code in isn’t working with my pic …actually the problem is with configuring echo …. and waiting for it . Can you suggest what can be done to make it work proper (the value of RB4 …. i.e., in pic24f i replaced it with LATBbits.LAT4 isn’t changing ) … Thank you 🙂

Ligo George
Guest

The first program will get stuck if there is no obstacle in-front of ultrasonic sensor…. So use the second code.. .
I published the first code just for the sake of explanation…

Guest

the device isn’t detect any object even if it is placed a cm away from it …. it is detecting things which are at a distance less than that …….. does frequency has anything to do with this … ??

Ligo George
Guest

Read the first paragraph of this article…
Distance measurement range : 2cm to 400cm.

sami
Guest

the second mplabxc8 code isn’t working with my pic ..any suggestions?

Ligo George
Guest

Above codes are 100% working.. .it verified in hardware..

joe
Guest

I need asm code for MPLAB IDE

Ligo George
Guest

Sorry we haven’t ASM code…

memo
Guest

I meant the pin 11 on the PIC

Ligo George
Guest

There are 2 VDD pins for PIC 16F877A, connecting any one will provide required power for its operation. So I haven’t connected pin 11 in my experiment. But it is recommended to connect it to VDD when you design PCB.

joe
Guest

Why you use the interrupt ?

joe
Guest

Sir can you help me with asm code because im a beginner 🙁 … please 🙂

Ligo George
Guest

Try using the first program without using the interrupt.. you can see that the program gets stuck sometimes..
I already described it in the above article, please read it carefully.

Ligo George
Guest

Sorry, I don’t know assembly programming..

Ponty
Guest

I used the second code with pic16f887, what can i change to working this code ?? I use mikro c pro.

Ligo George
Guest

Just try regenerating the hex file by rebuilding after changing 16F877A to 16F877 in MikroC Project Settings. .

Ponty
Guest

I change this but the program is not good, the lcd display this: 1. Developed by electrosome 2. Out of range. and after this nothing happening.

Ligo George
Guest

Try first program and check whether it is working or not.. make sure that you place an obstacle in its range..

Guest

hi Ligo George i used first code with pic16f873a, it is wrong value is on LCD. I use PICC compilier. what is wrong. Can i share my source code.

Guest

why shift TMR1H<<8.

Ligo George
Guest

If you are using CCS C Compiler, you can open a topic at our forums ( https://electrosome.com/forums ) ..

Ligo George
Guest

eg : 1100 OR 0011 = 1111
1100 OR 0011 << 8 = 00111100

Guest

thank you.

Guest

thank you but i dont understand quite. KKKK.

line
Guest

what is this “Ltrim(txt);” ?

Ligo George
Guest

It is a built in function in MikroC Pro… It trims blank spaces on the left side of txt… that may produced during IntToStr exection..

line
Guest

nice.