A robust ultrasonic sensor Arduino program for the HC-SR04 requires more than just a basic pulseIn() call. If you have ever watched your serial monitor spit out random spikes of 0 cm or 3000 cm, you already know that raw acoustic timing is inherently noisy. The core of a reliable build relies on precise microsecond timing, explicit timeout handling, and a median filter to reject acoustic jitter.

This guide targets the Arduino Uno R3 (ATmega328P) and Arduino Nano v3 operating at 5V logic. We will walk through the exact hardware specs, a production-ready wiring layout, and a fully compilable C++ program that filters out false echoes before they ever reach your main loop.

Hardware Spec Sheet & Parts List

The HC-SR04 is a 40 kHz ultrasonic transceiver pair. It calculates distance by firing an 8-cycle burst and measuring the time it takes for the echo to return. Because the speed of sound in dry air at 20°C is roughly 343 meters per second, the sensor relies on a fixed timing constant that shifts slightly with temperature and humidity.

Difficulty Rating: Beginner to Intermediate
Estimated Build Time: 15 minutes

HC-SR04 Specification Table

Parameter Value Practical Note
Operating Voltage 5V DC Will fail or return 0 if powered from 3.3V.
Working Current ~15 mA Idle current drops to ~2 mA between pings.
Measuring Range 2 cm to 400 cm Objects closer than 2 cm fall in the acoustic blind zone.
Measuring Angle ~15° cone Highly reflective surfaces outside this cone can cause false echoes.
Trigger Pulse 10 µs TTL HIGH Must be followed by a LOW state to reset the internal latch.

Required Parts

  • Microcontroller: Arduino Uno R3 or Arduino Nano v3 (ATmega328P, 5V logic)
  • Sensor: HC-SR04 Ultrasonic Module (Standard 4-pin variant)
  • Wiring: 4x Male-to-Male or Male-to-Female jumper wires (22 AWG stranded)
  • Prototyping: Half-size solderless breadboard

Pin Mapping & Wiring Steps

Before writing a single line of code, verify your physical connections. The most common cause of a completely dead sensor is routing the 5V VCC line to the 3.3V pin on the Arduino, which starves the internal oscillator.

HC-SR04 Pin Arduino Uno R3 Pin Wire Color (Standard) Function
VCC 5V Red Provides 5V power to the transceivers and logic IC.
TRIG Digital Pin 9 Yellow Receives the 10µs HIGH pulse to initiate a measurement.
ECHO Digital Pin 10 Blue Outputs a HIGH pulse proportional to the distance.
GND GND Black Common ground reference.

Wiring Steps:

  1. Insert the HC-SR04 into the breadboard, ensuring the pins are fully seated without bending the metal transceiver cans.
  2. Connect the red jumper from the sensor VCC pin directly to the 5V pin on the Arduino Uno R3.
  3. Connect the black jumper from the sensor GND pin to any Arduino GND pin.
  4. Connect the yellow jumper from TRIG to Arduino Digital Pin 9.
  5. Connect the blue jumper from ECHO to Arduino Digital Pin 10.
  6. Double-check that TRIG and ECHO are not swapped. While both are digital pins, the Arduino must drive TRIG as an OUTPUT and read ECHO as an INPUT.

The Complete Ultrasonic Sensor Arduino Program

Most basic tutorials use a single pulseIn() call. This is a mistake. Acoustic reflections off nearby walls or soft materials create micro-jitter that results in distance readings bouncing between 45 cm and 52 cm on a stationary object.

The program below solves this by implementing a median filter. It takes 5 rapid readings, sorts them, and returns the middle value. This mathematically guarantees that up to 2 completely erroneous readings (like a 0 cm timeout or a 400 cm stray echo) are discarded without skewing the final output. We also implement explicit error handling for the sensor's physical blind zone and maximum range.

#include <algorithm>

// --- PIN DEFINITIONS ---
#define TRIG_PIN 9
#define ECHO_PIN 10

// --- CONSTANTS ---
#define MAX_DISTANCE_CM 400
#define PING_ITERATIONS 5

// Speed of sound at 20°C in cm/µs is roughly 0.0343
// Distance = (duration * 0.0343) / 2
const float SOUND_SPEED_CM_US = 0.0343;

void setup() {
  Serial.begin(115200);
  
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  
  // Ensure trigger pin starts LOW to prevent accidental pings on boot
  digitalWrite(TRIG_PIN, LOW);
  delay(100);
}

long getMedianDistance() {
  long distances[PING_ITERATIONS];
  
  for (int i = 0; i < PING_ITERATIONS; i++) {
    // 1. Send 10µs HIGH pulse to trigger
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    
    // 2. Read echo with explicit timeout
    // Max round trip for 400cm = 400 * 2 / 0.0343 = ~23323 µs.
    // We set timeout to 25000 µs to safely catch out-of-range.
    long duration = pulseIn(ECHO_PIN, HIGH, 25000);
    
    if (duration == 0) {
      distances[i] = MAX_DISTANCE_CM + 1; // Flag as out of range/timeout
    } else {
      distances[i] = (duration * SOUND_SPEED_CM_US) / 2.0;
    }
    
    // 3. Wait 20ms between pings to let acoustic echoes dissipate
    delay(20); 
  }
  
  // Sort the array and return the median (middle) value
  std::sort(distances, distances + PING_ITERATIONS);
  return distances[PING_ITERATIONS / 2];
}

