The HC-SR04 is the undisputed workhorse of Arduino ultrasonic sensors. It operates at 40 kHz, offering a non-contact measurement range of 2 cm to 400 cm with a blind zone under 2 cm. This guide targets the Arduino Uno R3 Rev3 (ATmega328P) and provides a production-ready, timeout-protected C++ implementation. We will cover exact wiring, median filtering for acoustic noise, and the specific hardware failures that cause beginner builds to hang or return zero.

Project Overview & Hardware Spec Sheet

Difficulty Rating: Beginner-Intermediate (2/5)
Time to Build: 15 minutes
Target Board: Arduino Uno R3 Rev3 (ATmega328P)

Required Parts List

  • Sensor: HC-SR04 Ultrasonic Module (Standard 4-pin variant, ~$2.50)
  • Microcontroller: Arduino Uno R3 Rev3 (DIP ATmega328P, ~$27.00)
  • Wiring: 4x 22 AWG solid-core jumper wires (Male-to-Male)
  • Prototyping: Half-size 400-point solderless breadboard
ParameterSpecificationNotes
Operating Voltage5V DCDo not exceed 5.5V
Quiescent Current2 mASpikes to 15 mA during trigger
Measuring Range2 cm - 400 cmBlind zone is < 2 cm
Trigger Pulse10 µs TTL HIGHMust be exact to fire transducer
Beam Angle~15° (Effective)30° total cone width

Pin Mapping & Wiring Steps

The HC-SR04 requires four connections. Unlike I2C sensors, it uses dedicated GPIO pins for the trigger and echo pulses.

HC-SR04 PinArduino Uno R3 PinWire Color (Standard)
VCC5VRed
TrigDigital 9Yellow
EchoDigital 10Blue
GNDGNDBlack

Numbered Wiring Steps

  1. De-energize the board: Unplug the Arduino USB cable before wiring.
  2. Connect Power: Route the Red wire from HC-SR04 VCC to the Arduino 5V pin. Route Black from GND to Arduino GND.
  3. Connect Signal: Route Yellow from Trig to Digital Pin 9. Route Blue from Echo to Digital Pin 10.
  4. Verify: Tug gently on each jumper wire at the breadboard junction to ensure solid contact. Plug in USB and verify the Arduino power LED illuminates.
Callout Tip: 3.3V Board Warning
The HC-SR04 Echo pin outputs a 5V HIGH signal. If you adapt this build to an ESP32 or Arduino Nano 33 IoT, you must use a voltage divider (e.g., 1kΩ and 2kΩ resistors) on the Echo pin. Feeding 5V into a 3.3V GPIO will permanently destroy the microcontroller pin.

Complete Compilable Code with Timeout Handling

Beginner tutorials often use pulseIn() without a timeout. If the echo pulse never returns (due to a disconnected wire or an object >400cm away), the microcontroller hangs indefinitely waiting for a state change. The code below implements a strict timeout and a 3-sample median filter to reject acoustic multipath bouncing.

/*
 * HC-SR04 Ultrasonic Sensor with Timeout & Median Filter
 * Target Board: Arduino Uno R3 Rev3 (ATmega328P)
 * Assumes 20C ambient (Speed of Sound = 343 m/s)
 */

const uint8_t TRIG_PIN = 9;
const uint8_t ECHO_PIN = 10;
const unsigned long TIMEOUT_US = 23200; // 400cm * 2 * 29us/cm

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  digitalWrite(TRIG_PIN, LOW);
}

void loop() {
  long distance = getMedianDistance();
  
  if (distance == -1) {
    Serial.println(F("ERR: Echo timeout (>400cm or disconnected)"));
  } else if (distance < 2) {
    Serial.println(F("ERR: Blind zone (<2cm or acoustic bounce)"));
  } else {
    Serial.print(F("Distance: "));
    Serial.print(distance);
    Serial.println(F(" cm"));
  }
  
  delay(50); // 20Hz polling rate
}

long getMedianDistance() {
  long samples[3];
  for (int i = 0; i < 3; i++) {
    samples[i] = readRawDistance();
    delayMicroseconds(500); // Let acoustic ringing settle
  }
  // Simple bubble sort for 3 elements
  if (samples[0] > samples[1]) swap(samples[0], samples[1]);
  if (samples[1] > samples[2]) swap(samples[1], samples[2]);
  if (samples[0] > samples[1]) swap(samples[0], samples[1]);
  
  return samples[1]; // Return median
}

long readRawDistance() {
  // 1. Generate 10us trigger pulse
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  // 2. Read echo with timeout
  long duration = pulseIn(ECHO_PIN, HIGH, TIMEOUT_US);
  
  if (duration == 0) return -1; // Timeout occurred
  
  // 3. Calculate distance (duration / 2 / 29.1)
  return duration / 58;
}

