If you are breadboarding a distance-measurement project, the HC-SR04 is the default pick for 90% of indoor hobbyist builds. It operates at 5V, costs under $4, and uses a simple trigger/echo pulse protocol. However, copying basic tutorial code usually results in jittery readings and silent failures when the sound wave scatters. To get reliable data, your Arduino coding for an ultrasonic sensor must include timeout handling, median filtering, and explicit error states.

The Quick Decision: Which Ultrasonic Sensor Should You Buy?

Before wiring anything, confirm you have the right sensor for your environment. Ultrasonic sensors fail predictably when used outside their design constraints. Use this decision matrix to pick your module:

Use Case Recommended Sensor Interface Logic Level Price Range
Indoor robotics, tank levels, breadboard prototypes HC-SR04 (Default Pick) GPIO Pulse 5V $2 - $4
Outdoor, waterproof, car reverse, wet environments A02YYUW (Waterproof) UART Serial 3.3V / 5V $12 - $18
3.3V logic boards (ESP32), I2C bus daisy-chaining RCWL-1601 I2C 3.3V / 5V $4 - $6
Bench Tip: If you are using an ESP32 or Raspberry Pi Pico, do not wire a standard 5V HC-SR04 Echo pin directly to a 3.3V GPIO. The 5V pulse will forward-bias the microcontroller's internal clamping diode, causing erratic readings and potentially bricking the pin. Use a simple voltage divider (1kΩ and 2kΩ resistors) on the Echo line, or buy the RCWL-1601 instead.

Hardware Spec Sheet and Pin Mapping

The code and wiring below target the Arduino Uno R3 (DIP-28 ATmega328P) and the standard HC-SR04 5V variant. The HC-SR04 works by emitting a 40kHz burst when the Trigger pin is held HIGH for 10µs, then timing how long the Echo pin stays HIGH as the sound bounces back.

HC-SR04 Pin Arduino Uno R3 Pin Wire Color (Standard) Notes
VCC 5V Red Requires stable 5V. Do not use 3.3V out.
GND GND Black Connect to main system ground.
Trig Pin 9 Yellow Set as OUTPUT in code.
Echo Pin 10 Blue Set as INPUT in code.

At 20°C (68°F), the speed of sound in dry air is approximately 343 meters per second, or 0.0343 cm/µs. Because the sound travels to the object and back, we divide the total time by two. The formula is: Distance (cm) = (Pulse Duration (µs) * 0.0343) / 2.

Compilable Arduino Code with Error Handling

Most beginner tutorials use a single pulseIn() call. This is a mistake. The HC-SR04 is notoriously susceptible to acoustic noise and multipath scattering, resulting in random '0 cm' or '400 cm' spikes. The code below implements a 5-sample median filter to reject outliers and includes explicit error handling for timeouts and out-of-bounds readings.

// Target: Arduino Uno R3 (AVR ATmega328P)
// Sensor: HC-SR04 (5V)
// Library: None (Uses core Arduino functions)

const int trigPin = 9;
const int echoPin = 10;
const long MAX_DISTANCE_CM = 400;
// Timeout calculation: Max distance * 2 (round trip) / speed of sound (0.0343 cm/us)
// 400 * 2 / 0.0343 = ~23323 microseconds. We'll use 24000 for safety margin.
const long TIMEOUT_US = 24000; 

void setup() {
  Serial.begin(115200);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  
  // Ensure trigger pin starts LOW
  digitalWrite(trigPin, LOW);
  Serial.println("HC-SR04 Initialized. Median filter active.");
}

long getMedianDistance() {
  long distances[5];
  
  for (int i = 0; i < 5; i++) {
    // 1. Send 10us trigger pulse
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    
    // 2. Read echo pulse with timeout
    long duration = pulseIn(echoPin, HIGH, TIMEOUT_US);
    
    if (duration == 0) {
      distances[i] = -1; // Flag for timeout
    } else {
      distances[i] = duration * 0.0343 / 2;
    }
    
    // 3. Wait 20ms for acoustic echoes to dissipate before next ping
    delay(20); 
  }

  // Simple bubble sort to find the median (rejects high/low outliers)
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4 - i; j++) {
      if (distances[j] > distances[j+1]) {
        long temp = distances[j];
        distances[j] = distances[j+1];
        distances[j+1] = temp;
      }
    }
  }
  
  return distances[2]; // Return the middle (median) value
}

void loop() {
  long cm = getMedianDistance();

  // Explicit Error Handling
  if (cm == -1) {
    Serial.println("ERR: TIMEOUT - No echo received within 24ms");
  } else if (cm < 2) {
    Serial.println("ERR: OUT_OF_BOUNDS - Target too close (<2cm blind zone)");
  } else if (cm > MAX_DISTANCE_CM) {
    Serial.println("ERR: OUT_OF_BOUNDS - Target beyond 400cm range");
  } else {
    Serial.print("Distance: ");
    Serial.print(cm);
    Serial.println(" cm");
  }
  
  delay(100); // Update rate ~10Hz
}
Why a Median Filter? If a sound wave bounces off a angled surface, it might take a longer path back to the sensor, registering as 150cm when the wall is actually 50cm away. A median filter sorts 5 rapid readings and picks the middle one, automatically discarding the 150cm 'ghost' echo without slowing down your main loop like an averaging filter would.

