#170Beginner

Raspberry Pi Pico Parking Sensor (HC-SR04 + 3 LEDs + Buzzer)

Bring your car's reverse parking sensor to your desk! The **Raspberry Pi Pico** uses an **HC-SR04 ultrasonic sensor** to measure the distance to an obstacle about 16 times a second. As it gets closer, the **green, yellow and red LEDs** light up in turn and the **buzzer** beeps faster; under 10 cm the tone turns continuous and the red LED flashes. Ultrasonic ranging, PWM sound and non-blocking timing in one project.

0.0(0)
1 completed
Raspberry Pi Pico Parking Sensor (HC-SR04 + 3 LEDs + Buzzer)
A desktop version of the parking sensor in a car. The HC-SR04 sends out a sound too high for your ears and listens for the echo to measure how far away an obstacle is; once something comes within a metre, the green LED lights up and the buzzer beeps slowly. As it gets closer the yellow and red LEDs join in and the beeps get faster and higher, and under 10 centimetres the tone never stops while the red light flashes.

Video

Circuit Diagram

Raspberry Pi Pico Parking Sensor (HC-SR04 + 3 LEDs + Buzzer) - Circuit

Source Code

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Raspberry Pi Pico Parking Sensor (HC-SR04 + 3 LEDs + Buzzer)
4# ─────────────────────────────────────────
5# Car parking sensor. The HC-SR04 measures the distance to an obstacle; as
6# it gets closer the green, yellow and red LEDs light up in turn and the
7# beeps speed up. Very close, the tone becomes continuous and the red LED
8# flashes.
9#
10# Pins:
11#   GP27 -> HC-SR04 TRIG
12#   GP26 -> HC-SR04 ECHO (1k + 2k divider on real hardware)
13#   VBUS -> HC-SR04 VCC (5V), GND.6 -> HC-SR04 GND
14#   GP4  -> 220 ohm -> Red LED (cathode to GND)
15#   GP8  -> 220 ohm -> Yellow LED (cathode to GND)
16#   GP12 -> 220 ohm -> Green LED (cathode to GND)
17#   GP17 -> Buzzer (+), other leg to GND
18
19from machine import Pin, PWM, time_pulse_us
20import time
21
22# ============ SETTINGS ============
23GREEN_CM = 100         # Green on, beeping starts
24YELLOW_CM = 50         # Yellow joins in
25RED_CM = 25            # Red joins in
26STOP_CM = 10           # Continuous tone + flashing red
27
28BEEP_MS = 60           # Length of one beep
29SLOW_GAP_MS = 700      # Gap between beeps at GREEN_CM
30FAST_GAP_MS = 50       # Gap between beeps near STOP_CM
31VOLUME = 32768         # PWM duty, 32768 = 50%
32FLASH_MS = 120         # Red flash period in STOP zone
33
34MEASURE_MS = 60        # Measure interval, min 60 ms
35PRINT_MS = 250         # Serial print interval
36ECHO_TIMEOUT_US = 12000  # beyond ~2 m = no obstacle
37SOUND_CM_PER_US = 0.0343  # Speed of sound (20 C)
38LOOP_MS = 5            # Main loop step
39
40# Zones from near to far: (name, upper limit cm, (green, yellow, red), tone Hz)
41ZONES = (
42    ("DUR / STOP", STOP_CM, (1, 1, 1), 1900),
43    ("KIRMIZI / RED", RED_CM, (1, 1, 1), 1900),
44    ("SARI / YELLOW", YELLOW_CM, (1, 1, 0), 1500),
45    ("YESIL / GREEN", GREEN_CM, (1, 0, 0), 1200),
46)
47CLEAR = len(ZONES)     # No obstacle zone
48STOP = 0               # Index of the STOP zone
49
50# ============ HARDWARE ============
51trig = Pin(27, Pin.OUT, value=0)
52echo = Pin(26, Pin.IN)
53led_green = Pin(12, Pin.OUT, value=0)
54led_yellow = Pin(8, Pin.OUT, value=0)
55led_red = Pin(4, Pin.OUT, value=0)
56buzzer = PWM(Pin(17))
57buzzer.duty_u16(0)
58
59
60# ============ MEASUREMENT ============
61def measure_cm():
62    # Send a 10 us TRIG pulse, time how long ECHO stays HIGH
63    trig.value(0)
64    time.sleep_us(2)
65    trig.value(1)
66    time.sleep_us(10)
67    trig.value(0)
68    pulse = time_pulse_us(echo, 1, ECHO_TIMEOUT_US)
69    if pulse < 0:
70        return None    # No echo or too far
71    # Sound travels there and back: half
72    return pulse * SOUND_CM_PER_US / 2
73
74
75def median3(values):
76    # Median of the last 3 readings: one bad reading does not flicker the LEDs
77    a, b, c = values
78    return max(min(a, b), min(max(a, b), c))
79
80
81def zone_of(dist):
82    if dist is None:
83        return CLEAR
84    for i, zone in enumerate(ZONES):
85        if dist <= zone[1]:
86            return i
87    return CLEAR
88
89
90def beep_gap_ms(dist):
91    # Between STOP_CM and GREEN_CM the beep gap grows linearly from FAST to SLOW
92    ratio = (dist - STOP_CM) / (GREEN_CM - STOP_CM)
93    ratio = min(1.0, max(0.0, ratio))
94    return int(FAST_GAP_MS + ratio * (SLOW_GAP_MS - FAST_GAP_MS))
95
96
97# ============ SOUND ============
98tone_on = False
99tone_hz = 0
100
101
102def tone(hz):
103    # Leave PWM alone if the tone is already on
104    global tone_on, tone_hz
105    if tone_on and tone_hz == hz:
106        return
107    buzzer.freq(hz)
108    buzzer.duty_u16(VOLUME)
109    tone_on = True
110    tone_hz = hz
111
112
113def silence():
114    global tone_on
115    if tone_on:
116        buzzer.duty_u16(0)
117        tone_on = False
118
119
120def set_leds(green, yellow, red):
121    led_green.value(green)
122    led_yellow.value(yellow)
123    led_red.value(red)
124
125
126def self_test():
127    # Short power-on test of LEDs and buzzer: green -> yellow -> red
128    for leds, hz in (((1, 0, 0), 1200), ((1, 1, 0), 1500), ((1, 1, 1), 1900)):
129        set_leds(*leds)
130        tone(hz)
131        time.sleep_ms(90)
132        silence()
133        time.sleep_ms(160)
134    set_leds(0, 0, 0)
135
136
137# ============ MAIN LOOP ============
138print("== BlueGrays · bluegrays.com ==")
139print("Pico Park Sensoru / Parking Sensor")
140print("Yesil <= %d cm, sari <= %d cm, kirmizi <= %d cm, surekli ses <= %d cm"
141      % (GREEN_CM, YELLOW_CM, RED_CM, STOP_CM))
142print("Green <= %d cm, yellow <= %d cm, red <= %d cm, continuous tone <= %d cm"
143      % (GREEN_CM, YELLOW_CM, RED_CM, STOP_CM))
144self_test()
145
146FAR = 999.0                       # Placeholder for no obstacle
147history = [FAR, FAR, FAR]         # Last 3 readings
148distance = None                   # Filtered distance (cm)
149zone = CLEAR
150last_zone = -1
151now = time.ticks_ms()
152next_measure = now
153next_print = now
154last_beep = time.ticks_add(now, -10000)   # First beep plays at once
155beep_end = now
156
157while True:
158    now = time.ticks_ms()
159
160    # 1) Measurement: every MEASURE_MS, with a median filter
161    if time.ticks_diff(now, next_measure) >= 0:
162        next_measure = time.ticks_add(now, MEASURE_MS)
163        reading = measure_cm()
164        history = history[1:] + [reading if reading is not None else FAR]
165        filtered = median3(history)
166        distance = None if filtered >= FAR else filtered
167        zone = zone_of(distance)
168        if zone != last_zone:
169            name = "ENGEL YOK / CLEAR" if zone == CLEAR else ZONES[zone][0]
170            print("Bolge / Zone ->", name)
171            last_zone = zone
172
173    # 2) LEDs: bar graph, red flashes in the STOP zone
174    if zone == CLEAR:
175        set_leds(0, 0, 0)
176    elif zone == STOP:
177        flash_on = (now // FLASH_MS) % 2 == 0
178        set_leds(1, 1, 1 if flash_on else 0)
179    else:
180        set_leds(*ZONES[zone][2])
181
182    # 3) Sound: non-blocking beep timer
183    if zone == CLEAR:
184        silence()
185    elif zone == STOP:
186        tone(ZONES[STOP][3])          # Very close: continuous tone
187    else:
188        if tone_on and time.ticks_diff(now, beep_end) >= 0:
189            silence()
190        # The gap is recalculated every pass: the next beep speeds up as soon as you get closer
191        period = BEEP_MS + beep_gap_ms(distance)
192        if not tone_on and time.ticks_diff(now, last_beep) >= period:
193            tone(ZONES[zone][3])
194            last_beep = now
195            beep_end = time.ticks_add(now, BEEP_MS)
196
197    # 4) Serial monitor: distance in cm
198    if time.ticks_diff(now, next_print) >= 0:
199        next_print = time.ticks_add(now, PRINT_MS)
200        if distance is None:
201            print("Mesafe / Distance: --- (engel yok / no obstacle)")
202        else:
203            print("Mesafe / Distance: %5.1f cm" % distance)
204
205    time.sleep_ms(LOOP_MS)
206

FAQ

Why does ECHO need a voltage divider on a real circuit?▾

The HC-SR04 runs on 5V and its ECHO output is 5V, but the Pico's pins accept 3.3V at most. Connect ECHO to GP26 through 1 kΩ and GP26 to GND through 2 kΩ, so the pin sees about 3.3V. Wokwi doesn't need the divider.

How do I change the warning distances?▾

Edit the `GREEN_CM`, `YELLOW_CM`, `RED_CM` and `STOP_CM` constants at the top of the code. Keep the thresholds in the order `STOP_CM < RED_CM < YELLOW_CM < GREEN_CM`; for example, use `STOP_CM = 30`, `RED_CM = 50`, `YELLOW_CM = 100` and `GREEN_CM = 150`. The LEDs and beeps follow the new thresholds automatically.

Why do the beeps speed up smoothly instead of zone by zone?▾

`beep_gap_ms()` scales the gap linearly with distance: 700 ms at 100 cm, 50 ms at 10 cm. It is recalculated on every pass of the main loop, so the next beep comes sooner the moment the obstacle gets closer. The zones only change the LEDs and the pitch.

Do I need to install any libraries?▾

No. The `time_pulse_us()` function used for the measurement is part of the built-in `machine` module. Flash MicroPython onto the Pico and save the code to it as `main.py` in Thonny; the sensor starts on its own with an LED test and three beeps.

Raspberry Pi Pico Parking Sensor with HC-SR04, 3 LEDs and Buzzer | BlueGrays