If you are looking for reliable Arduino sonar sensor code, the direct answer is to avoid basic delay-based loops and instead use the pulseIn() function with a strict microsecond timeout, paired with a median filter to eliminate acoustic ghost readings. The code provided below targets the Arduino Uno R3 and the newer Arduino Uno R4 Minima, utilizing 5V logic to interface directly with the standard HC-SR04 ultrasonic module.

Ultrasonic sensors are notorious for throwing erratic values when placed near angled walls or soft fabrics. By implementing a 30-millisecond timeout and a 5-sample median sort in your C++ sketch, you prevent the main loop from hanging indefinitely and filter out multipath acoustic reflections. Below is the complete bench-tested guide to wiring, coding, and debugging your sonar build.

Project Spec Sheet & Parts List

Before writing a single line of code, verify your hardware. The standard HC-SR04 is fine for indoor robotics, but if you are building an outdoor rover or a sump-pump monitor, you need the waterproof variant.

Component Exact Variant Specs & Notes Est. Price (2026)
Microcontroller Arduino Uno R3 or R4 Minima ATmega328P or Renesas RA4M1. 5V logic. $24 - $28
Sonar Sensor (Standard) HC-SR04 2cm - 400cm range. 15° beam angle. 5V VCC. $1.50 - $3.00
Sonar Sensor (Waterproof) JSN-SR04T 20cm - 400cm range. Sealed transducer. 5V VCC. $5.00 - $8.00
Jumper Wires 22 AWG Dupont (M-M) Keep under 30cm to prevent signal degradation. $4.00 (pack)
Bench Note: Do not power the HC-SR04 from the Arduino's 3.3V pin. The sensor requires a stable 5V supply and draws up to 15mA during the ultrasonic burst. A 3.3V feed will result in continuous '0 cm' timeout errors.

Pin Mapping & Wiring Rules

The HC-SR04 uses a simple two-wire synchronous interface (Trigger and Echo). While you can use any digital pins, assigning them to standard I/O pins avoids conflicts with hardware serial or I2C buses.

HC-SR04 Pin Arduino Uno Pin Wire Color (Standard) Function
VCC 5V Red Power supply (4.8V - 5.5V DC)
Trig Digital 9 Yellow Input: Receives 10µs HIGH pulse
Echo Digital 10 Blue Output: Sends HIGH pulse proportional to distance
GND GND Black Common ground reference

ESP32 / 3.3V Board Warning: If you adapt this Arduino sonar sensor code for an ESP32 or Raspberry Pi Pico, the Echo pin outputs a 5V HIGH signal when the HC-SR04 is powered by 5V. Feeding 5V into a 3.3V GPIO will fry the microcontroller. You must use a voltage divider (e.g., 1kΩ and 2kΩ resistors) on the Echo line to step it down to 3.3V.

The Compilable Arduino Sonar Sensor Code

This sketch avoids the pitfalls of basic tutorials. It includes a strict timeout on the pulseIn() function—preventing the board from freezing if the sound wave scatters—and a median filter to discard acoustic anomalies. For more on how pulseIn() handles microsecond timing at the hardware level, refer to the official Arduino pulseIn reference.

/*
 * Reliable Arduino Sonar Sensor Code
 * Target: Arduino Uno R3 / R4 Minima
 * Sensor: HC-SR04 or JSN-SR04T
 */

// Pin Definitions
#define TRIG_PIN 9
#define ECHO_PIN 10

// Physics Constants
#define SOUND_SPEED_CM_PER_US 0.0343  // Speed of sound at 20°C
#define TIMEOUT_US 30000              // 30ms timeout (max range ~5 meters)
#define SAMPLE_SIZE 5                 // Median filter window

unsigned long getMedianDistance();

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  
  // Ensure trigger pin is low on startup
  digitalWrite(TRIG_PIN, LOW);
  Serial.println("Sonar Sensor Initialized.");
}

void loop() {
  unsigned long distance = getMedianDistance();
  
  // Error Handling & Output
  if (distance == 0) {
    Serial.println("Error: Timeout or No Echo (Check Wiring/Power)");
  } else if (distance > 400) {
    Serial.println("Error: Out of Range (>400cm)");
  } else {
    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" cm");
  }
  
  delay(250); // 4Hz polling rate (HC-SR04 max is ~20Hz, 4Hz prevents echo overlap)
}

// Function to get a single raw reading with timeout
unsigned long getRawDistance() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // Read echo with strict timeout to prevent blocking
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, TIMEOUT_US);

  if (duration == 0) return 0; // Timeout occurred

  // Calculate distance: (duration / 2) * speed of sound
  return (duration / 2) * SOUND_SPEED_CM_PER_US;
}

// Median filter to eliminate ghost readings from multipath reflections
unsigned long getMedianDistance() {
  unsigned long samples[SAMPLE_SIZE];
  
  for (int i = 0; i < SAMPLE_SIZE; i++) {
    samples[i] = getRawDistance();
    delay(10); // Small gap between pings to let acoustic ringing settle
  }

  // Simple bubble sort for small array
  for (int i = 0; i < SAMPLE_SIZE - 1; i++) {
    for (int j = 0; j < SAMPLE_SIZE - i - 1; j++) {
      if (samples[j] > samples[j + 1]) {
        unsigned long temp = samples[j];
        samples[j] = samples[j + 1];
        samples[j + 1] = temp;
      }
    }
  }

  // Return the middle value
  return samples[SAMPLE_SIZE / 2];
}

