The HC-SR04 ultrasonic sensor outputs a 5V digital pulse on its Echo pin, where the pulse width in microseconds directly corresponds to the distance of an object. It requires no analog-to-digital conversion; you simply measure the time the Echo pin stays HIGH. Unlike analog infrared sensors that output a varying voltage, or industrial ultrasonic transducers that use a 4-20mA current loop, the HC-SR04 relies entirely on digital timing, making it trivial to interface with any microcontroller capable of measuring pulse widths.
HC-SR04 Specifications and Pinout
Before wiring the module, you need to understand its electrical boundaries. The HC-SR04 is strictly a 5V device. While it will sometimes trigger at 3.3V, the Echo pin will still attempt to pull up to the VCC rail, which creates a severe overvoltage hazard for 3.3V microcontrollers like the ESP32 or Raspberry Pi Pico.
| Parameter | Min | Typical | Max | Unit |
|---|---|---|---|---|
| Working Voltage (VCC) | 4.5 | 5.0 | 5.5 | V DC |
| Quiescent Current | - | 2 | 3 | mA |
| Active Working Current | - | 15 | 20 | mA |
| Operating Frequency | - | 40 | - | kHz |
| Measuring Angle | - | 15 | - | Degrees |
| Blind Zone (Min Range) | - | 2 | - | cm |
| Max Measuring Range | - | 400 | - | cm |
| Trigger Input Signal | - | 10 | - | µs TTL Pulse |
| Pin Name | Direction | Function |
|---|---|---|
| VCC | Input | Supply voltage (must be 5V for reliable 40kHz oscillation). |
| Trig | Input | Trigger pin. Requires a minimum 10µs HIGH pulse to initiate measurement. |
| Echo | Output | Outputs a 5V HIGH pulse whose width equals the sound transit time. |
| GND | Ground | Common ground reference. |
The Physics and Math: Raw Pulse to Centimeters
The sensing principle relies on time-of-flight (ToF) acoustics. When the Trig pin receives a 10µs HIGH pulse, the module's internal oscillator drives the transmitter transducer with an 8-cycle burst at 40 kHz. This sound wave travels through the air, strikes a physical object, and reflects back to the receiver transducer. The module's internal comparator detects the returning echo and pulls the Echo pin HIGH for the exact duration of the round-trip transit.
To convert this raw microsecond reading into a physical distance, we use the speed of sound. At 20°C in dry air, sound travels at approximately 343 meters per second, which translates to 0.0343 centimeters per microsecond (cm/µs). Because the measured time includes the journey to the object and back, we must divide the total distance by two. The raw-to-unit math is:
Distance (cm) = (Echo Pulse Width in µs × 0.0343 cm/µs) / 2Which simplifies to:
Distance (cm) = Echo Pulse Width in µs / 58.31
If you prefer inches, the speed of sound is roughly 0.0135 inches/µs, making the divisor 148. No analog scaling, ADC reference voltage calibration, or current shunt resistors are needed—the microcontroller's internal hardware timer handles the microsecond counting natively via functions like pulseIn().
Wiring to Arduino and ESP32 (With Level Shifting)
Wiring the HC-SR04 to a 5V Arduino Uno or Mega is straightforward: connect the pins directly. However, connecting it to an ESP32, ESP8266, or Raspberry Pi Pico requires hardware protection. The Espressif ESP32 Datasheet explicitly limits GPIO input voltage to 3.3V. Feeding the 5V Echo signal directly into an ESP32 pin will cause long-term silicon degradation or immediate latch-up failure.
| HC-SR04 Pin | Arduino Uno (5V Logic) | ESP32 / Pico (3.3V Logic) |
|---|---|---|
| VCC | 5V Pin | 5V Pin (VIN or VBUS) |
| Trig | Any Digital Pin (e.g., D9) | Any GPIO (e.g., GPIO 5) |
| Echo | Any Digital Pin (e.g., D10) | Voltage Divider to GPIO (e.g., GPIO 18) |
| GND | GND | GND |
To safely step the 5V Echo signal down to 3.3V, build a simple resistor voltage divider. Connect a 1kΩ resistor in series with the Echo pin, and a 2kΩ resistor from the junction to GND. The microcontroller GPIO reads the junction. Using the voltage divider formula: Vout = 5V × (2000 / (1000 + 2000)) = 3.33V. This is perfectly safe for ESP32 inputs.
Interference, Calibration, and Edge Cases
The HC-SR04 is highly susceptible to environmental variables. Understanding these interference sources is the difference between a reliable tank-level monitor and a buggy prototype.
- Acoustic Cross-Talk: If you deploy multiple HC-SR04 modules in the same array, firing them simultaneously will cause the receivers to pick up adjacent transmitters. Fix: Fire them sequentially in code, waiting at least 50ms between readings to allow the 40kHz burst to dissipate.
- Target Material and Angle: The 40kHz wavelength struggles with soft, sound-absorbing materials (like foam or heavy fabric) and will deflect entirely off surfaces angled greater than 15 degrees away from the sensor's normal axis. Fix: Ensure the target surface is hard, flat, and perpendicular to the transducer.
- The Blind Zone: The module cannot distinguish the transmit burst from the receive echo if the object is closer than 2cm. Readings below 2cm will return garbage data or max out. Fix: Mount the sensor at least 3cm away from the minimum physical boundary.
- Temperature and Humidity Drift: The standard '58.3' divisor assumes 20°C dry air. According to Engineering ToolBox acoustic data, the speed of sound increases by roughly 0.6 m/s for every 1°C rise. In a 35°C greenhouse, sound travels ~352 m/s, introducing a 2.6% distance error (roughly 1cm per 40cm measured).
Calibration Strategy: For high-precision applications, do not hardcode the divisor. Instead, read the ambient temperature from a co-located sensor (like a BME280) and calculate the dynamic speed of sound in your firmware. The formula for the speed of sound in air based on temperature (T in °C) is: v = 331.4 + (0.606 × T) m/s.
Complete Code Implementation (Arduino & ESP32)
The following C++ code implements a robust reading function. It includes the 10µs trigger pulse, a hardware timeout to prevent the microcontroller from hanging if no echo is received, and dynamic temperature compensation.
// Pin Definitions (Adjust for your specific board)
const int trigPin = 9; // ESP32: e.g., GPIO 5
const int echoPin = 10; // ESP32: e.g., GPIO 18 (via voltage divider!)
// Environmental variable (Update via BME280/DHT22 in production)
float ambientTempC = 20.0;
void setup() {
Serial.begin(115200);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
digitalWrite(trigPin, LOW); // Ensure clean start state
}
void loop() {
float distanceCm = readUltrasonicDistance();
if (distanceCm > 0) {
Serial.print("Distance: ");
Serial.print(distanceCm);
Serial.println(" cm");
} else {
Serial.println("Out of range or timeout.");
}
delay(100); // 10Hz read rate prevents cross-talk and self-interference
}
float readUltrasonicDistance() {
// 1. Clear the trigger pin
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
// 2. Send 10µs HIGH pulse to trigger the 40kHz burst
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// 3. Read the Echo pin.
// Timeout set to 25000µs (approx 430cm, safely beyond the 400cm max)
unsigned long duration = pulseIn(echoPin, HIGH, 25000);
// 4. Handle timeout / blind zone
if (duration == 0 || duration < 116) { // 116µs is approx 2cm round trip
return -1.0;
}
// 5. Calculate dynamic speed of sound (cm/µs)
// v (m/s) = 331.4 + (0.606 * TempC)
// v (cm/µs) = v (m/s) / 10000
float speedOfSound_cm_us = (331.4 + (0.606 * ambientTempC)) / 10000.0;
// 6. Calculate distance (divide by 2 for round-trip)
float distance = (duration * speedOfSound_cm_us) / 2.0;
return distance;
}
pulseIn()
The standard Arduino pulseIn() function is blocking. If the Echo pin never goes HIGH (e.g., the sound wave scatters into an open void), the function will hang until the timeout expires. Always provide a timeout argument (like the 25000µs used above) to prevent your main loop from freezing. For non-blocking requirements in complex RTOS environments on the ESP32, migrate to hardware timer interrupts or the NewPing library.






