To successfully interface an HC-SR04 ultrasonic sensor with Raspberry Pi hardware, you must solve a fundamental voltage mismatch: the sensor outputs a 5V logic signal on its Echo pin, while the Raspberry Pi GPIO pins are strictly 3.3V tolerant. Feeding 5V directly into a Pi 5 or Pi 4 GPIO pin will permanently damage the SoC or the RP1 I/O controller. This guide provides the exact voltage divider schematic, pin mappings, and modern Python code targeting Raspberry Pi OS (Bookworm) using the gpiozero library and the lgpio backend.
Parts List and Hardware Specifications
Before wiring, verify you have the exact components listed below. Substituting the HC-SR04 with a 3.3V-native sensor (like the RCWL-1601) eliminates the need for resistors, but the HC-SR04 remains the most common and cost-effective module on the market.
| Component | Exact Variant / Specification | Estimated Cost (2026) |
|---|---|---|
| Microcontroller | Raspberry Pi 5 (8GB) or Pi 4 Model B (4GB+) | $80.00 / $55.00 |
| Ultrasonic Sensor | HC-SR04 (Standard 4-pin, 5V VCC logic) | $2.50 |
| Resistor 1 (R1) | 1kΩ (1/4W, 5% tolerance, through-hole) | $0.10 |
| Resistor 2 (R2) | 2kΩ (1/4W, 5% tolerance, through-hole)* | $0.10 |
| Prototyping | Half-size breadboard, male-to-female & male-to-male jumpers | $5.00 |
*Note: If you cannot find a 2kΩ resistor, use a 330Ω and 470Ω in series, or two 1kΩ resistors in series. The goal is a ratio where R2 is roughly double R1.
Pin Mapping and the 3.3V Voltage Divider
The HC-SR04 requires four connections: VCC, Trig, Echo, and GND. While VCC, Trig, and GND can connect directly to the Pi, the Echo pin requires a voltage divider to step the 5V high signal down to a safe 3.3V.
V_out = V_in * (R2 / (R1 + R2)), a 5V input with a 1kΩ (R1) and 2kΩ (R2) yields 5 * (2000 / 3000) = 3.33V. This is perfectly within the 3.3V logic high threshold of the Raspberry Pi GPIO.
| HC-SR04 Pin | Raspberry Pi GPIO (BCM) | Physical Pin # | Wiring Notes |
|---|---|---|---|
| VCC | 5V Power | Pin 2 or 4 | Do not use 3.3V; the sensor will fail to trigger. |
| Trig | GPIO 23 | Pin 16 | Direct connection. Pi outputs 3.3V, which the HC-SR04 reads as a valid HIGH. |
| Echo | GPIO 24 | Pin 18 | Must go through the voltage divider. |
| GND | Ground | Pin 14 | Shared ground with the Pi and the bottom of R2. |
- Place the Resistors: Insert the 1kΩ resistor into the breadboard. Insert the 2kΩ resistor so that one of its legs shares the same row as one leg of the 1kΩ resistor. This shared row is your Echo Output.
- Wire the Sensor Echo: Connect a jumper from the HC-SR04 Echo pin to the unshared leg of the 1kΩ resistor.
- Wire to Pi GPIO: Connect a jumper from the shared row (between R1 and R2) to Physical Pin 18 (GPIO 24) on the Raspberry Pi.
- Complete the Ground: Connect a jumper from the unshared leg of the 2kΩ resistor to the Pi's Ground (Physical Pin 14). This grounds the divider.
- Power and Trigger: Connect HC-SR04 VCC to Pi 5V (Pin 2), Trig to GPIO 23 (Pin 16), and GND to Pi Ground (Pin 6).
Python Code for Raspberry Pi OS (Bookworm)
The Raspberry Pi 5 utilizes the RP1 southbridge chip, which changed how GPIO is accessed at the hardware level. Legacy libraries like RPi.GPIO are deprecated on Bookworm. The code below uses the officially supported gpiozero library, which automatically leverages the lgpio backend on modern Pi OS installations.
Ensure your environment is prepared by running: sudo apt update && sudo apt install python3-gpiozero python3-lgpio
from gpiozero import DistanceSensor
from time import sleep
import signal
import sys
# Pin Definitions (BCM Numbering)
TRIG_PIN = 23
ECHO_PIN = 24
# Initialize sensor.
# max_distance=4.0 prevents infinite timeout loops.
# queue_len=5 smooths out OS-level timing jitter inherent to Linux.
sensor = DistanceSensor(
echo=ECHO_PIN,
trigger=TRIG_PIN,
max_distance=4.0,
queue_len=5
)
def graceful_exit(signum, frame):
"""Handles Ctrl+C to safely release GPIO pins."""
print("\n[INFO] Exiting safely and releasing pins...")
sensor.close()
sys.exit(0)
# Bind the signal handler
signal.signal(signal.SIGINT, graceful_exit)
print("[INFO] Starting HC-SR04 distance measurement...")
print("[INFO] Press Ctrl+C to stop.\n")
try:
while True:
# gpiozero returns distance in meters as a float
distance_m = sensor.distance
distance_cm = distance_m * 100
# sensor.value == 1.0 indicates the echo timed out (object beyond max_distance)
if sensor.value == 1.0:
print("Status: Out of range (Timeout)")
else:
print(f"Distance: {distance_cm:6.2f} cm")
sleep(0.25) # 4Hz polling rate is optimal for HC-SR04
except Exception as e:
print(f"[ERROR] Sensor read failed: {e}")
finally:
sensor.close()
Debugging: First Three Checks and Exact Error Strings
When building embedded projects on a non-real-time OS like Linux, timing jitter and pin conflicts are common. If your script fails, perform these first three checks before rewriting code.
1. The First Three Things to Check When It Fails
- Verify the Voltage Divider with a Multimeter: Set your DMM to DC Voltage. Put the black probe on Pi Ground and the red probe on the shared row of your voltage divider. Run a script that forces the Echo pin HIGH (or manually trigger the sensor). If your multimeter reads ~3.3V, the hardware is safe. If it reads 5V, your wiring is wrong and you risk frying the Pi.
- Verify the GPIO Backend: The Pi 5 requires
lgpio. If you are on a virtual environment or an older OS image,gpiozeromight fallback to a brokenRPi.GPIOstate. Runpip show rpi-lgpioin your terminal. If it returns "Not found", install it viapip install rpi-lgpio. - Check for Ghost Processes: If a previous Python script crashed without calling
sensor.close(), the OS might still hold the pin. Runsudo killall python3or reboot the Pi to clear locked GPIO states.
2. Exact Error Strings and Ranked Causes
| Exact Error String | Ranked Causes & Fixes |
|---|---|
gpiozero.exc.GPIOPinInUse: pin 24 is already in use |
1. Ghost process holding the pin (Fix: sudo killall python3).2. Pin mapped to a system function in /boot/firmware/config.txt (Fix: check for dtoverlay conflicts). |
RuntimeError: Failed to initialize the lgpio library |
1. Missing backend on Pi 5 (Fix: sudo apt install python3-lgpio).2. Running script without proper user permissions for GPIO memory access (Fix: add user to gpio group or run with sudo). |
ValueError: distance must be between 0 and 1 (or constant 1.0 timeouts) |
1. Trigger pulse too short due to OS jitter (Fix: increase queue_len in code).2. Sensor VCC is sagging below 4.5V under load (Fix: measure 5V rail with DMM; use a dedicated 5V power supply if Pi USB-C is underpowered). |
How to Extend or Simplify the Build
Depending on your project requirements, you may want to alter the hardware footprint or add network capabilities.
How to Simplify the Build
If you want to eliminate the breadboard and voltage divider entirely, replace the HC-SR04 with the ME007YS or RCWL-1601. These are drop-in replacements that operate natively at 3.3V logic and 3.3V VCC. They cost roughly $4.00 more per unit but save 15 minutes of wiring and completely eliminate the risk of blowing a GPIO pin. The Python code above remains 100% identical; only the physical wiring changes.
How to Extend the Build
To turn this into an IoT proximity alarm, extend the Python script to publish MQTT payloads. Install paho-mqtt and add a callback inside the while loop:
import paho.mqtt.client as mqtt
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.connect("192.168.1.100", 1883, 60)
# Inside your while loop:
client.publish("homeassistant/sensor/garage_distance", payload=f"{distance_cm:.2f}", qos=1)
This allows Home Assistant to ingest the distance data natively via the MQTT integration, triggering automations like turning on a workbench light when you sit down.
Frequently Asked Questions
Can I use an HC-SR04 ultrasonic sensor with Raspberry Pi without resistors?
No. The HC-SR04 Echo pin outputs a 5V HIGH signal when the sound wave returns. The Raspberry Pi 5 and Pi 4 GPIO pins have an absolute maximum voltage tolerance of 3.6V before silicon degradation begins. Connecting the Echo pin directly to the Pi without a voltage divider or a logic level converter (like a BSS138 MOSFET module) will likely destroy the GPIO pin and potentially short the internal RP1 or BCM2711 power rails.
Why is my Raspberry Pi ultrasonic sensor reading 0 or fluctuating wildly?
Wild fluctuations (e.g., jumping from 12cm to 150cm to 4cm) are almost always caused by acoustic multipath interference or Linux OS timing jitter. Acoustically, ensure the sensor is not pointed at a curved surface or a chain-link fence, which scatters the 40kHz sound waves. Electrically, Linux is not a real-time operating system; background tasks can delay the microsecond timing required to measure the Echo pulse. Using the queue_len=5 parameter in gpiozero forces the library to take 5 rapid samples and average them, smoothing out OS-level jitter.
What is the maximum reliable range for the HC-SR04 on a Raspberry Pi?
While the HC-SR04 datasheet claims a maximum range of 400cm (4 meters), practical bench testing reveals that reliable, noise-free readings top out around 250cm to 300cm. Beyond 3 meters, the 40kHz acoustic wave attenuates significantly in ambient air, and the returning echo voltage drops below the sensor's internal comparator threshold, resulting in timeouts. For ranges beyond 3 meters, switch to a LiDAR module like the TF-Luna (I2C/UART) or a time-of-flight (ToF) optical sensor like the VL53L1X.
Does the Raspberry Pi 5 require a different library than the Pi 4 for ultrasonic sensors?
The Python library (gpiozero) remains the same, but the underlying C-backend changes. The Pi 4 uses the BCM2711 SoC and historically relied on the RPi.GPIO library accessing /dev/mem. The Pi 5 uses the RP1 I/O controller, which requires the lgpio backend to communicate via the character device interface (/dev/gpiochip0). If you copy an older tutorial that explicitly imports RPi.GPIO, it will throw a fatal error on a Pi 5 running Raspberry Pi OS Bookworm. Always use gpiozero for cross-compatibility.