Debugging: First Three Checks When Readings Fail

When your serial monitor stops printing valid distances, do not immediately rewrite your code. Hardware and power issues cause 95% of HC-SR04 failures. If you see the error strings below, follow this ranked troubleshooting path.

1. Serial prints: "ERR: TIMEOUT - No echo received within 24ms"

What it means: The Arduino sent the trigger pulse, but the Echo pin never went HIGH, or it stayed HIGH longer than the 24ms timeout limit.

  • Cause A (Most Likely): VCC Sag. The HC-SR04 draws a spike of current (up to 15mA) when firing the 40kHz burst. If powered from a weak USB hub or a long, thin breadboard rail, the voltage drops below 4.5V, and the sensor's internal oscillator fails to trigger. Fix: Measure VCC at the sensor pins with a multimeter during a ping. If it drops below 4.8V, add a 100µF electrolytic capacitor across the sensor's VCC and GND pins.
  • Cause B: Floating Echo Pin. A broken jumper wire or poor breadboard contact leaves the input pin floating, picking up ambient EMI. Fix: Check continuity on the Echo jumper wire.

2. Serial prints: "ERR: OUT_OF_BOUNDS - Target too close (<2cm blind zone)"

What it means: The sensor is detecting an echo almost instantly, calculating a distance under 2cm.

  • Cause A: Acoustic Cross-Talk. If you have two HC-SR04 sensors mounted closer than 15cm apart, Sensor A is hearing Sensor B's echo. Fix: Stagger the loop() timing so they never fire simultaneously, or physically separate them.
  • Cause B: Dust/Debris on Transducer. The metal mesh screens on the 40kHz transducers are easily clogged by dust or solder splatter, causing the sound wave to reflect immediately off the sensor face. Fix: Clean the mesh gently with compressed air or a soft brush.

3. Readings are erratic, jumping between 10cm and 300cm randomly

What it means: The sensor is working, but the physics of the environment are defeating the 40kHz wave.

  • Cause A: Soft Targets. Ultrasonic sensors require hard, flat surfaces to reflect sound. Foam, cloth, and thick carpet absorb 40kHz frequencies almost entirely. Fix: Tape a small piece of flat cardboard or acrylic to your target object.
  • Cause B: Temperature Extremes. The speed of sound changes with temperature (approx. 0.6 m/s per °C). If your sensor is in a freezer or near a heater, the hardcoded 0.0343 multiplier will introduce drift. Fix: For high-precision industrial builds, add a DS18B20 temperature sensor and calculate the speed of sound dynamically: speed = 331.3 + (0.606 * tempC).
The First 3 Things to Check (Quick Checklist):
1. Put your multimeter in DC Voltage mode and probe the sensor's VCC and GND pins directly (not the Arduino 5V pin). It must read >4.8V.
2. Put your multimeter in Continuity mode and beep out the Echo and Trig jumper wires.
3. Verify your target object is hard, flat, and positioned perpendicular to the sensor face.

Extending and Simplifying the Build

Once you have stable distance readings, you will likely want to integrate this data into a larger system. Here is how to scale the project up or down based on your needs.

How to Extend: Adding an I2C LCD Display

For standalone projects (like a smart trash can or a parking assist module), you need a display. Use a standard 16x2 I2C LCD (address usually 0x27). Wire the SDA to A4 and SCL to A5 on the Uno R3. Install the LiquidCrystal_I2C library via the Arduino Library Manager. Replace the Serial.print() lines in the code above with lcd.setCursor(0, 1); lcd.print(cm);. This keeps the I2C bus free from the timing-critical pulse measurements.

How to Simplify: Using the NewPing Library

If you are building a complex robot and need to poll multiple sensors without blocking the main loop, writing custom median filters for each sensor becomes tedious. The NewPing library (available in the Arduino Library Manager) handles timeouts, median filtering, and non-blocking timer interrupts natively.

While the raw code provided in this article is superior for learning the exact hardware timing and avoiding external dependencies, NewPing is the industry standard for scaling up to 3 or more ultrasonic sensors on a single Arduino. Just remember that NewPing defaults to a maximum distance of 500cm; you must explicitly pass 400 as the max distance parameter in the constructor to match the HC-SR04's physical limits and prevent phantom readings.

For deeper technical specifications on the pulseIn() function's internal timer mechanics, refer to the official Arduino language reference. Understanding how the microcontroller counts clock cycles while waiting for a pin state change will help you debug timing conflicts when you eventually add servos and PWM motor controllers to the same board.