When you need to measure distance without physical contact, ultrasonic sensors are the default choice for embedded projects. But picking the wrong module for your environment or using the native pulseIn() function without timeouts will result in frozen microcontrollers and phantom readings. For 90% of standard indoor Arduino projects, the HC-SR04 (priced around $2) is the correct pick, wired to 5V logic, and driven by the NewPing library to prevent timeout hangs.
This guide cuts through the generic tutorials. We will cover exact module variants, the acoustic blind zones that cause false zeros, and a robust code implementation with median filtering to eliminate multipath noise.
The Quick Verdict: Which Ultrasonic Sensor Should You Buy?
Do not buy a sensor until you have mapped it to your physical environment. Ultrasonic transducers are highly susceptible to temperature, humidity, and acoustic dampening. Use this decision path to select the exact part number you need.
| Environment & Requirement | Recommended Module | Why This Pick? |
|---|---|---|
| Indoor, clean air, robotics, tank levels (<4m) | HC-SR04 | Cheapest (~$2), 2cm blind zone, standard 4-pin footprint. |
| Outdoor, wet, dusty, or condensation-prone (<4.5m) | JSN-SR04T | Sealed transducer (~$12), IP67 rated, but has a massive 20cm blind zone. |
| Through-wall motion detection (no exact distance needed) | RCWL-0516 | Microwave radar (~$3), passes through plastics, but cannot give precise cm measurements. |
Parts List & Spec Sheet
The code and wiring below target the Arduino Uno R3 (or any ATmega328P-based board like the Nano v3). If you are using a 3.3V board like the ESP32, see the voltage divider note in the wiring section.
- Microcontroller: Arduino Uno R3 (ATmega328P, 5V logic)
- Sensor: HC-SR04 (Standard) or JSN-SR04T (Waterproof variant)
- Wiring: 4x Male-to-Female or Male-to-Male Dupont jumper wires (22 AWG)
- Display (Optional): 16x2 I2C LCD (address 0x27) for standalone readout
| Parameter | HC-SR04 | JSN-SR04T (V2.0) |
|---|---|---|
| Operating Voltage | 5V DC | 3.3V - 5V DC |
| Measuring Range | 2 cm to 400 cm | 20 cm to 450 cm |
| Blind Zone (Minimum Distance) | ~2 cm | ~20 cm (Critical for tank leveling) |
| Beam Angle | ~15 degrees | ~10 degrees (tighter cone) |
| Quiescent Current | ~2 mA | ~5 mA |
Pin Mapping & Wiring Steps
Ultrasonic sensors require precise timing. The Trigger pin sends a 10-microsecond pulse, and the Echo pin goes HIGH for a duration proportional to the distance. Because the HC-SR04 operates at 5V logic, connecting its Echo pin directly to a 3.3V ESP32 GPIO can fry the microcontroller. For the Arduino Uno R3 (5V tolerant), direct wiring is safe.
| HC-SR04 Pin | Arduino Uno R3 Pin | Wire Color (Standard) |
|---|---|---|
| VCC | 5V | Red |
| GND | GND | Black |
| TRIG | Digital Pin 9 | Yellow |
| ECHO | Digital Pin 10 | Blue |
Numbered Wiring Steps
- De-energize the board: Unplug the USB cable from your Arduino Uno before making connections to prevent accidental shorting of the 5V rail.
- Connect Power: Route the red jumper from the sensor VCC to the Arduino 5V pin. Do not use the 3.3V pin; the HC-SR04 will fail to trigger reliably below 4.5V.
- Connect Ground: Route the black jumper from the sensor GND to any Arduino GND pin. Ensure a solid connection; a floating ground causes erratic microsecond timing.
- Connect Trigger: Route the yellow jumper from TRIG to Digital Pin 9.
- Connect Echo: Route the blue jumper from ECHO to Digital Pin 10.
- Verify: Tug gently on each Dupont connector. Loose breadboard contacts are the #1 cause of '0 cm' timeout errors in ultrasonic builds.
Compilable Arduino Code with Error Handling
Most beginner tutorials use the native Arduino pulseIn() function. This is a mistake for production code. If the sensor fails to receive an echo, pulseIn() will block the main loop for up to a full second, freezing your robot or UI. Instead, we use the NewPing library, which handles timeouts gracefully and includes a median filter to strip out acoustic multipath noise (phantom echoes bouncing off side walls).
Target Board: Arduino Uno R3 / Nano v3 (ATmega328P).
Required Library: Install NewPing via the Arduino IDE Library Manager (Tools > Manage Libraries > search 'NewPing' by Tim Eckel).
#include <NewPing.h>
// --- PIN DEFINITIONS ---
#define TRIGGER_PIN 9
#define ECHO_PIN 10
// --- SENSOR CONFIGURATION ---
// Max distance we want to measure (in cm).
// Setting this to 200cm prevents 1-second timeouts on the 400cm max limit.
#define MAX_DISTANCE 200
// Iterations for the median filter.
// 5 iterations take ~30ms but eliminate 99% of acoustic ghosting.
#define PING_ITERATIONS 5
// Initialize the NewPing object
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
void setup() {
Serial.begin(115200);
Serial.println(F("Ultrasonic Sensor Initialized."));
Serial.println(F("Targeting Arduino Uno R3 / ATmega328P"));
}
void loop() {
// ping_median() fires multiple pings and returns the median value.
// This filters out outlier readings caused by acoustic interference.
unsigned int medianDistance = sonar.ping_median(PING_ITERATIONS);
// Error Handling: Check for timeout (0 return value)
if (medianDistance == 0) {
Serial.println(F("Error: Out of range or no echo received (Timeout)."));
} else {
// Convert ping time to distance in cm.
// US_ROUNDTRIP_CM is a built-in NewPing constant accounting for air temp.
float distanceCM = sonar.convert_cm(medianDistance);
Serial.print(F("Distance: "));
Serial.print(distanceCM);
Serial.println(F(" cm"));
}
// Wait 50ms between pings.
// Pinging faster than 35ms causes cross-talk from previous echoes.
delay(50);
}
Debugging: The First Three Things to Check When It Fails
Ultrasonic sensors are notorious for failing silently or outputting garbage data. If your serial monitor isn't showing accurate distances, follow this ranked troubleshooting path.
1. Compilation Error: error: 'NewPing' does not name a type
- Cause: The NewPing library is not installed, or the IDE is pointing to the wrong sketchbook folder.
- Fix: Go to Sketch > Include Library > Manage Libraries. Search for 'NewPing' by Tim Eckel and click Install. Restart the Arduino IDE.
2. Runtime Error: Serial prints Error: Out of range or Distance: 0 cm constantly
- Cause A (Power): The HC-SR04 is wired to the 3.3V pin instead of 5V. It requires 4.5V minimum to fire the transducer.
- Cause B (Blind Zone): You are holding your hand 1 cm away from the sensor. The HC-SR04 has a physical blind zone of ~2 cm; the JSN-SR04T has a blind zone of ~20 cm. Objects inside the blind zone return 0.
- Cause C (Wiring): Trigger and Echo pins are swapped in the physical wiring or the
#definemacros. - Fix: Verify 5V at the VCC rail with a multimeter. Move the target object to exactly 10 cm away. Verify pin 9 is TRIG and pin 10 is ECHO.
3. Runtime Error: Random spikes to Distance: 400 cm or erratic jumping
- Cause: Acoustic multipath interference. The sound cone (15 degrees) is bouncing off a nearby breadboard, table edge, or wire bundle before hitting the target, causing a longer echo return time.
- Fix: Ensure you are using
ping_median()as shown in the code above. If the spikes persist, physically elevate the sensor away from the table surface and ensure no loose wires are dangling in front of the transducer mesh.
Extending and Simplifying the Build
Once you have stable serial output, you will likely want to adapt the hardware for a specific enclosure or network integration.
How to Extend the Build
- Add an I2C OLED Display: Wire an SSD1306 128x64 OLED to the I2C pins (A4/A5 on Uno R3). Use the
Adafruit_SSD1306library to print the distance locally without needing a PC tether. This is ideal for standalone parking sensors or tank level monitors. - Network via MQTT (ESP32 Upgrade): If you migrate this code to an ESP32 (using the voltage divider mentioned earlier), add the
PubSubClientlibrary. Publish thedistanceCMvariable to an MQTT broker (like Mosquitto) every 5 seconds to integrate with Home Assistant for smart home water tank automation. - Temperature Compensation: The speed of sound changes with air temperature (approx 0.6 m/s per degree Celsius). For high-precision industrial builds, wire a DS18B20 temperature sensor and manually calculate the distance using the compensated speed of sound formula rather than relying on NewPing's hardcoded
US_ROUNDTRIP_CMconstant.
How to Simplify the Build
- Drop the Library: If you are constrained by flash memory on an ATtiny85 and cannot install NewPing, you can revert to the native Arduino Ping tutorial method using
pulseIn(ECHO_PIN, HIGH, 12000). The third argument (12000) sets a hard 12-millisecond timeout to prevent the code from freezing, mimicking NewPing's safety behavior at the cost of median filtering. - Remove the Breadboard: For permanent installations, bypass Dupont wires entirely. Solder the HC-SR04 directly to a 4-pin JST-XH connector and route it to the microcontroller. Dupont wires introduce micro-ohm contact resistance and capacitance that can occasionally skew microsecond timing on long cable runs (>1 meter).






