Ultrasonic Distance Measurement HC-SR04

Distance measurement is vital in many applications like robotics, automation, and security systems. The HC-SR04 is a popular ultrasonic sensor used for non-contact distance measurements. This sensor operates by emitting an ultrasonic sound pulse and measuring the time it takes for the echo to return to calculate the distance to an object.

The HC-SR04 is reliable and accurate over short ranges, typically up to 4 meters. Use cases include parking sensors, obstacle avoidance in robotics, and interactive installations.

HC-SR04 Ultrasonic Distance Measurement Module

The HC-SR04 module includes an ultrasonic transmitter, a receiver, and a control circuit. It emits a high-frequency sound wave and receives the echo reflected from a target object. The distance is calculated based on the time interval between sending the wave and receiving the echo.

This module can be interfaced with microcontrollers (like Arduino or ESP32) using digital I/O pins. The HC-SR04 requires a short trigger pulse to start the measurement and then provides a pulse width output corresponding to the distance. The sensor requires a 5V power source.

The following is Micropython code to read 20 distance (cm) values over 10 seconds with the HC-SR04.

import time
from hcsr04 import HCSR04

sensor = HCSR04(trigger_pin=16, echo_pin=0)

for _ in range(20):
    print(f'Distance (mm): {sensor.distance_mm()}')
    time.sleep(0.5)

The hcsr04.py script is needed to interface with the HC-SR04 sensor. Thanks to Roberto Sánchez for the micropython-hcsr04 GitHub repository.

from machine import Pin, time_pulse_us
from utime import sleep_us

__version__ = '0.2.1'
__author__ = 'Roberto Sánchez'
__license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"

class HCSR04:
    """
    Driver to use the untrasonic sensor HC-SR04.
    The sensor range is between 2cm and 4m.

    The timeouts received listening to echo pin are converted to OSError('Out of range')

    """

    # echo_timeout_us is based in chip range limit (400cm)
    def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
        """
        trigger_pin: Output pin to send pulses
        echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor
        echo_timeout_us: Timeout in microseconds to listen to echo pin.
        By default is based in sensor limit range (4m)
        """

        self.echo_timeout_us = echo_timeout_us
        # Init trigger pin (out)
        self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
        self.trigger.value(0)

        # Init echo pin (in)
        self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)

    def _send_pulse_and_wait(self):
        """
        Send the pulse to trigger and listen on echo pin.
        We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
        """

        self.trigger.value(0) # Stabilize the sensor
        sleep_us(5)
        self.trigger.value(1)
        # Send a 10us pulse.
        sleep_us(10)
        self.trigger.value(0)
        try:
            pulse_time = time_pulse_us(self.echo, 1, self.echo_timeout_us)
            # time_pulse_us returns -2 if there was timeout waiting for condition; and -1 if there was timeout during the main measurement. It DOES NOT raise an exception
            # ...as of MicroPython 1.17: http://docs.micropython.org/en/v1.17/library/machine.html#machine.time_pulse_us
            if pulse_time < 0:
                MAX_RANGE_IN_CM = const(500) # it's really ~400 but I've read people say they see it working up to ~460
                pulse_time = int(MAX_RANGE_IN_CM * 29.1) # 1cm each 29.1us
            return pulse_time
        except OSError as ex:
            if ex.args[0] == 110: # 110 = ETIMEDOUT
                raise OSError('Out of range')
            raise ex

    def distance_mm(self):
        """
        Get the distance in milimeters without floating point operations.
        """

        pulse_time = self._send_pulse_and_wait()

        # To calculate the distance we get the pulse_time and divide it by 2
        # (the pulse walk the distance twice) and by 29.1 becasue
        # the sound speed on air (343.2 m/s), that It's equivalent to
        # 0.34320 mm/us that is 1mm each 2.91us
        # pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582
        mm = pulse_time * 100 // 582
        return mm

    def distance_cm(self):
        """
        Get the distance in centimeters with floating point operations.
        It returns a float
        """

        pulse_time = self._send_pulse_and_wait()

        # To calculate the distance we get the pulse_time and divide it by 2
        # (the pulse walk the distance twice) and by 29.1 becasue
        # the sound speed on air (343.2 m/s), that It's equivalent to
        # 0.034320 cm/us that is 1cm each 29.1us
        cms = (pulse_time / 2) / 29.1
        return cms

✅ Activity: Distance Measurement with HC-SR04

Here are a few applications where the HC-SR04 sensor might be useful:

  • Robotics: For detecting obstacles and aiding in navigation.
  • Home Automation: Such as turning lights on/off based on presence.
  • Security Systems: Detecting movement or presence in a particular area.
  • Educational Projects: To teach students about sensors and microcontrollers.

Experiment with the HC-SR04 sensor by measuring distances to various objects. Explore how the sensor accuracy varies with different materials and distances. Discuss the findings and the implications for potential applications.

Collect Data

Collect data using the following Python script, which takes 100 samples every 5 seconds. Position an object at a measured distance from the sensor, and move it as prompted, allowing a 5-second interval for repositioning. The data is stored in the dist_hcsr04.csv file on the microcontroller.

import time
from hcsr04 import HCSR04
from machine import Pin,SoftI2C

dist = HCSR04(trigger_pin=16, echo_pin=0)

fid = open('dist_hcsr04.csv','w')
fid.write('Time,Dist,Measured\n')
ts = time.time()

# calibration distances (inches)
for d in [35,20,10,30,15,5,25,0,35]:
   print(f'------Set distance to {d} inches---------')
   time.sleep(5)
   print('Reading 100 samples')
   for _ in range(100):
      fid.write(f'{time.time()-ts},{d},{dist.distance_mm()}\n')
      time.sleep(0.01)
fid.close()
print('done')

Compare the results of this test to the VL53L0X Laser Sensor. What are the advantages and disadvantages of using sound versus light to measure distance?

Solution

Import the data and convert distance from inches to millimeters. Remove the Time column to help with plotting the data.

import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('http://apmonitor.com/dde/uploads/Main/dist_hcsr04.csv')
# convert inches to mm
data['Dist'] = data['Dist'].values * 25.4
# remove Time column
del data['Time']

data.plot(figsize=(6,3.5))

The data recorded in this solution likely has errors of +/- 20 mm based on where the object was placed for each measurement along the yard stick. Produce a scatter plot that shows the measured versus recorded distance. The lower limit for the distance sensor is about 40mm. The sensor measures the closest object and sound waves travel outward in a spherical form.

data.plot(x='Dist',y='Measured',kind='scatter',figsize=(6,3.5))
upper = data['Dist'].max()
plt.plot([0,upper],[0,upper],'r-')

The sensor can be used in applications up to 4 m range and as low as 4 cm. The sample frequency may be high enough so that multiple samples can be used to reduce distance uncertainty or remove outliers. As an example, the median 5 values of 11 samples (eliminate 3 highest and 3 lowest) can be averaged and reported as the measured value. The median values eliminate potential outliers, and the average reduces the random uncertainties in the data.