The Sensing Principle: How the HC-SR04 Actually Works
The HC-SR04 measures distance using acoustic time-of-flight. When triggered, the onboard transmitter emits an eight-cycle burst of 40kHz ultrasonic sound. This pulse travels through the air, strikes a target object, and reflects back to the module's receiver transducer. The sensor's internal comparator circuit detects the returning echo and drives the Echo pin HIGH for the exact duration of the sound's round trip.
Because the speed of sound in air is relatively constant at a given temperature, measuring the width of that HIGH pulse in microseconds provides a direct proxy for distance. The module handles the analog acoustic amplification and threshold detection internally, meaning your microcontroller only needs to generate a 10-microsecond trigger pulse and time the resulting digital echo pulse. It is a strictly digital time-domain measurement, not an analog voltage scaling.
Hardware Specs and Wiring Pinout
The HC-SR04 is a 5V native device. While it is often plugged directly into 5V Arduino boards (like the Uno or Mega), connecting it directly to a 3.3V logic board like the ESP32 or Raspberry Pi Pico without level shifting will eventually degrade or destroy the microcontroller's GPIO pin due to the 5V echo return.
| Pin | Function | Electrical Characteristics | Microcontroller Connection |
|---|---|---|---|
| VCC | Power Supply | 5V DC (Operating range: 4.5V - 5.5V) | 5V pin on Arduino/ESP32 |
| Trig | Trigger Input | Accepts 3.3V or 5V logic HIGH | Any digital GPIO |
| Echo | Echo Output | Outputs 5V logic HIGH (Duration = Time of Flight) | Digital GPIO (Requires voltage divider for 3.3V boards) |
| GND | Ground | 0V Reference | System GND |
The Math: Converting Raw Echo Pulses to Centimeters
The raw output of the HC-SR04 is not a distance; it is a time value in microseconds (µs). To convert this raw reading into a physical unit, we use the fundamental kinematic equation: $Distance = \frac{Velocity \times Time}{2}$. We divide by two because the sound wave travels to the object and back (round-trip).
At standard room temperature (20°C / 68°F), the speed of sound in dry air is approximately 343 meters per second, which translates to 0.0343 centimeters per microsecond. Therefore, the baseline raw-to-unit math is:
- Centimeters:
distance_cm = (echo_time_us * 0.0343) / 2 - Inches:
distance_in = (echo_time_us * 0.0135) / 2
Calibration and Temperature Scaling: The speed of sound changes with air temperature. If your project operates in an unheated garage or outdoors, a fixed 0.0343 multiplier will introduce error. For high-precision applications, read the ambient temperature ($T$ in °C) via a sensor like a BME280, calculate the exact speed of sound ($v = 331.4 + 0.6T$ m/s), and update your multiplier dynamically. According to Engineering Toolbox acoustic data, the speed of sound shifts by roughly 0.6 m/s for every 1°C change, which equates to a ~0.17% measurement error per degree Celsius if left uncompensated.
Step-by-Step Implementation and Robust Code
The HC-SR04 is notoriously susceptible to acoustic noise and multi-path reflections, which can cause the pulseIn() function to return erratic spikes or time out completely. The following implementation includes a timeout parameter to prevent code-blocking and a simple median-filter approach to reject outlier readings.
- Wire the hardware: Connect VCC to 5V, GND to GND, Trig to GPIO 5, and Echo through a voltage divider to GPIO 18 (ESP32) or directly to GPIO 2 (Arduino Uno).
- Initialize pins: Set Trig as OUTPUT (LOW) and Echo as INPUT.
- Trigger the burst: Pull Trig LOW for 2µs (to clear it), then HIGH for 10µs, then LOW again.
- Read the echo: Use
pulseIn()with a 30,000 µs timeout (equivalent to ~5 meters max range). - Filter and calculate: Discard 0s and extreme outliers, then apply the math.
// Robust HC-SR04 Code for ESP32 / Arduino
const int trigPin = 5;
const int echoPin = 18; // Use voltage divider if ESP32!
const float speedOfSound_cm_us = 0.0343; // Adjust for temp if needed
const long timeout = 30000; // 30ms timeout prevents infinite blocking
void setup() {
Serial.begin(115200);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
digitalWrite(trigPin, LOW);
}
long getRawDistance() {
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Read pulse with timeout. Returns 0 if no echo is received.
long duration = pulseIn(echoPin, HIGH, timeout);
return duration;
}
float getFilteredDistanceCM() {
long readings[3];
for(int i=0; i<3; i++) {
readings[i] = getRawDistance();
delay(20); // 20ms settling time between pings to avoid acoustic crosstalk
}
// Simple bubble sort to find the median value (rejects extreme spikes)
for(int i=0; i<2; i++) {
for(int j=0; j<2-i; j++) {
if(readings[j] > readings[j+1]) {
long temp = readings[j];
readings[j] = readings[j+1];
readings[j+1] = temp;
}
}
}
long medianTime = readings[1];
if(medianTime == 0) return -1.0; // Return -1 to indicate timeout/out of range
return (medianTime * speedOfSound_cm_us) / 2.0;
}
void loop() {
float distance = getFilteredDistanceCM();
if(distance < 0) {
Serial.println("Out of range or timeout.");
} else {
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm");
}
delay(100);
}
Frequently Asked Questions
Does the HC-SR04 output an analog voltage or a digital signal?
The HC-SR04 outputs a strictly digital 5V pulse on the Echo pin. It does not output a variable analog voltage proportional to distance (like a Sharp IR sensor does). The microcontroller measures the time width of this digital HIGH state, not the voltage amplitude. Conflating this with analog sensors is a common mistake that leads beginners to incorrectly wire the Echo pin to an ADC (Analog-to-Digital Converter) pin and wonder why the readings make no sense. Always wire the Echo pin to a standard digital GPIO capable of reading pulse widths.
Why is my ultrasonic sensor HC-SR04 reading stuck at 0 or maxing out at 400cm?
A reading stuck at 0 means the pulseIn() function timed out before detecting a returning echo. This is usually caused by the target being out of the sensor's physical range (max ~4 meters), the target surface being highly sound-absorbent (like foam or heavy fabric), or a wiring fault on the Echo pin. Conversely, if your readings are randomly spiking to exactly 400cm or showing massive erratic jumps, you are likely experiencing acoustic crosstalk (multiple HC-SR04s firing at once) or multi-path reflections. Ensure you have a minimum 20-millisecond delay between consecutive trigger pulses to allow the acoustic ringing in the transducer to dissipate.
What causes interference and false readings on the HC-SR04?
Because the sensor relies on 40kHz sound waves, it is blind to optical interference but highly susceptible to acoustic interference. Common culprits include:
- Soft/Angled Surfaces: Acoustic foam, curtains, or targets angled more than 20 degrees away from the sensor's normal axis will scatter the sound wave, resulting in a timeout (0 reading).
- Multi-path Reflections: In tight enclosures or corners, the sound wave may bounce off a side wall before hitting the target, artificially inflating the distance reading.
- Electrical Noise: Long, unshielded jumper wires acting as antennas can pick up EMI from nearby motors or switching power supplies, tricking the internal comparator into seeing a false echo. Keep wires under 1 meter and use twisted pairs for Trig/GND and Echo/GND if extending the range.
How do I calibrate the HC-SR04 for accurate distance measurements?
Out of the box, the HC-SR04 is accurate to within ~3mm to 1cm under ideal conditions. True calibration requires addressing the speed of sound variance. Place a hard, flat target at a precisely measured distance (e.g., exactly 100.0 cm using a steel tape measure). Record the raw microsecond echo time over 50 samples. If your calculated distance consistently reads 101.5 cm, your local speed of sound multiplier is slightly off due to altitude, humidity, or temperature. Adjust your speedOfSound_cm_us constant in the code until the software output matches the physical tape measure. For production environments, integrate a digital temperature sensor and apply the $v = 331.4 + 0.6T$ formula dynamically, as documented in standard Arduino pulse timing references.