void loop() {
  long distance = getMedianDistance();
  
  // --- ERROR HANDLING & OUTPUT ---
  if (distance > MAX_DISTANCE_CM) {
    Serial.println("Error: Out of range or timeout (>400cm)");
  } else if (distance < 2) {
    Serial.println("Error: Inside blind zone (<2cm)");
  } else {
    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" cm");
  }
  
  // Main loop delay (adjust based on your application's refresh rate)
  delay(100);
}

Debugging: When the Sensor Returns 0 or Erratic Values

When your ultrasonic sensor Arduino program fails, it usually fails in one of two specific ways. Before rewriting your code, check these physical layer issues.

The First Three Things to Check:
  1. Power Rail Continuity: Measure the voltage between the HC-SR04 VCC and GND pins with a multimeter. It must read between 4.8V and 5.2V. If it reads 3.3V, you are plugged into the wrong Arduino pin.
  2. TRIG/ECHO Swap: Verify the yellow and blue wires. If swapped, the Arduino is listening to an output pin and driving an input pin, resulting in permanent 0 µs readings.
  3. Breadboard Contact: The metal cans on the HC-SR04 are heavy. If the module is leaning, the pins may not be making solid contact with the breadboard's internal leaf springs.

Error String: "Distance: 0 cm" or Serial Monitor Blank

If your raw pulseIn() returns exactly 0, the Arduino timed out waiting for the ECHO pin to go HIGH.

  • Cause 1 (Most Likely): The ECHO pin is wired to the wrong GPIO, or the wire is broken.
  • Cause 2: The sensor is powered by 3.3V instead of 5V, causing the internal CS1008 driver IC to brownout and fail to fire the transducer.
  • Cause 3: The object is physically inside the 2 cm acoustic blind zone, causing the transmit burst to bleed directly into the receiver without a measurable delay.

Error String: "Error: Out of range or timeout (>400cm)"

This means the ECHO pin never went LOW within the 25,000 µs timeout window, or the calculated distance exceeded physical limits.

  • Cause 1: The target object is made of sound-absorbing material (acoustic foam, heavy cloth, carpet). The 40 kHz wave is absorbed rather than reflected.
  • Cause 2: Specular reflection. The target is a flat, hard surface angled more than 15° away from the sensor, bouncing the sound wave away from the receiver.
  • Cause 3: Acoustic cross-talk from a second nearby HC-SR04 firing at the exact same time.

Extending and Simplifying the Build

Depending on your project constraints, you may want to strip this code down or build it up.

How to Simplify: Use the NewPing Library

If you do not want to manage raw pulseIn() timeouts and array sorting, use Paul Stoffregen's NewPing library. It handles the 20ms ping delay, timeout limits, and median calculations natively. Simply install it via the Arduino Library Manager and replace the custom function with sonar.ping_median(5) / US_ROUNDTRIP_CM.

How to Extend: Add I2C OLED or MQTT

To make this a standalone distance gauge, wire an SSD1306 128x64 I2C OLED display to the A4 (SDA) and A5 (SCL) pins. Use the Adafruit_SSD1306 library to render the distance variable. For IoT applications, migrate the code to an ESP32 (remembering to use a logic level shifter or voltage divider on the ECHO pin to step 5V down to 3.3V) and publish the median distance to an MQTT broker via WiFi.

Frequently Asked Questions

Why does my ultrasonic sensor Arduino program freeze or hang?

If your program freezes, your pulseIn() function is likely blocking indefinitely. By default, pulseIn() has a one-second timeout if no third argument is provided. If the ECHO pin is floating or stuck HIGH due to a wiring fault, the Arduino will halt execution for a full second per ping. Always use the explicit timeout parameter: pulseIn(ECHO_PIN, HIGH, 25000) to cap the blocking time at 25 milliseconds.

Can I run the HC-SR04 ultrasonic sensor on an ESP32 or 3.3V Arduino?

Yes, but with a critical hardware caveat. The HC-SR04 requires 5V to operate its internal oscillator reliably. If you power it from 3.3V, it will often fail to trigger. You must power the VCC pin with 5V. However, the ECHO pin will output a 5V HIGH signal when triggered, which will fry the 3.3V GPIO on an ESP32 or Arduino Due. You must use a simple voltage divider (e.g., a 1kΩ resistor in series with the ECHO pin, and a 2kΩ resistor to GND) to step the 5V ECHO signal down to a safe ~3.3V.

How do I filter out jitter in my ultrasonic sensor readings?

Jitter is caused by multipath acoustic reflections (sound bouncing off a wall, then the floor, then the sensor). The most effective software fix is a median filter, as implemented in the code above. Taking 5 to 9 rapid samples and discarding the highest and lowest values removes the statistical outliers caused by multipath echoes, leaving you with a rock-solid baseline reading.

What is the minimum delay required between ultrasonic sensor readings?

You must wait at least 20 milliseconds between consecutive trigger pulses. If you ping the sensor faster than this, the receiver will pick up the lingering tail-end of the previous 40 kHz burst (acoustic cross-talk), resulting in artificially short distance readings. The 20ms delay in the getMedianDistance() loop enforces this physical requirement.