
What you'll build
This guide builds a tabletop parking sensor that measures distance with an HC-SR04P ultrasonic module, shows the live reading on a small SSD1306 OLED, and gives an obvious warning using green, yellow, and red LEDs. When an object is far away, the green LED stays on. As it moves closer, the yellow LED takes over. Inside the stop zone, the red LED lights and the buzzer beeps faster so the warning is useful without staring at the screen.
The build is bench-safe. It does not connect to a real vehicle, control brakes, or make safety decisions. The HC-SR04P version runs at 3.3 V logic, so it is a better fit for an ESP32 than the older 5 V-only HC-SR04 modules. The firmware sends a short trigger pulse, measures the echo time, converts it to centimetres, smooths the reading with an exponential filter, and updates the LEDs, display, and buzzer from the same distance thresholds.
By the end you will have a compact distance-warning box you can test with a cardboard wall, robot chassis, or desk edge. It is a good first project for learning timing, digital outputs, simple state thresholds, and sensor noise handling before moving to a rover or any build where distance sensing affects motion.
What you are building
The firmware has four main jobs:
- Pulse GPIO27 (TRIG) for 10 µs and measure the echo duration on GPIO26 (ECHO) to compute distance in centimetres,
- Smooth the raw reading with an exponential filter (75% old, 25% new) to reduce jitter,
- Drive the green (GPIO14), yellow (GPIO12), and red (GPIO13) LEDs and the passive buzzer (GPIO25) based on three distance thresholds —
SAFE_DISTANCE_CM(70 cm),CAUTION_DISTANCE_CM(35 cm), andSTOP_DISTANCE_CM(18 cm), - Update the SSD1306 OLED (I2C: SDA GPIO21, SCL GPIO22) with the current distance and a status label every 80 ms.
Bluetooth or Wi-Fi connectivity, data logging, and vehicle integration are outside the scope of this build.
Upload and calibrate
Install the Adafruit SSD1306 and Adafruit GFX libraries, then open the project in Schematik and click Deploy using Chrome or Edge.
Open Serial Monitor at 115200 baud. The sketch starts immediately; the OLED should display "Parking sensor" with a live centimetre reading within a second.
The three distance thresholds are defined as constants at the top of the sketch:
SAFE_DISTANCE_CM— 70 cm, boundary between green and yellow zonesCAUTION_DISTANCE_CM— 35 cm, boundary between yellow and red zonesSTOP_DISTANCE_CM— 18 cm, threshold where the buzzer interval halves and the tone rises from 1200 Hz to 1800 Hz
Adjust these constants to match your intended use. For a desk edge the defaults are reasonable; for a garage the stop distance will need to be larger.
If your OLED stays blank, try changing the I2C address in the sketch from 0x3C to 0x3D — some SSD1306 modules use the alternate address.
Troubleshooting
- OLED stays blank after boot: most SSD1306 modules use address
0x3C, but some use0x3D. Check the solder bridge or marking on the back of your module and update thedisplay.begin()address accordingly. - Distance reads 0 or a fixed large number: the echo timeout is set to 30 000 µs, which gives a maximum range of about 2.6 m. Objects further away return no echo. Also check that TRIG is on GPIO27 and ECHO is on GPIO26 — swapping them is a common wiring mistake.
- Readings are very noisy or jump around: aim the sensor at a flat surface and keep it level. Foam, fabric, and angled surfaces absorb or scatter ultrasonic pulses. The exponential smoothing filter (0.75 × old + 0.25 × new) reduces jitter but cannot compensate for unreliable echoes.
- All LEDs light at once or never change: confirm each LED anode goes through its own resistor to the correct GPIO. Check that cathodes go to GND, not 3V3.
- Buzzer makes no sound in the stop zone: confirm GPIO25 connects to the SIG pin of a passive buzzer. An active buzzer (one with a built-in oscillator) will not respond to
tone(). You can test the GPIO with a simpledigitalWrite(25, HIGH)— an active buzzer will sound; a passive one will not. - ESP32 resets during testing: if you are driving the LEDs without resistors, the combined current may pull down the 3V3 rail. Add a 220 Ω resistor in series with each LED anode.
Going further
The sensor and threshold logic in this build transfers directly to a rover or any chassis where distance affects motion — the same three-zone pattern can drive PWM motor speed reduction instead of LEDs. Adding a second HC-SR04P facing sideways gives a rudimentary corner warning. For permanent installation in a garage, replacing the breadboard with a small PCB and adding a 5 V supply via a USB phone charger makes the build self-contained.
Wiring diagram
Components needed
| Component | Type | Qty | Buy |
|---|---|---|---|
| HC-SR04 Ultrasonic Sonar Distance Sensor + 2 x 10K resistors | sensor | 1 | $3.95 |
| Grove OLED Display 0.66" (SSD1306) | display | 1 | $5.50 |
| 3mm LED Pack (50 PCS) | actuator | 1 | $3.45 |
| 3mm LED Pack (50 PCS) | actuator | 1 | $3.45 |
| 3mm LED Pack (50 PCS) | actuator | 1 | $3.45 |
| Piezo Buzzer | actuator | 1 | $1.50 |
Supplier links, prices, and availability are shown as a guide and may change. Schematik may earn a commission from purchases made through affiliate links.
Assembly
Wire the distance sensor
Connect the HC-SR04P VCC and GND to the ESP32 3V3 and GND rails, then connect TRIG to GPIO27 and ECHO to GPIO26.
- Use the HC-SR04P 3.3V-compatible module, not a 5V-only HC-SR04, unless you add proper level shifting.
- Do not connect a 5V echo signal directly to an ESP32 GPIO.
Add the display
Connect the SSD1306 OLED to the same 3V3 and GND rails, with SDA on GPIO21 and SCL on GPIO22.
- If the OLED stays blank, check whether your module uses address 0x3C or 0x3D.
Add the warning outputs
Connect the green LED to GPIO14, yellow LED to GPIO12, red LED to GPIO13, and the passive buzzer signal pin to GPIO25. Each LED should use a current-limiting resistor.
- Place the LEDs left to right as green, yellow, red so the warning pattern reads naturally.
- Do not drive bare LEDs directly from GPIO without resistors.
Upload and test against a wall
Upload the sketch, aim the sensor at a flat surface, and move it from about one metre away to the stop zone. The OLED, LEDs, and buzzer should change together.
- Cardboard, foam board, or a book makes a better test target than a thin chair leg.
- This is a learning aid, not a safety-rated vehicle parking system.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| 3V3 | hc-sr04p-ultrasonic-sensor_0 VCC | POWER |
| GND | hc-sr04p-ultrasonic-sensor_0 GND | GROUND |
| GPIO 27 | hc-sr04p-ultrasonic-sensor_0 TRIG | DIGITAL |
| GPIO 26 | hc-sr04p-ultrasonic-sensor_0 ECHO | DIGITAL |
| 3V3 | ssd1306-oled_0 VCC | POWER |
| GND | ssd1306-oled_0 GND | GROUND |
| GPIO 21 | ssd1306-oled_0 SDA | I2C |
| GPIO 22 | ssd1306-oled_0 SCL | I2C |
| GPIO 14 | green-led_0 ANODE | DIGITAL |
| GND | green-led_0 CATHODE | GROUND |
| GPIO 12 | yellow-led_0 ANODE | DIGITAL |
| GND | yellow-led_0 CATHODE | GROUND |
| GPIO 13 | red-led_0 ANODE | DIGITAL |
| GND | red-led_0 CATHODE | GROUND |
| GPIO 25 | passive-buzzer_0 SIG | DIGITAL |
| GND | passive-buzzer_0 GND | GROUND |
Code
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define TRIG_PIN 27
#define ECHO_PIN 26
#define GREEN_LED_PIN 14
#define YELLOW_LED_PIN 12
#define RED_LED_PIN 13
#define BUZZER_PIN 25
#define SDA_PIN 21
#define SCL_PIN 22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
const int SAFE_DISTANCE_CM = 70;
const int CAUTION_DISTANCE_CM = 35;
const int STOP_DISTANCE_CM = 18;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
float smoothedDistance = 100;
unsigned long lastBeepMs = 0;
bool buzzerOn = false;
float readDistanceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) return smoothedDistance;
return duration / 58.0;
}
void setOutputs(float distanceCm) {
bool safe = distanceCm > SAFE_DISTANCE_CM;
bool caution = distanceCm <= SAFE_DISTANCE_CM && distanceCm > CAUTION_DISTANCE_CM;
bool warning = distanceCm <= CAUTION_DISTANCE_CM;
digitalWrite(GREEN_LED_PIN, safe ? HIGH : LOW);
digitalWrite(YELLOW_LED_PIN, caution ? HIGH : LOW);
digitalWrite(RED_LED_PIN, warning ? HIGH : LOW);
if (!warning) {
noTone(BUZZER_PIN);
buzzerOn = false;
return;
}
int interval = distanceCm <= STOP_DISTANCE_CM ? 120 : 320;
if (millis() - lastBeepMs > interval) {
lastBeepMs = millis();
buzzerOn = !buzzerOn;
if (buzzerOn) tone(BUZZER_PIN, distanceCm <= STOP_DISTANCE_CM ? 1800 : 1200);
else noTone(BUZZER_PIN);
}
}
void drawDisplay(float distanceCm) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Parking sensor");
display.setTextSize(2);
display.setCursor(0, 18);
display.print(distanceCm, 0);
display.println(" cm");
display.setTextSize(1);
display.setCursor(0, 48);
if (distanceCm <= STOP_DISTANCE_CM) display.println("STOP");
else if (distanceCm <= CAUTION_DISTANCE_CM) display.println("Slow down");
else if (distanceCm <= SAFE_DISTANCE_CM) display.println("Getting close");
else display.println("Clear");
display.display();
}
void setup() {
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
pinMode(YELLOW_LED_PIN, OUTPUT);
pinMode(RED_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
Wire.begin(SDA_PIN, SCL_PIN);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
}
void loop() {
float reading = readDistanceCm();
smoothedDistance = (smoothedDistance * 0.75) + (reading * 0.25);
setOutputs(smoothedDistance);
drawDisplay(smoothedDistance);
delay(80);
}
// Run this and build other cool things at schematik.ioReady to build this?
Open this project in Schematik to get the full wiring diagram, pin assignments, and deployable code for the ESP32 Ultrasonic Parking Sensor.
Open in Schematik →