The HC-SR04 is one of the most popular ultrasonic distance sensors in the maker world — cheap, accurate to within a few millimetres, and dead simple to wire. Pair it with a 16x2 LCD display and an ESP32, and you have a compact, self-contained distance meter that shows live readings without needing a computer or serial monitor. This is a great beginner project that teaches you PWM timing, I2C displays, and clean real-time output all in one build.
Components for This Project
| Component | Shop |
|---|---|
| ESP32 DEVKIT (WROOM 32) | View Product |
| HC-SR04 Ultrasonic Sensor | View Product |
| LCD 1602 I2C Display | View Product |
| I2C Serial Interface Board (for parallel LCD) | View Product |
How the HC-SR04 Works
Before writing any code, it helps to understand what is happening at the hardware level.
The HC-SR04 uses sound to measure distance — the same principle as a bat or a car parking sensor. Here is the sequence:
- Your microcontroller sends a short 10 µs HIGH pulse to the TRIG pin
- The sensor fires a burst of 8 ultrasonic pulses at 40 kHz
- The sound bounces off the nearest object and returns to the sensor
- The sensor holds the ECHO pin HIGH for exactly as long as the round-trip took
- You measure that pulse duration and convert it to distance
The conversion formula uses the speed of sound (~343 m/s at 20°C):
textcode1distance (cm) = pulse duration (µs) / 58.0
Or equivalently:
textcode1distance (cm) = (pulse duration (µs) * 0.0343) / 2
Effective range: 2 cm to 400 cm. Objects closer than 2 cm may not return a reliable echo. Objects further than 4 m or with soft/angled surfaces (like fabric or carpet at 45°) can give inconsistent readings.
What You Will Need
| Component | Notes |
|---|---|
| ESP32 Development Board | DEVKIT V1 DOIT or any 30/36-pin variant |
| HC-SR04 Ultrasonic Sensor | 4-pin module (VCC, TRIG, ECHO, GND) |
| LCD 1602 with I2C Backpack | The I2C version saves 6 wires vs parallel wiring |
| Breadboard | Full-size or half-size |
| Jumper Wires | Male-to-male and male-to-female |
| USB Cable | Data cable for programming |
Why the I2C LCD? The standard LCD 1602 without I2C requires 6 data pins plus power — nearly half your ESP32's GPIO pins just for a display. The I2C backpack solves this with just 2 data wires (SDA + SCL). If you only have a parallel LCD, you can add an PCF8574 I2C expander module for under RM 2.
Wiring Guide
HC-SR04 to ESP32
The ECHO pin outputs 5V on many HC-SR04 modules. The ESP32 GPIO pins are 3.3V tolerant only. Use a voltage divider (2x resistors) or a logic level shifter to protect the ESP32.
Simple voltage divider for ECHO (1kΩ + 2kΩ):
textcode1HC-SR04 ECHO ──── 1kΩ ──── ESP32 GPIO (safe 3.3V) 2 | 3 2kΩ 4 | 5 GND
| HC-SR04 Pin | Connect To |
|---|---|
| VCC | 5V (VIN on ESP32 dev board) |
| GND | GND |
| TRIG | GPIO 5 |
| ECHO | GPIO 18 (via voltage divider) |
LCD 1602 I2C to ESP32
| LCD I2C Pin | ESP32 Pin |
|---|---|
| VCC | 5V (VIN) |
| GND | GND |
| SDA | GPIO 21 |
| SCL | GPIO 22 |
I2C Address: Most PCF8574-based I2C LCD modules use address
. Some usetextcode10x27. If the display stays blank after uploading, trytextcode10x3Fin the code.textcode10x3F
Installing Required Libraries
You need two libraries. Install both via Arduino IDE > Tools > Manage Libraries:
| Library | Search For | Author |
|---|---|---|
| LiquidCrystal I2C | textcode | Frank de Brabander |
| No extra library needed for HC-SR04 | Uses built-in textcode | — |
The Code
cppcode1#include <Wire.h> 2#include <LiquidCrystal_I2C.h> 3 4// ── Pin Definitions ─────────────────────────────────────────────────────────── 5#define TRIG_PIN 5 // HC-SR04 trigger 6#define ECHO_PIN 18 // HC-SR04 echo (use voltage divider from 5V to 3.3V) 7 8// ── LCD Setup ───────────────────────────────────────────────────────────────── 9// Parameters: I2C address, columns, rows 10// Try 0x3F if 0x27 does not work 11LiquidCrystal_I2C lcd(0x27, 16, 2); 12 13// ── Measure distance in centimetres ─────────────────────────────────────────── 14float measureDistance() { 15 // Send 10 µs trigger pulse 16 digitalWrite(TRIG_PIN, LOW); 17 delayMicroseconds(2); 18 digitalWrite(TRIG_PIN, HIGH); 19 delayMicroseconds(10); 20 digitalWrite(TRIG_PIN, LOW); 21 22 // Read echo pulse duration (timeout = 30 ms → ~5 m max) 23 long duration = pulseIn(ECHO_PIN, HIGH, 30000); 24 25 if (duration == 0) return -1; // no echo received (out of range) 26 27 return duration / 58.0; // convert to cm 28} 29 30void setup() { 31 Serial.begin(115200); 32 33 pinMode(TRIG_PIN, OUTPUT); 34 pinMode(ECHO_PIN, INPUT); 35 36 // Initialise LCD 37 lcd.init(); 38 lcd.backlight(); 39 40 // Splash screen 41 lcd.setCursor(0, 0); 42 lcd.print(" Distance Meter"); 43 lcd.setCursor(0, 1); 44 lcd.print(" HC-SR04 + ESP32"); 45 delay(2000); 46 lcd.clear(); 47} 48 49void loop() { 50 float distance = measureDistance(); 51 52 // ── Print to Serial Monitor ──────────────────────────────────────────────── 53 if (distance < 0) { 54 Serial.println("Out of range"); 55 } else { 56 Serial.printf("Distance: %.1f cm\n", distance); 57 } 58 59 // ── Update LCD ───────────────────────────────────────────────────────────── 60 lcd.setCursor(0, 0); 61 lcd.print("Distance: "); // trailing spaces clear old characters 62 63 lcd.setCursor(0, 1); 64 if (distance < 0 || distance > 400) { 65 lcd.print("Out of range "); 66 } else { 67 // Format: "123.4 cm " 68 String display = String(distance, 1) + " cm"; 69 // Pad to 16 chars to overwrite previous longer values 70 while (display.length() < 16) display += " "; 71 lcd.print(display); 72 } 73 74 delay(200); // update 5 times per second 75}
Understanding the Code
The textcode1measureDistance()
Function
1measureDistance()This is the heart of the project. It drives TRIG LOW briefly to clean up any noise, then pulls it HIGH for 10 µs to fire the ultrasonic burst.
1pulseIn(ECHO_PIN, HIGH, 30000)Clearing Old Characters on the LCD
LCD displays do not auto-clear when you write shorter text over longer text. For example, writing
1"9.5 cm"1"123.4 cm"1"9.5 cmcm"The Update Rate
1delay(200)1delay(50)1delay(100)Serial Monitor Output
Open Tools > Serial Monitor at 115200 baud and press the RESET button. You should see:
textcode1Distance: 14.3 cm 2Distance: 14.4 cm 3Distance: 14.2 cm 4Distance: 14.3 cm 5Out of range 6Distance: 28.7 cm
This is useful for debugging — if the LCD shows garbage but the Serial Monitor looks correct, the issue is with the LCD address or wiring.
Calibration and Accuracy Tips
The HC-SR04 is reasonably accurate for most DIY purposes, but there are a few things that affect readings:
| Factor | Effect | Fix |
|---|---|---|
| Temperature | Speed of sound changes ~0.6 m/s per °C | Add a temperature sensor (DS18B20) and use textcode |
| Surface angle | Angled or soft surfaces absorb/deflect ultrasound | Works best on flat, hard, perpendicular surfaces |
| Fast movement | Object moves during echo travel | Average 3–5 readings for stability |
| Electrical noise | Long TRIG/ECHO wires act as antennas | Keep wires short; add 100 nF ceramic capacitor across VCC/GND on the sensor |
| Multiple sensors | Sensors interfere with each other | Trigger them one at a time with at least 60 ms between triggers |
Averaging for Stability
For smoother readings, take multiple samples and average them:
cppcode1float stableDistance() { 2 const int SAMPLES = 5; 3 float total = 0; 4 int valid = 0; 5 for (int i = 0; i < SAMPLES; i++) { 6 float d = measureDistance(); 7 if (d > 0 && d <= 400) { 8 total += d; 9 valid++; 10 } 11 delay(15); 12 } 13 return valid > 0 ? total / valid : -1; 14}
Replace
1measureDistance()1stableDistance()Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| LCD shows nothing | Wrong I2C address or no backlight power | Try textcode |
| LCD shows squares/blocks | Display initialised but no characters sent | Check SDA/SCL wiring; confirm I2C address with I2C scanner sketch |
| Distance always 0 or -1 | ECHO pin not receiving signal | Check TRIG and ECHO pins are not swapped; check voltage divider |
| Readings wildly inconsistent | ECHO is at 5V directly on ESP32 GPIO | Add voltage divider — ESP32 GPIO are not 5V tolerant |
| Readings too short | Object further than 4 m, or surface absorbing sound | Move object closer; test with a flat hard wall |
| ESP32 resets randomly | Insufficient power | Use a powered USB hub or add a 10 µF capacitor on VIN/GND |
Taking It Further
Once your distance meter is working, there are many directions to expand it:
- Proximity alert — add a buzzer that beeps faster as objects get closer, like a car parking sensor
- Water level monitor — mount the sensor facing down into a tank; calculate water level from textcode
1tank_height - distance - People counter — place the sensor in a doorway and count when the reading drops below a threshold
- Robot obstacle avoidance — mount on a motor car chassis and steer away when distance drops below 20 cm
- Wi-Fi dashboard — use the ESP32's Wi-Fi to POST readings to a local Node-RED or web dashboard every second
- Temperature-compensated distance — add a DS18B20 for accurate speed-of-sound correction
Wrapping Up
You have built a complete real-time distance meter using an HC-SR04 ultrasonic sensor, a 1602 LCD display, and an ESP32. Along the way you learned how ultrasonic ranging works, how to protect ESP32 GPIO pins from 5V signals, how to drive an I2C LCD, and how to keep display output clean and readable.
Browse our HC-SR04 sensors, LCD displays, and ESP32 boards in the store — all the components for this project ship together.
Happy building!