void swap(long &a, long &b) {
  long temp = a;
  a = b;
  b = temp;
}

Debugging: First 3 Checks & Exact Error Strings

When your serial monitor outputs errors or locks up, follow this decision path. These are the first three things to check when an HC-SR04 build fails.

1. The "ERR: Echo timeout" Error

Exact String: ERR: Echo timeout (>400cm or disconnected)

Ranked Causes:

  1. Object is out of range: The target is further than 400 cm or absorbs acoustic energy (like thick foam or clothing).
  2. Echo wire disconnected: The pulseIn() function hits the 23,200 µs timeout because the pin never goes HIGH. Check breadboard continuity with a multimeter.
  3. Trigger pulse too short: If you modified the code and dropped the delayMicroseconds(10), the transducer will not fire.

2. The "ERR: Blind zone" Error

Exact String: ERR: Blind zone (<2cm or acoustic bounce)

Ranked Causes:

  1. Object is too close: The HC-SR04 cannot separate the transmit ring-down from the receive echo under 2 cm.
  2. Acoustic cross-talk: The sensor is picking up its own bounce off the breadboard or mounting bracket. Angle the sensor slightly upward.

3. Board Locks Up / No Serial Output

Ranked Causes:

  1. VCC/GND Reversed: The HC-SR04 has no reverse polarity protection. If you swap 5V and GND, the onboard MAX232 equivalent chip will overheat and short out within seconds, potentially dragging down the Arduino 5V rail and resetting the ATmega328P via brownout. Touch the sensor chip; if it is burning hot, discard it.
  2. USB Power Sag: The sensor draws 15 mA spikes. If powered from a weak USB hub, the voltage drops below 4.5V, causing the Arduino voltage regulator to drop out.

Extending and Simplifying the Build

Depending on your project constraints, you may need to alter the hardware approach to fit production or rapid-prototyping needs.

How to Simplify the Build

If you want to eliminate GPIO timing issues and pulseIn() blocking entirely, swap the HC-SR04 for an I2C Ultrasonic Sensor (like the DFRobot SEN0311 or Grove Ultrasonic). These modules handle the 40 kHz pulsing and timing on an internal MCU, exposing only a standard I2C address (usually 0x57). You simply request a byte array over the Wire library, freeing up the Arduino to handle Wi-Fi or motor control without interrupt latency. Remember to include 4.7kΩ pull-up resistors on the SDA/SCL lines if your breakout board lacks them.

How to Extend the Build

Add Temperature Compensation: The speed of sound changes by roughly 0.6 m/s per degree Celsius. At 0°C, the speed is 331 m/s; at 30°C, it is 349 m/s. This introduces a ~5% error across a typical year. To fix this, wire a DS18B20 waterproof temperature sensor to a spare digital pin. Read the ambient temperature, calculate the exact speed of sound, and replace the hardcoded / 58 divisor in the code with a dynamic float variable derived from the Engineering Toolbox acoustic formulas.

Frequently Asked Questions

Can I power Arduino ultrasonic sensors directly from the 3.3V pin?

No. The HC-SR04 requires a stable 5V supply to drive the ultrasonic transducers. Running it from 3.3V will result in erratic triggering, severely reduced range (often under 50 cm), and failure to generate a valid Echo pulse. If you only have a 3.3V board (like an ESP32), you must power the sensor from a separate 5V buck converter or the board's 5V VIN pin, and use a voltage divider on the Echo return line.

Why do Arduino ultrasonic sensors read 0 cm when the object is too close?

This is the acoustic blind zone. When an object is closer than 2 cm, the receiver transducer is still "ringing" from the physical vibration of the transmit pulse. The internal comparator cannot distinguish the initial ring-down from the actual echo. The pulseIn() function either catches the immediate ring-down (returning a near-zero duration) or misses the pulse entirely.

How do I waterproof an HC-SR04 for outdoor liquid level sensing?

The standard HC-SR04 is open-mesh and will fail instantly in high humidity or rain. For outdoor tank monitoring, use the JSN-SR04T variant. It features a sealed, waterproof transducer head connected via a 2.5-meter shielded cable. Be aware that the JSN-SR04T has a larger blind zone (typically 20 cm to 25 cm) due to the acoustic dampening required to prevent the sealed housing from resonating, and some revisions require a 10kΩ pull-up resistor on the Echo pin.

What causes random 400cm spikes in an otherwise stable reading?

These spikes are caused by acoustic multipath reflection or electrical noise on the Echo line. If the 40 kHz pulse bounces off a nearby wall before hitting the target, the travel time exceeds the timeout threshold, or the returning signal is too attenuated to cross the comparator threshold. Implementing the 3-sample median filter provided in the Arduino pulseIn reference code above will automatically discard these outlier spikes.