HC-SR04 Ultrasonic Sensor with ESP32 ESP-IDF

In this tutorial, we will learn how to interface HC-SR04 ultrasonic sensor with ESP32 using ESP-IDF. Firstly, we will introduce you to the ultrasonic sensor, then interface it with our development board and program it for contactless distance measurement using ESP-IDF. For demonstration, we will first measure the distance readings and print them on the ESP-IDF console. Afterwards, we will connect an SSD1306 OLED with the ESP32 board to view the distance measurements on the OLED as well.

HC-SR04 Ultrasonic Sensor with ESP32 ESP-IDF

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

HC-SR04 Ultrasonic Sensor

The HC-SR04 ultrasonic sensor is used to measure distance to an object using sonar. It is able to measure the distance from 2-400 cm with a good accuracy of 0.3cm. This sensor consists of both the transmitter and the receiver modules. The transmitter module is responsible for converting the electrical signal into a 40KHz burst of 8 sonar wave pulses. Whereas, the ultrasonic receiver circuit listens to the ultrasonic waves produced by the transmitter circuit.

The figure below shows the HC-SR04 ultrasonic sensor. You can view the pin configuration of the sensor.

HC-SR04 ultrasonic sensor pinout

The HC-SR04 consists of 4 pins which include two power supply pins (VCC and GND) and two input output pins (trigger and echo pulse).

VCCThe VCC pin powers the ultrasonic sensor. Connect it with 5V.
TriggerThe trigger input pin is responsible for starting the distance measurement/ranging. To obtain the distance measurement, a 10µs pulse is applied to this pin which is the input electrical signal to the transmitter circuit.
Echo PulseThe echo pulse pin is the pulse output pin that generates a pulse. The width/on-time of the pulse is determined by the distance between the ultrasonic sensor and the object in front of the HC-SR04 sensor. When the sensor is inactive, the state of the echo pin is active low.
GNDThis is the ground pin which is used to provide the common ground.

Working of the Ultrasonic Sensor

In this section, let us look at how the ultrasonic sensor works for distance measurement. The basic principle used by this sensor when measuring distance is the reflection of inaudible ultrasonic sound waves with frequency of 40KHz. These waves when hit an object, they reflect back according to the angle of incidence with the object. When an ultrasonic sensor is placed in parallel with an object, the ultrasonic waves reflect back at an angle of 180 degrees. To measure the distance between an object, make sure it is placed exactly in a parallel position with the ultrasonic sensor. You can view the positioning in the figure below:

HC-SR04 ultrasonic sensor distance measurement

When measuring the distance with HC-SR04, a 10µs pulse is generated by the ESP32 digital output pin at the trigger pin of the sensor, to initiate the process. The transmitter circuit generates eight ultrasonic sonar pulses when the 10µs input trigger signal becomes active low. Simultaneously, the state of the echo pin changes from logic low level to logic high level. The time measurement is started when the state of the echo pin goes high. When the receiver module receives any ultrasonic waves which have been reflected back after striking the object, the state of the echo pin goes low. Th ESP32 development board detects this state change of the echo output signal from active high to active low and stops the time measurement. By measuring the on-time of the Echo output pulse signal, we can measure the distance between the object and the sensor.

The following figure shows the sequence of the trigger input signal, internal signal consisting of the eight 40KHz ultrasonic pulses and the output signal.

HC-SR04 Echo output signal, input trigger signal and 8 sonar pulses

The time it takes for the echo output signal to remain in a high state is dependent on the distance between the ultrasonic sensor and the object placed in front of it. The greater the distance, the longer it takes for the sonar waves to reach back to the ultrasonic receiver module.

Interface HC-SR04 Ultrasonic sensor with ESP32

We will need the following components to connect our ESP32 board with the ultrasonic sensor.

  1. ESP32 board
  2. HC-SR04
  3. Breadboard
  4. Connecting Wires

The connections between the devices are specified in the table below:

HC-SR04 PinESP32
VCCThe VCC pin will be connected with Vin (5V) pin of the ESP32 board.
TriggerThis pin is connected with a digital input pin of ESP32. We will use GPIO5.
EchoThis pin is connected with a digital output pin of ESP32. We will use GPIO18.
GNDCommon ground with ESP32.

Connect the HC-SR04 sensor with the ESP32 development board as shown in the schematic diagram below:

ESP32 with HC-SR04 ultrasonic sensor schematic diagram

GPIO5 of ESP32 will act as a digital output pin to provide a trigger signal to the ultrasonic sensor. Likewise, GPIO18 will act as a digital input pin to capture the echo output signal of the sensor.

ESP32 Ultrasonic Sensor with ESP-IDF