Debugging: Fixing '0 cm' and '400 cm' Ghost Readings

When your serial monitor spits out bad data, do not immediately rewrite the code. Hardware and physics are usually the culprits. Here is the ranked decision tree for the most common failure modes.

Symptom 1: Serial Monitor prints 'Error: Timeout or No Echo (Check Wiring/Power)'

This means pulseIn() hit the 30,000µs timeout and returned 0. The sensor never pulled the Echo pin HIGH.

  1. Check 1: Power Rail Continuity. Use a multimeter to measure DC voltage between the sensor's VCC and GND pins directly at the module. If it reads below 4.7V, your breadboard power rails are loose, or your USB cable is suffering from voltage drop. The HC-SR04 will fail to generate the 40kHz burst without a solid 5V.
  2. Check 2: Trig/Echo Swap. It is incredibly common to plug the yellow and blue wires into the wrong pins. Verify that D9 goes to Trig and D10 goes to Echo.
  3. Check 3: Common Ground. If you are using an external 5V power supply for the sensor, its GND must be tied directly to the Arduino's GND. Without a shared reference, the Arduino cannot read the Echo pulse.

Symptom 2: Serial Monitor prints 'Error: Out of Range (>400cm)' when an object is close

This happens when the Echo pin stays HIGH longer than expected, or the sensor is experiencing acoustic ringing.

  1. Cause: Missed Trigger. If the 10µs trigger pulse is slightly too short due to interrupt latency, the sensor might fire a partial burst, confusing its internal flip-flop. The code above uses delayMicroseconds(10) which is highly accurate on AVR boards.
  2. Cause: Acoustic Multipath. If the sensor is mounted inside a 3D-printed shroud or near a angled baffle, the 40kHz sound waves bounce around the enclosure before exiting. This is called 'acoustic ringing'. Fix: Add a 20ms delay between pings, or line the inside of your sensor shroud with acoustic dampening foam.
Pro-Tip for JSN-SR04T Users: The waterproof JSN-SR04T has a minimum blind spot of about 20cm. If you place an object 5cm away from the waterproof transducer, the internal amplifier will saturate, and it will falsely report a distance of 400cm or timeout entirely. Keep your target >25cm away.

Extending and Simplifying the Build

Once you have the raw Arduino sonar sensor code running, you will likely want to integrate it into a larger system. Here is how to scale the project without reinventing the wheel.

Simplifying with the NewPing Library

If you are managing multiple ultrasonic sensors (e.g., a 3-sensor collision avoidance rover), writing custom median filters and timers for each pin becomes messy. The NewPing library is the industry standard for this. It handles the timer interrupts and ping scheduling natively, allowing you to ping up to 15 sensors without blocking the main loop. It also includes built-in median filtering via ping_median(iterations).

Adding an I2C OLED Display

To make the build standalone, add a 0.96-inch SSD1306 I2C OLED display. Wire the SDA to A4 and SCL to A5 on the Uno. Use the Adafruit_SSD1306 library to render the distance value. Because I2C operates on a separate hardware bus, it will not interfere with the microsecond-precise timing required by the pulseIn() function on the digital pins.

Arduino Sonar Sensor Code FAQ

Can I use the HC-SR04 sonar sensor with an ESP32 using this exact code?

You can use the logic, but you must change the pin definitions to valid ESP32 GPIOs (avoid strapping pins like GPIO 0, 2, 12, and 15). More importantly, the HC-SR04 outputs a 5V Echo signal. The ESP32 is strictly 3.3V tolerant. You must build a voltage divider using a 1kΩ resistor (series) and a 2kΩ resistor (to ground) on the Echo line to step the 5V pulse down to a safe ~3.3V before it hits the ESP32 GPIO.

Why does my Arduino sonar sensor code freeze the entire robot after a few minutes?

This is almost always caused by using pulseIn(ECHO_PIN, HIGH) without a timeout parameter. If the sensor misses an echo (due to a soft fabric target absorbing the sound, or a wiring glitch), the Echo pin never goes HIGH. The pulseIn() function will wait indefinitely, effectively bricking your main loop until you press the hardware reset button. Always use the 3-argument version: pulseIn(pin, HIGH, timeout_us).

How do I convert the distance from centimeters to inches in the code?

Divide the final centimeter value by 2.54. In the code provided, you can change the return line in the getMedianDistance() function to return (samples[SAMPLE_SIZE / 2] / 2.54); and update your Serial print statement to read 'inches'. Note that the speed of sound changes slightly with temperature and humidity, but for hobbyist robotics at room temperature (20°C), the 0.0343 cm/µs constant is accurate to within 1%.

What is the maximum polling rate for the HC-SR04 sensor?

The HC-SR04 requires a minimum of 50 milliseconds between trigger pulses to allow the 40kHz acoustic ringing to dissipate and to prevent the previous echo from overlapping with the next ping. This equates to a maximum theoretical polling rate of 20Hz. However, for reliable operation with a median filter (which takes 5 samples), a 250ms delay (4Hz) in the main loop is the sweet spot for mobile robots.