A Raspberry Pi radar project maps physical space by sweeping an ultrasonic sensor across a 180-degree arc and plotting the distance returns on a polar coordinate graph. This guide targets the Raspberry Pi 4 Model B (4GB) and the Raspberry Pi 5, using the modern gpiozero library and matplotlib for the GUI. We will cover the mandatory 5V-to-3.3V logic step-down, exact pin mappings, and a fully compilable Python script with error handling.
Hardware Specifications & Parts List
Before wiring, you must understand the logic-level mismatch between the Pi and the sensor. The Raspberry Pi GPIO pins operate at 3.3V. The HC-SR04 Echo pin outputs 5V. Feeding 5V directly into a Pi GPIO will eventually degrade or destroy the SoC pin. A voltage divider is non-negotiable.
| Component | Variant / Model | Operating Voltage | Logic / Signal Level | Key Limitation |
|---|---|---|---|---|
| Microcontroller | Raspberry Pi 4B / 5 | 5V (via USB-C) | 3.3V CMOS | Max 50mA per GPIO pin |
| Ultrasonic Sensor | HC-SR04 | 5V DC | 5V TTL (Echo) | Requires voltage divider on Echo |
| Servo Motor | SG90 Micro Servo | 4.8V - 6.0V DC | 3.3V PWM (50Hz) | Stalls at >1.8kg-cm torque |
| Resistors | 1kΩ and 2kΩ (1/4W) | N/A | N/A | Tolerance ±5% is acceptable |
Pin Mapping & Physical Assembly
Wire the components on a half-size breadboard. Mount the SG90 servo to a fixed base and attach the HC-SR04 to the servo horn using hot glue or a 3D-printed bracket.
| Sensor / Servo Pin | Pi Physical Pin | Pi BCM GPIO | Wiring Notes |
|---|---|---|---|
| HC-SR04 VCC | Pin 2 | 5V Power | Direct to 5V rail |
| HC-SR04 GND | Pin 6 | Ground | Common ground with Pi and Servo |
| HC-SR04 Trig | Pin 16 | GPIO 23 | Direct connection (3.3V is enough to trigger 5V sensor) |
| HC-SR04 Echo | Pin 18 | GPIO 24 | Must pass through 1kΩ/2kΩ voltage divider |
| SG90 PWM (Orange) | Pin 11 | GPIO 17 | Direct PWM signal |
| SG90 VCC (Red) | Pin 4 | 5V Power | Use separate 5V supply if Pi brownouts occur |
| SG90 GND (Brown) | Pin 9 | Ground | Common ground |
Python Polar Plot Radar Code
This script uses gpiozero for hardware abstraction and matplotlib to render an animated polar plot. It includes try/except blocks to handle sensor timeouts and safe GPIO cleanup.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from gpiozero import DistanceSensor, AngularServo
from time import sleep
import sys
# --- Pin Definitions (BCM) ---
TRIG_PIN = 23
ECHO_PIN = 24
SERVO_PIN = 17
# --- Hardware Initialization ---
# max_distance=4.0 sets the timeout threshold to prevent infinite hangs
try:
sensor = DistanceSensor(echo=ECHO_PIN, trigger=TRIG_PIN, max_distance=4.0)
# SG90 pulse widths: min=1ms (0 deg), max=2ms (180 deg)
servo = AngularServo(SERVO_PIN, min_angle=0, max_angle=180,
min_pulse_width=0.001, max_pulse_width=0.002)
except Exception as e:
print(f'Hardware Init Error: {e}')
sys.exit(1)
# --- Matplotlib Polar Setup ---
fig = plt.figure(figsize=(7, 7), facecolor='#111111')
ax = fig.add_subplot(111, polar=True, facecolor='#000000')
ax.set_ylim(0, 400) # Max range 400cm
ax.set_yticks([50, 100, 200, 300])
ax.set_yticklabels(['50cm', '100cm', '200cm', '300cm'], color='white')
ax.set_xticklabels(['90°', '60°', '30°', '0°', '330°', '300°', '270°'], color='white')
ax.grid(color='green', linestyle=':', linewidth=0.5)
line, = ax.plot([], [], 'o-', color='#00ff00', linewidth=2, markersize=4)
# Data storage for the sweep
angles = []
distances = []
def update_radar(frame):
global angles, distances
# Sweep logic: 0 to 180 degrees
current_angle = frame % 181
servo.angle = current_angle
sleep(0.03) # Allow servo to settle and sensor to ping
try:
# gpiozero returns distance in meters; convert to cm
dist_cm = sensor.distance * 100
if dist_cm > 400 or dist_cm < 2:
dist_cm = 400 # Treat out-of-bounds as max range
except Exception:
dist_cm = 400 # Fallback on timeout/error
angles.append(np.radians(current_angle))
distances.append(dist_cm)
# Keep only the last 181 points (one full sweep)
if len(angles) > 181:
angles.pop(0)
distances.pop(0)
line.set_data(angles, distances)
return line,
# Run animation
ani = animation.FuncAnimation(fig, update_radar, frames=range(360),
interval=50, blit=False, repeat=True)
try:
plt.title('Raspberry Pi Ultrasonic Radar', color='white', pad=20)
plt.show()
except KeyboardInterrupt:
print('Radar stopped by user.')
finally:
servo.detach() # Stop PWM signal to prevent servo jitter
print('GPIO cleaned up.')
Debugging Common Radar Failures
When your radar fails to initialize or the GUI freezes, check these exact error strings and their root causes.
1. The "No access to /dev/mem" Error
Exact Error String: RuntimeError: No access to /dev/mem. Try running as root!
Ranked Causes:
- Legacy Library on Bookworm OS: You are using the deprecated
RPi.GPIOlibrary on Raspberry Pi OS Bookworm, which dropped support for it. Fix: Switch togpiozeroas shown in the code above. - Missing Permissions: Your user isn't in the
gpiogroup. Fix: Runsudo usermod -aG gpio $USERand reboot.
2. The Pi 5 Pin Factory Error
Exact Error String: gpiozero.exc.BadPinFactory: Unable to load any default pin factory!
Ranked Causes:
- Missing lgpio Backend: The Raspberry Pi 5 requires the
lgpiobackend forgpiozeroto function. Fix: Runsudo apt install python3-rpi-lgpio. - Virtual Environment Isolation: You are running inside a Python
venvthat lacks system site-packages. Fix: Recreate the venv withpython3 -m venv --system-site-packages env.
3. The First Three Things to Check When Hardware Fails
If the code runs but the radar plot is empty or the Pi reboots randomly:
- Multimeter the Voltage Divider: Disconnect the Echo pin from the Pi. Trigger the sensor and measure the voltage at the junction of the 1kΩ and 2kΩ resistors. It must read between 3.2V and 3.3V. If it reads 5V, your resistor wiring is wrong and you are risking the Pi.
- Check for 5V Rail Brownout: The SG90 servo draws up to 700mA under stall. If powered directly from the Pi's 5V pin, it will pull the voltage down, causing the Pi's low-voltage warning (lightning bolt icon) and random reboots. Fix: Power the servo from a dedicated 5V 2A buck converter or USB power bank, tying only the GND to the Pi.
- Verify Sensor Timeout Threshold: If the sensor points at a soft surface (like a curtain) that absorbs sound waves, it will timeout. Ensure
max_distance=4.0is set ingpiozeroso the library handles the timeout gracefully instead of hanging the thread.
Extending or Simplifying the Build
Depending on your application, you can scale this raspberry pi radar project up or down.
If you don't need the GUI and just want to log proximity data for a security alarm, strip out
matplotlib entirely. Replace the animation loop with a simple while True: loop that writes sensor.distance and servo.angle to a CSV file or publishes it via MQTT to Home Assistant. This reduces CPU load to near zero and allows the script to run as a background systemd service.
How to Extend (mmWave Upgrade):
The HC-SR04 struggles with soft materials and wide beam angles (15°). For a 2026-spec upgrade, replace the ultrasonic sensor with an HLK-LD2410 mmWave Radar Sensor. The LD2410 communicates via UART (TX/RX pins) and provides gating data for both moving and stationary targets up to 6 meters. You will need to swap the DistanceSensor class for a custom pyserial parser to read the hex frames, but the physical servo sweep and polar plotting logic remain identical. For more on serial communication protocols, refer to the gpiozero documentation and matplotlib subplot references.






