The standard HC-SR04 ultrasonic sensor outputs a 5V logic high on its Echo pin. The ESP32-WROOM-32 GPIO pins are strictly 3.3V tolerant. Connecting the Echo pin directly to an ESP32 will backfeed 5V into the silicon, eventually bricking the GPIO pad or causing phantom brownout resets. To interface an HC-SR04 with an ESP32 safely, you must step down the Echo signal using a resistor voltage divider, or switch to a 3.3V-native variant like the HC-SR04P.
This guide walks through the exact hardware protection required, provides a pinout for the ESP32 DevKit V1, and delivers production-ready C++ code with timeout handling and acoustic glitch filtering.
The 5V vs 3.3V Logic Trap: Sensor Variants Compared
Before wiring your breadboard, verify exactly which ultrasonic module you have in your bin. The market is flooded with clones that look identical but use entirely different internal ICs and logic levels. Below is a data-dense breakdown of the most common 40kHz ultrasonic modules and their direct compatibility with the ESP32.
| Module Variant | Operating Voltage | Echo Logic High | Max Range | ESP32 Direct Connect? |
|---|---|---|---|---|
| HC-SR04 (Standard) | 5.0V DC | ~5.0V | 400 cm | NO (Requires voltage divider) |
| HC-SR04P | 3.3V to 5.0V | Matches VCC | 400 cm | YES (Run at 3.3V) |
| JSN-SR04T | 5.0V DC | ~5.0V | 450 cm | NO (Requires voltage divider) |
| RCWL-1601 | 3.3V to 5.0V | Matches VCC | 300 cm | YES (Run at 3.3V) |
Source data derived from manufacturer datasheets and independent logic-analyzer bench tests. If your module lacks the 'P' suffix or an 'RCWL' designation, assume it outputs 5V on the Echo pin.
Hardware Build: Parts, Pinout, and the Voltage Divider
For this build, we are targeting the ubiquitous ESP32-WROOM-32 DevKit V1 (30-pin variant) and the standard 5V HC-SR04.
- 1x ESP32 DevKit V1 (30-pin, ESP32-WROOM-32 module)
- 1x HC-SR04 Ultrasonic Sensor (4-pin)
- 1x 1kΩ Resistor (1/4W, 5% tolerance or better)
- 1x 2kΩ Resistor (1/4W, 5% tolerance or better. Note: If you lack a 2kΩ, two 1kΩ resistors in series will work perfectly.)
- 1x Half-size breadboard and male-to-female / male-to-male jumper wires
Pin Mapping Table
| HC-SR04 Pin | ESP32 DevKit V1 Pin | Notes / Routing |
|---|---|---|
| VCC | VIN (5V) | Do not use 3V3. The 40kHz burst draws ~15mA, which can sag the ESP32's onboard AMS1117 3.3V regulator. |
| GND | GND | Connect to the main ground rail. |
| Trig | GPIO 5 | Direct connection. ESP32 3.3V output is sufficient to trigger the HC-SR04's 5V logic input. |
| Echo | GPIO 18 | MUST route through the voltage divider (see steps below). |
Wiring the Voltage Divider (Step-by-Step)
The voltage divider steps the 5V Echo pulse down to a safe 3.33V. The formula is Vout = Vin × (R2 / (R1 + R2)). With a 1kΩ R1 and 2kΩ R2, 5V × (2000 / 3000) = 3.33V. This sits perfectly within the ESP32's VIH (Voltage Input High) threshold.
- Insert the 1kΩ resistor into the breadboard. Connect one leg to the HC-SR04 Echo pin wire.
- Insert the 2kΩ resistor so that one leg shares the same breadboard row as the free leg of the 1kΩ resistor.
- Connect the free leg of the 2kΩ resistor to the breadboard ground rail (GND).
- Run a jumper wire from the shared junction (where the 1kΩ and 2kΩ resistors meet) directly to GPIO 18 on the ESP32.
- Verify the physical connections before applying USB power.
Bulletproof ESP32 Code with Error Handling
The HC-SR04 is notorious for returning '0' or wildly inaccurate readings due to acoustic multipath reflections (sound bouncing off adjacent walls) and transducer ringing. The code below implements a 10µs trigger pulse, a strict pulseIn() timeout, and a 5-sample moving average filter to smooth out acoustic glitches.
Target Board: ESP32 Dev Module (Arduino IDE). Ensure you have the Espressif ESP32 Core installed via Board Manager.
// HC-SR04 to ESP32 Bulletproof Distance Reader
// Target: ESP32 DevKit V1 (30-pin)
#define TRIG_PIN 5
#define ECHO_PIN 18
#define MAX_DISTANCE 400 // HC-SR04 max range in cm
#define TIMEOUT_US 25000 // ~400cm at 343m/s = 23.3ms. Set timeout to 25ms.
#define SAMPLE_SIZE 5
float distanceSamples[SAMPLE_SIZE];
int sampleIndex = 0;
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// Initialize sample array
for (int i = 0; i < SAMPLE_SIZE; i++) {
distanceSamples[i] = 0.0;
}
// Allow sensor to stabilize
delay(500);
Serial.println("HC-SR04 ESP32 Initialized.");
}
void loop() {
float currentDistance = readUltrasonicDistance();
// Error handling for timeout / out of bounds
if (currentDistance < 0) {
Serial.println("Error: pulseIn timeout or acoustic blind spot (<2cm).");
} else if (currentDistance > MAX_DISTANCE) {
Serial.println("Error: Object out of max range (>400cm).");
} else {
// Update moving average buffer
distanceSamples[sampleIndex] = currentDistance;
sampleIndex = (sampleIndex + 1) % SAMPLE_SIZE;
float averageDistance = 0;
for (int i = 0; i < SAMPLE_SIZE; i++) {
averageDistance += distanceSamples[i];
}
averageDistance /= SAMPLE_SIZE;
Serial.print("Smoothed Distance: ");
Serial.print(averageDistance, 1);
Serial.println(" cm");
}
delay(60); // HC-SR04 needs ~60ms between readings to avoid echo tail overlap
}
float readUltrasonicDistance() {
// 1. Clear the trigger pin
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
// 2. Send 10µs high pulse to trigger the 8-cycle 40kHz burst
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 3. Read the echo pin, returning the pulse travel time in microseconds
unsigned long duration = pulseIn(ECHO_PIN, HIGH, TIMEOUT_US);
// 4. Handle timeout (returns 0 if no pulse detected within TIMEOUT_US)
if (duration == 0) {
return -1.0;
}
// 5. Calculate distance (Speed of sound = 343 m/s -> 0.0343 cm/µs. Divide by 2 for round trip)
float distance = (duration * 0.0343) / 2.0;
return distance;
}
Debugging: Why Your Readings Are Stuck at 0cm or 400cm
When troubleshooting embedded sensors, vague advice like 'check your wiring' wastes time. If your Serial Monitor is misbehaving, look for the exact error strings and follow this ranked diagnostic path.
Symptom: Serial Monitor spams "Error: pulseIn timeout" or "Distance: 0 cm"
The pulseIn() function returns 0 when the Echo pin never goes HIGH within the timeout window. This means the ESP32 sent the trigger, but never received the acoustic return signal.
The First Three Things to Check:
- Verify the Voltage Divider Output with a DMM: Disconnect the Echo wire from GPIO 18. Power the circuit. Set your multimeter to DC Voltage. Measure the voltage at the junction of your 1kΩ and 2kΩ resistors while the sensor is pointed at a wall. If it reads 0V, your HC-SR04 is dead or unpowered. If it reads 5V, your 2kΩ ground connection is broken, and you are currently feeding 5V into the air (which is lucky for your ESP32).
- Check for USB Cable Voltage Drop (Brownout): The HC-SR04 draws a spike of current during the 40kHz transmission. Cheap, thin USB cables suffer from voltage drop, causing the ESP32's 5V rail to sag below 4.5V. The HC-SR04 will fail to trigger. Swap to a heavy-gauge, data-rated USB cable and monitor the ESP32's 5V pin with your meter during a read cycle.
- Clear the Acoustic Blind Spot: The HC-SR04 transducers experience 'ringing' (mechanical vibration persistence) immediately after transmitting. The sensor cannot listen for an echo while it is still ringing. This creates a hard acoustic blind spot from 0cm to roughly 3cm. If your target is closer than 3cm, the sensor will time out and return 0. Move the target back to 10cm and re-test.
Symptom: Readings are locked at exactly 400.0 cm
If your code lacks a timeout parameter in pulseIn(), or if your physical environment is highly reflective (like an empty plastic water tank), the 40kHz sound wave can bounce endlessly. The ESP32 GPIO stays HIGH indefinitely, and the math overflows or maxes out. Ensure the TIMEOUT_US parameter is strictly enforced in your code as shown above to force the function to abort after 25 milliseconds.
Extending and Simplifying the Build
Once you have stable distance readings on the Serial Monitor, you have two paths forward depending on your project goals.
How to Simplify the Hardware
If you are designing a custom PCB or want to eliminate the breadboard voltage divider entirely, buy the HC-SR04P or the RCWL-1601. Both modules feature an internal voltage regulator and logic-level translation IC. You can power them directly from the ESP32's 3V3 pin and wire the Echo pin straight to any GPIO with zero risk of silicon damage. The HC-SR04P costs roughly $0.50 more per unit on AliExpress or Amazon, which is a massive time-saver for production builds.
How to Extend the Functionality
To turn this bench test into an IoT smart-home sensor (e.g., monitoring a basement sump pit or a rainwater cistern):
- Add MQTT Telemetry: Include the
PubSubClientlibrary. Connect the ESP32 to your local WiFi and publish theaverageDistancevariable to an MQTT broker (like Mosquitto or Home Assistant) every 60 seconds. - Add Deep Sleep: Ultrasonic sensors drain batteries quickly. Use the ESP32's
esp_sleep_enable_timer_wakeup()API to put the board into deep sleep for 15 minutes between readings. Crucial detail: You must pull the HC-SR04 VCC pin via a P-channel MOSFET controlled by an ESP32 GPIO to fully cut power to the sensor during sleep, otherwise the sensor's quiescent current (2mA) will still drain your 18650 cells. - Upgrade to I2C Display: Wire an SSD1306 0.96-inch OLED display to GPIO 21 (SDA) and GPIO 22 (SCL) using the
Adafruit_SSD1306library to display the smoothed distance locally without needing a PC.






