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.
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 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.
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 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.
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.
✅ Activity: Proximity Indicator
Create a proximity indicator with a Red (stop), Yellow (slow), Green (near), Blue (far) light LED. Use the HC-SR04 sensor to determine the distance of the approaching object.
- Red: < 20 cm
- Yellow: 20-50 cm
- Green: 50-80 cm
- Blue: >80 cm
Below is code to change the build-in color changing LED of the ESP32 S2 WROVER.
from neopixel import NeoPixel
from time import sleep
# Define the GPIO pin connected to the RGB LED
LED_PIN = 18 # Update this to the correct pin for your board
# Initialize the NeoPixel object
np = NeoPixel(Pin(LED_PIN), 1) # '1' indicates a single LED
def set_color(r, g, b):
"""Set the color of the RGB LED."""
np[0] = (r, g, b)
np.write()
# Example usage: cycle through red, green, and blue colors
for i in range(2):
set_color(255, 0, 0) # Red
sleep(1)
set_color(255, 255, 0) # Yellow
sleep(1)
set_color(0, 255, 0) # Green
sleep(1)
set_color(0, 0, 255) # Blue
sleep(1)
from neopixel import NeoPixel
from time import sleep
# Define pins
TRIG_PIN = 5
ECHO_PIN = 17
LED_PIN = 18
# Initialize the NeoPixel object
np = NeoPixel(Pin(LED_PIN), 1) # '1' indicates a single LED
# Initialize trigger and echo pins
trig = Pin(TRIG_PIN, Pin.OUT)
echo = Pin(ECHO_PIN, Pin.IN)
def set_color(r, g, b):
"""Set the color of the RGB LED."""
np[0] = (r, g, b)
np.write()
def measure_distance():
"""Measure distance using the HC-SR04 sensor."""
# Ensure the trigger pin is low
trig.value(0)
sleep(0.02)
# Send a 10us pulse to trigger
trig.value(1)
sleep(0.00001)
trig.value(0)
# Measure the duration of the echo pulse
duration = time_pulse_us(echo, 1, 30000) # Timeout after 30ms
if duration < 0:
return None # Timeout or no object detected
# Calculate distance in centimeters
distance = (duration / 2) / 29.1
return distance
# Distance thresholds in centimeters
GREEN_THRESHOLD = 80
YELLOW_THRESHOLD = 50
RED_THRESHOLD = 20
while True:
distance = measure_distance()
if distance is None:
print("Out of range")
set_color(0, 0, 255) # Turn off LED
else:
print("Distance: {:.2f} cm".format(distance))
if distance <= RED_THRESHOLD:
set_color(255, 0, 0) # Red
elif distance <= YELLOW_THRESHOLD:
set_color(255, 255, 0) # Yellow
elif distance <= GREEN_THRESHOLD:
set_color(0, 255, 0) # Green
else:
set_color(0, 0, 255) # Blue (object is far away)
sleep(0.2)