The Verdict: Is the HC-SR04 the Right Choice?
If you need basic 2cm to 400cm air-gap ranging for under $2 in a clean, indoor environment, the HC-SR04 ultrasonic sensor with Arduino is the default pick. It is the undisputed king of hobbyist proximity detection. However, if you need sub-millimeter precision, operation in dusty/wet conditions, or detection through solid objects, you need to look at Time-of-Flight (ToF) or microwave alternatives.
Use this decision matrix to terminate your component search and pick the exact part number for your build:
| Criteria | HC-SR04 (Ultrasonic) | VL53L0X (Laser ToF) | RCWL-0516 (Microwave) |
|---|---|---|---|
| Effective Range | 2 cm – 400 cm | 3 cm – 200 cm | 500 cm – 700 cm |
| Precision | ~3 mm (temp dependent) | ~1 mm | ~50 mm |
| Beam Angle | 15° (Wide cone) | 25° (Focused IR) | 360° (Omnidirectional) |
| Environment | Clean air, fails in dust/foam | Dusty/dark, fails in direct sun | Through walls, dust, rain |
| Typical Cost | $1.50 | $4.00 | $1.00 |
Hardware Spec Sheet & Exact Parts List
Difficulty: Beginner | Time to Complete: 20 Minutes
Before wiring, verify your exact module variant. Cheap clones often use the LM393 comparator instead of the original MAX232 level-shifter chip, which slightly alters the analog threshold but works identically with digital Arduino pins.
| Parameter | Specification |
|---|---|
| Operating Voltage | 5V DC (4.5V to 5.5V acceptable) |
| Quiescent Current | < 2mA |
| Working Current | ~15mA (during 40kHz burst) |
| Resonant Frequency | 40 kHz |
| Logic Level (Echo) | 5V TTL (Requires divider for 3.3V boards) |
Required Parts
- Microcontroller: Arduino Uno R3 or Nano v3 (ATmega328P, 5V logic)
- Sensor: HC-SR04 Ultrasonic Module
- Wiring: 4x Male-to-Male jumper wires (22 AWG solid core)
- Prototyping: Half-size solderless breadboard
- Optional (for ESP32/RPi): 10kΩ and 20kΩ resistors for logic level voltage divider
Pin Mapping & Wiring Steps
The HC-SR04 uses a simple two-wire synchronous protocol. You send a 10µs HIGH pulse on the Trigger pin, and the module responds by holding the Echo pin HIGH for a duration proportional to the distance.
| HC-SR04 Pin | Arduino Uno R3 Pin | Wire Color (Standard) |
|---|---|---|
| VCC | 5V | Red |
| Trig | D9 | Yellow |
| Echo | D10 | Blue |
| GND | GND | Black |
Wiring Procedure
- De-energize the board: Unplug the Arduino USB cable before wiring to prevent accidental short circuits on the breadboard.
- Connect Power: Insert the HC-SR04 into the breadboard. Run the red jumper from the sensor VCC to the Arduino 5V pin. Run the black jumper from sensor GND to Arduino GND.
- Connect Signal: Run the yellow jumper from Trig to Digital Pin 9. Run the blue jumper from Echo to Digital Pin 10.
- Verify: Tug gently on the jumper wires at the breadboard edge to ensure solid friction contact. Poor breadboard contacts are the #1 cause of intermittent '0 cm' readings.
Complete Compilable Arduino Code
This code targets the Arduino Uno R3 and Nano v3 (ATmega328P). We are using the NewPing library instead of the default pulseIn() function. The native pulseIn() is a blocking function that halts your entire sketch while waiting for an echo, which can cause timeouts and freeze your robot. NewPing uses timer interrupts and includes a built-in median filter to reject acoustic outliers.
Prerequisite: Install the 'NewPing' library via the Arduino IDE Library Manager (Sketch > Include Library > Manage Libraries > Search 'NewPing').
#include <NewPing.h>
// --- PIN DEFINITIONS ---
#define TRIGGER_PIN 9
#define ECHO_PIN 10
#define MAX_DISTANCE 400 // Maximum distance we want to ping (in cm)
// Initialize NewPing object
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
void setup() {
Serial.begin(115200);
// Wait for serial port to connect (harmless on Uno, necessary for Leonardo/Micro)
while (!Serial && millis() < 2500);
Serial.println("HC-SR04 NewPing Initialized. Median filter active.");
}
void loop() {
// Wait 35ms between pings. 29ms is the absolute minimum to prevent
// the previous 40kHz echo from overlapping with the next ping.
delay(35);
// ping_median(5) sends 5 pings and returns the median distance.
// This filters out 'ghost' echoes from acoustic reflections.
unsigned int distance = sonar.ping_median(5);
// Error handling: NewPing returns 0 if the ping times out or is out of range
if (distance == 0) {
Serial.println("Error: Out of range or Ping Timeout (0 cm)");
} else {
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm");
}
}
The speed of sound in air is not a static 343 m/s; it changes with temperature. The formula is
v = 331.4 + (0.6 * Temperature_in_C). At 0°C, sound travels ~331 m/s. At 30°C, it travels ~349 m/s. If your environment fluctuates by 20°C, your HC-SR04 will have a built-in ~5% measurement drift. For high-precision builds, add a DS18B20 temperature sensor and apply this math to the microsecond duration before converting to centimeters.
Debugging: Fixing '0 cm' and Timeout Errors
When the HC-SR04 fails, it almost always fails silently by returning a zero. If your serial monitor is spamming the exact error string Error: Out of range or Ping Timeout (0 cm), or if your sketch freezes entirely, follow this ranked troubleshooting path.
The First 3 Things to Check
- Shared Ground Reference: The sensor and the Arduino must share the exact same GND rail. If you are powering the sensor from a separate 5V breadboard supply, you must run a wire connecting the supply GND to the Arduino GND. Without a shared ground, the Echo signal has no reference voltage.
- Trig/Echo Pin Swap: The silkscreen on cheap HC-SR04 clones is sometimes misprinted. If you get 0 cm, physically swap the yellow and blue wires on the Arduino side and update the
#definepins in your code. - Power Supply Ripple: The 40kHz transducers draw a sudden 15mA spike when firing. If powered from a weak USB hub or a long, thin breadboard power rail, the voltage dips, resetting the module's internal logic. Measure the VCC pin with a multimeter while the code is running; it should not drop below 4.5V.
Ranked Causes for Specific Failure Modes
| Symptom / Error String | Most Likely Cause | The Fix |
|---|---|---|
Serial prints 0 cm constantly |
Target is < 2cm or > 400cm, or Echo pin is dead. | Move target to 20cm. If still 0, test Echo pin with multimeter (should pulse 5V). |
| Readings jump wildly (e.g., 15cm to 120cm) | Acoustic crosstalk or soft target absorbing 40kHz waves. | Increase delay() to 50ms. Ensure target is hard/flat (wood/plastic, not foam/cloth). |
| Code hangs/freezes completely | Using native pulseIn() without a timeout parameter. |
Switch to the NewPing library provided above, or add timeout: pulseIn(ECHO, HIGH, 30000). |
| ESP32 crashes or reboots | 5V Echo signal backfeeding into 3.3V GPIO pin. | Install a 10k/20k voltage divider on the Echo line immediately. |
Extending or Simplifying the Build
Depending on your project constraints, you may need to strip this build down to bare metal or scale it up to a multi-sensor array.
How to Simplify (No Library Required)
If you are constrained by flash memory (e.g., using an ATtiny85) and cannot afford the 2KB overhead of the NewPing library, you can use the native Arduino pulseIn() function. However, you must include a timeout parameter to prevent the CPU from hanging if no echo returns.
// Simplified blocking method (Use only if memory is strictly limited)
pinMode(TRIGGER_PIN, OUTPUT);
digitalWrite(TRIGGER_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER_PIN, LOW);
pinMode(ECHO_PIN, INPUT);
// 30000µs timeout prevents infinite blocking
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
float distance_cm = duration * 0.034 / 2;
How to Extend (Multi-Sensor Arrays)
The HC-SR04 is notorious for 'crosstalk'—where Sensor A hears the echo from Sensor B's ping. If you are building a 360-degree robot bumper with 4+ sensors, do not fire them simultaneously.
NewPing supports up to 15 sensors natively. Create an array of NewPing objects and poll them sequentially with a 35ms delay between each. For advanced builds, consider hardware multiplexing: wire all Echo pins to a single Arduino input, and use a 74HC4051 multiplexer to route the signals, saving valuable GPIO pins on your microcontroller.