We will build and create a project in VS Code with ESP-IDF extension to show distance measurements with the HC-SR04 sensor. The code will read distance measurements and print them in the ESP-IDF terminal after every 0.5 seconds. We will use the ultrasonic library provided by UncleRus on GitHub.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory, along with the ESP-IDF board and target. For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

  • First, let’s add the necessary header files for the libraries required for this project. Create a new folder called ‘components’ and then create two sub folders inside it called ‘esp_idf_lib_helpers’ and ‘ultrasonic.’ Now add the following files listed below:
ESP32 with HC-SR04 ultrasonic sensor ESP-IDF Add components

You can obtain the components files from the links given below:

Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in that file and save it.

ESP32 ESP-IDF Ultrasonic Sensor Code

#include <stdio.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <ultrasonic.h>
#include <esp_err.h>

#define MAX_DISTANCE_CM 500 // 5m max

#define TRIGGER_GPIO 5
#define ECHO_GPIO 18


void ultrasonic_test(void *pvParameters)
{
    ultrasonic_sensor_t sensor = {
        .trigger_pin = TRIGGER_GPIO,
        .echo_pin = ECHO_GPIO
    };

    ultrasonic_init(&sensor);

    while (true)
    {
        float distance;
        esp_err_t res = ultrasonic_measure(&sensor, MAX_DISTANCE_CM, &distance);
        if (res != ESP_OK)
        {
            printf("Error %d: ", res);
            switch (res)
            {
                case ESP_ERR_ULTRASONIC_PING:
                    printf("Cannot ping (device is in invalid state)\n");
                    break;
                case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
                    printf("Ping timeout (no device found)\n");
                    break;
                case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
                    printf("Echo timeout (i.e. distance too big)\n");
                    break;
                default:
                    printf("%s\n", esp_err_to_name(res));
            }
        }
        else
            printf("Distance: %0.04f cm\n", distance*100);

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void app_main()
{
    xTaskCreate(ultrasonic_test, "ultrasonic_test", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL);
}

How the Code Works?

We start off by including the necessary libraries for this project which includes the FreeRTOS libraries to create tasks and generate delays, ultrasonic library for the sensor functionality and esp_err.h for the ESP error handling.

#include <stdio.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <ultrasonic.h>
#include <esp_err.h>

We will set the maximum distance limit to be 500cm which is 5m.

#define MAX_DISTANCE_CM 500

Define the GPIO pins that will be connected with the trigger and echo pins of the ESP32 board. In our case, we are using GPIO5 as the trigger pin and GPIO18 as the echo pin.

#define TRIGGER_GPIO 5
#define ECHO_GPIO 18

Inside the ultrasonic_test(), we will initialize the HC-SR04 sensor and start the distance measurement.

void ultrasonic_test(void *pvParameters)
{
    ultrasonic_sensor_t sensor = {
        .trigger_pin = TRIGGER_GPIO,
        .echo_pin = ECHO_GPIO
    };

    ultrasonic_init(&sensor);

    while (true)
    {
        float distance;
        esp_err_t res = ultrasonic_measure(&sensor, MAX_DISTANCE_CM, &distance);
        if (res != ESP_OK)
        {
            printf("Error %d: ", res);
            switch (res)
            {
                case ESP_ERR_ULTRASONIC_PING:
                    printf("Cannot ping (device is in invalid state)\n");
                    break;
                case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
                    printf("Ping timeout (no device found)\n");
                    break;
                case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
                    printf("Echo timeout (i.e. distance too big)\n");
                    break;
                default:
                    printf("%s\n", esp_err_to_name(res));
            }
        }
        else
            printf("Distance: %0.04f cm\n", distance*100);

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

Firstly, we will configure the ultrasonic_sensor_t structure which is the device descriptor. It holds the GPIO pins that will be used as the trigger and echo pins.

   ultrasonic_sensor_t sensor = {
        .trigger_pin = TRIGGER_GPIO,
        .echo_pin = ECHO_GPIO
    };

After that we will initialize the ultrasonic sensor by calling ultrasonic_init() and pass the pointer to the ultrasonic_sensor_t structure as a parameter inside it.

ultrasonic_init(&sensor);

Inside the infinite while loop, we will start measuring the distance through ultrasonic_measure() function. It takes in three parameters. The first parameter is the pointer to the ultrasonic_sensor_t structure. The second parameter is the maximum distance value that we previously set and the third parameter is the pointer to the variable that holds the distance reading. If the ESP error response is ESP_OK then we will print out the distance measurement in the ESP-IDF terminal converted to centimeters, after every 0.5 seconds. Otherwise, the error response along with a relevant message will get printed on the terminal.

 while (true)
    {
        float distance;
        esp_err_t res = ultrasonic_measure(&sensor, MAX_DISTANCE_CM, &distance);
        if (res != ESP_OK)
        {
            printf("Error %d: ", res);
            switch (res)
            {
                case ESP_ERR_ULTRASONIC_PING:
                    printf("Cannot ping (device is in invalid state)\n");
                    break;
                case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
                    printf("Ping timeout (no device found)\n");
                    break;
                case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
                    printf("Echo timeout (i.e. distance too big)\n");
                    break;
                default:
                    printf("%s\n", esp_err_to_name(res));
            }
        }
        else
            printf("Distance: %0.04f cm\n", distance*100);

        vTaskDelay(pdMS_TO_TICKS(500));
    }

Inside the app_main() function, we will create the ultrasonic test task using the xTaskCreate() function.

void app_main()
{
    xTaskCreate(ultrasonic_test, "ultrasonic_test", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL);
}

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, you can view the distance values in centimeters. Align the HC-SR04 sensor parallel to the object whose distance you want to measure. Move the object/sensor at various distances to see the readings changing.

ESP32 with HC-SR04 ultrasonic sensor ESP-IDF print readings on console

ESP32 ESP-IDF Display Ultrasonic Sensor Distance Readings on OLED

Now let us create another project where we will program our ESP32 board to display the distance measurements obtained by the ultrasonic sensor and display them on a 0.96 SSD1306 OLED display.

Create Project

Follow all the same steps to create a similar project as we did before. This time, however, we have to add library for the OLED as well.

  • First, let’s add the necessary header files for the libraries required for this project. After adding the ultrasonic libraries let us proceed to add ssd1306 libraries. Under the components folder, create a new folder called ‘ssd1306’ and add all the files present at this link. This includes all the files listed below:
ESP32 with HC-SR04 ultrasonic sensor and OLED ESP-IDF Add components

OLED MenuConfig Settings ESP-IDF

Let’s first head over to the menuconfig. Open the ESP-IDF SDK Configuration Editor. Scroll down and open the SSD1306 Configuration. Here we can set the SSD1306 configuration parameter according to our needs. This includes the I2C interface, panel type, SCL GPIO pin, SDA GPIO pin, and the reset GPIO pin. Here you can view that by default, ESP-IDF is using the interface as I2C, panel type as 128×64, SCL GPIO pin as 22, SDA GPIO pin as 21 and Reset GPIO pin as 33. You can alter these parameters according to your OLED display and then click the Save button found at the top. We will leave the configuration settings as default as they match our module.

ESP32 OLED using ESP-IDF TextDemo project SDK configuration editor

You may this dedicated tutorial for ESP32 and OLED:

ESP32 ESP-IDF Ultrasonic Sensor Display Readings on OLED Code

Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in that file and save it.

#include <stdio.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <ultrasonic.h>
#include <esp_err.h>
#include "ssd1306.h"

#define MAX_DISTANCE_CM 500 // 5m max

#define TRIGGER_GPIO 5
#define ECHO_GPIO 18


void ultrasonic_oled_test(void *pvParameters)
{
    ultrasonic_sensor_t sensor = {
        .trigger_pin = TRIGGER_GPIO,
        .echo_pin = ECHO_GPIO
    };

    ultrasonic_init(&sensor);

    SSD1306_t dev;
	i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);
	ssd1306_init(&dev, 128, 64);
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);

    while (true)
    {
        float distance;
        esp_err_t res = ultrasonic_measure(&sensor, MAX_DISTANCE_CM, &distance);
        if (res != ESP_OK)
        {
            printf("Error %d: ", res);
            switch (res)
            {
                case ESP_ERR_ULTRASONIC_PING:
                    printf("Cannot ping (device is in invalid state)\n");
                    break;
                case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
                    printf("Ping timeout (no device found)\n");
                    break;
                case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
                    printf("Echo timeout (i.e. distance too big)\n");
                    break;
                default:
                    printf("%s\n", esp_err_to_name(res));
            }
        }
        else
        printf("Distance: %0.04f cm\n", distance*100);
        char dist[10];
        sprintf(dist, "%0.04f cm", distance*100);
        ssd1306_display_text(&dev, 2, dist, 10, false);

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void app_main()
{
    xTaskCreate(ultrasonic_oled_test, "ultrasonic_oled_test", configMINIMAL_STACK_SIZE * 6, NULL, 5, NULL);
}

How the Code Works?

Most of the code is the same as in the previous project where we measured the distance of the object from the ultrasonic sensor after every 0.5 seconds. This time however, instead of displaying the values on the ESP-IDF terminal, our aim is to display them on an OLED. So let us look over the parts where we are incorporating the OLED.

Inside the ultrasonic_oled_test(), after initializing the ultrasonic sensor we then initialize the I2C interface. The I2C interface will be configured by calling the i2c_master_init() function and passing the address of the SSD1306_t structure, SDA pin, SCL pin and Reset pin as parameters inside it. This will configure the ESP32 master to listen to slaves on the I2C bus.

SSD1306_t dev;
i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);

After that, we will initialize the OLED display by calling ssd1306_init() function. This takes in three parameters. The first parameter is the address of the SSD1306_t structure, the second parameter is the width and the third parameter is the height of the display in pixels. We are using a 128×64 display hence the width is 128 and the height is 64.

ssd1306_init(&dev, 128, 64);

Let’s clear the screen of the OLED display by using ssd1306_clear_screen(). This function takes in two parameters. The first is the address of the SSD1306_t structure and the second parameter is invert which is a bool variable. It is set as false. This means we have a dark background and the text will be displayed in white.

ssd1306_clear_screen(&dev, false);

Secondly, the contrast of the screen is set using ssd1306_contrast(). This function takes in two parameters. The first is the address of the SSD1306_t structure and the second parameter is the contrast value which is an int variable. It is set as 0xff.

ssd1306_contrast(&dev, 0xff);

After that the ultrasonic distance measurement is done and saved in the float variable ‘distance.’ We will first convert the float data type to an array of characters, round off to four decimal places and add the unit for measurement. Then display the distance reading on the OLED after every 0.5 seconds.

To display the reading on the OLED screen, we will call the function ssd1306_display_text. It takes in five parameters. The first parameter is the address of the SSD1306_t structure. The second parameter is the page number of the composition data which is set as ‘2’. The third parameter is the text that we want to display. It is the array of characters consisting of the distance measurement in centimeters. The fourth parameter is the length of the text. Lastly, the fifth parameter is invert which is a bool variable. It is set as false. This means we have a dark background and the text will be displayed in white.

    
        printf("Distance: %0.04f cm\n", distance*100);
        char dist[10];
        sprintf(dist, "%0.04f cm", distance*100);
        ssd1306_display_text(&dev, 2, dist, 10, false);

        vTaskDelay(pdMS_TO_TICKS(500));
    

Inside the app_main() function, we will call xTaskCreate() to create the ultrasonic_oled_test task that will measure the distance between an object placed in front of the sensor and print it on the OLED after every 0.5 seconds.

void app_main()
{
    xTaskCreate(ultrasonic_oled_test, "ultrasonic_oled_test", configMINIMAL_STACK_SIZE * 6, NULL, 5, NULL);
}

Hardware Setup OLED with ESP32 and HC-SR04 Ultrasonic Sensor

Now let us setup our circuit by connecting the ESP32 development board with an OLED and a HC-SR04 sensor.

An OLED display requires an operating voltage of 3.3-5V, hence we can connect the VCC pin of OLED either with the 3.3V pin or the Vin pin of the ESP32 module. The GND pins will be common. To connect the I2C pins, we will use the default I2C pins of the ESP32 board. By default in ESP32, GPIO22 is set up as SCL pin and GPIO21 is set up as SDA pin. Therefore, we will connect SDA pin of display with GPIO21 and SCL pin of display with GPIO22.

The tables below show the connections that we will be using to patch up our circuit.

OLED DisplayESP32
VCCVin
GNDGND
SCLGPIO22
SDAGPIO21
ESP32HC-SR04
VinVCC
GPIO5Trigger
GPIO18Echo
GNDGND

Assemble the circuit as shown in the schematic diagram below:

ESP32 with HC-SR04 ultrasonic sensor and OLED schematic diagram

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, you can view in the ESP-IDF terminal that the OLED got configured successfully.

ESP32 with HC-SR04 ultrasonic sensor and OLED ESP-IDF terminal

The OLED will start displaying distance values in centimeters. Align an object parallel to the ultrasonic sensor and move it around to see the distance measurements changing on the display.

Watch the video below to see the demonstration.

You may also like to read:

If you are using Arduino IDE instead of ESP-IDF, you can refer to this article:

2 thoughts on “HC-SR04 Ultrasonic Sensor with ESP32 ESP-IDF”

  1. Hi, first of all thanks for all those tutorials, its been helping me a lot. But i am having some troubles with this one in particular. I created the “Components” folder and the two subfolders without problems, then also added the necessary files to those folders and copied and pasted the code to the main.c file.

    But when i try to build i get the following error: fatal error: ultrasonic.h: No such file or directory
    #include

    But that’s obviously not true since i added the files in the right folder. P.S: I also tried with #include “ultrasonic.h” but the result is the same. I tried adding the full path to the file and it worked (like this: #include ) but after that i got a new error: undefined reference to `ultrasonic_measure’.

    And after this i don’t know what else to do, i checked the ultrasonic.h file and, no surprise, it has the “ultrasonic_measure” function there so don’t know what’s happening.

    If you know what’s happening, and how to fix it, then i would appreciate the help. Thanks

    Reply

Leave a Comment