Here are two solid Python options for the [HC-SR04](https://www.ampheo.com/product/hc-sr04-26835950) [ultrasonic sensor](https://www.onzuu.com/category/ultrasonic-receivers-transmitters) on [Raspberry Pi](https://www.ampheo.com/c/raspberry-pi/raspberry-pi-boards): a quick RPi.GPIO version and a more accurate pigpio version.

**Wiring (very important)**
* VCC → 5V
* GND → GND
* TRIG → Pi GPIO (3.3V output OK) (example uses GPIO23)
* ECHO → Pi GPIO via level shift to 3.3V (use a voltage divider, e.g., 1 kΩ + 2 kΩ).
Do NOT feed 5V ECHO directly into the Pi.
**Option A — Simple (RPi.GPIO)**
```
# file: hcsr04_gpio.py
# python3 hcsr04_gpio.py
import time
import RPi.GPIO as GPIO
TRIG = 23 # BCM numbering
ECHO = 24
GPIO.setmode(GPIO.BCM)
GPIO.setup(TRIG, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(ECHO, GPIO.IN)
def distance_cm(temp_c=20.0):
"""Return one distance sample in cm. Uses blocking timing."""
# Speed of sound (m/s) ~ 331.3 + 0.606*TempC
sos = 331.3 + 0.606 * temp_c # m/s
time.sleep(0.0002) # settle
# 10 µs pulse on TRIG
GPIO.output(TRIG, True)
time.sleep(10e-6)
GPIO.output(TRIG, False)
# Wait for echo to go high
t0 = time.time()
while GPIO.input(ECHO) == 0:
start = time.time()
if start - t0 > 0.02: # 20 ms timeout
return None
# Measure high pulse width
t0 = time.time()
while GPIO.input(ECHO) == 1:
end = time.time()
if end - t0 > 0.04: # 40 ms timeout (≈ 6.8 m range)
return None
pulse = end - start # seconds high
# distance = (time * speed_of_sound)/2
dist_m = (pulse * sos) / 2.0
return dist_m * 100.0 # cm
try:
while True:
d = distance_cm(temp_c=22.0)
if d is None:
print("No echo / timeout")
else:
print(f"{d:6.2f} cm")
time.sleep(0.2)
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()
```
**Run**
```
pip install RPi.GPIO
python3 hcsr04_gpio.py
```
**Option B — More accurate (pigpio, edge-timed callbacks)**
```
# file: hcsr04_pigpio.py
# python3 hcsr04_pigpio.py
import time
import pigpio
TRIG = 23
ECHO = 24
pi = pigpio.pi()
if not pi.connected:
raise SystemExit("pigpio daemon not running. Start with: sudo pigpiod")
pi.set_mode(TRIG, pigpio.OUTPUT)
pi.set_mode(ECHO, pigpio.INPUT)
pi.write(TRIG, 0)
class PulseTimer:
def __init__(self, pin):
self._rise = None
self.width = None
self.cb = pi.callback(pin, pigpio.EITHER_EDGE, self._cb)
def _cb(self, gpio, level, tick):
# tick is in microseconds (wraps ~ 72 minutes)
if level == 1:
self._rise = tick
elif level == 0 and self._rise is not None:
dt = pigpio.tickDiff(self._rise, tick) # microseconds
self.width = dt / 1e6 # seconds
pulse = PulseTimer(ECHO)
def distance_cm(temp_c=20.0, timeout_s=0.04):
sos = 331.3 + 0.606 * temp_c # m/s
pulse.width = None
# 10 µs trigger
pi.gpio_trigger(TRIG, 10, 1)
t0 = time.time()
while pulse.width is None and (time.time() - t0) < timeout_s:
time.sleep(0.0002)
if pulse.width is None:
return None
dist_m = (pulse.width * sos) / 2.0
return dist_m * 100.0
try:
while True:
d = distance_cm(22.0)
print("No echo / timeout" if d is None else f"{d:6.2f} cm")
time.sleep(0.2)
except KeyboardInterrupt:
pass
finally:
pulse.cb.cancel()
pi.stop()
```
**Run**
```
sudo apt install -y pigpio
sudo systemctl enable --now pigpiod
pip install pigpio
python3 hcsr04_pigpio.py
```
**Notes & tips**
* Typical reliable range: 2 cm → ~4 m. Very close objects can saturate; far ones may timeout.
* Add a small median filter over 3–5 samples if your readings jump.
* Temperature compensation included above; set temp_c if you know ambient temp.
* If you see constant timeouts, check: level shifting on ECHO, common GND, and cable length (<20 cm) for TRIG/ECHO.