The Arduino and HC-SR04 combination is the undisputed workhorse of hobbyist distance measurement. But while getting a basic reading is trivial, getting reliable readings without phantom spikes, 0cm dropouts, or fried logic pins requires understanding the sensor's 40kHz transducer physics and the microcontroller's timing constraints. This guide skips the generic overviews and gives you the exact wiring, robust C++ code, and a debugging decision tree to solve the most common bench failures.

Quick Decision Path: Which Sensor Variant to Pick

Not all "HC-SR04" modules are identical. The market is flooded with clones that use different internal comparator chips (like the LM324 vs. TL072), which changes their logic-level tolerance. Use this decision table to pick the exact part number for your build.

Scenario Board Logic Environment Concrete Pick (Buy This)
Basic indoor robotics / obstacle avoidance 5V (Uno R3, Mega 2560) Dry, indoor, >2cm blind zone Standard HC-SR04 (~$2.00)
ESP32, Raspberry Pi Pico, or STM32 integration 3.3V Dry, indoor HC-SR04+ (3.3V-5V tolerant, ~$3.50)
Sump pump monitoring / outdoor tank levels 5V or 3.3V Wet, humid, condensation risk JSN-SR04T (Waterproof separated probe, ~$12.00)
Default Recommendation: If you are using an Arduino Uno R3 for a standard dry-environment school or hobby project, buy the Standard HC-SR04. If you are using an ESP32, do not risk the standard 5V module without a voltage divider on the Echo pin; buy the HC-SR04+ instead.

Hardware Spec Sheet and Pin Mapping

Before wiring, verify your module's specifications. The HC-SR04 relies on a 40kHz piezoelectric transducer pair (one transmit, one receive). The speed of sound in air at 20°C is roughly 343 m/s, which dictates the math we use in the code.

HC-SR04 Specification Table

Parameter Value Practical Implication
Operating Voltage 5V DC Requires 5V VCC. 3.3V VCC will cause unstable triggers.
Logic Levels 5V TTL Echo pin outputs 5V. Do not connect directly to 3.3V MCU GPIO.
Measuring Range 2cm to 400cm 2cm "blind zone" due to transducer ringing time after TX pulse.
Beam Angle ~15 degrees Will not detect thin objects (like chair legs) outside this cone.
Trigger Pulse 10µs TTL HIGH Must be held HIGH for at least 10 microseconds to initiate burst.

Pin Mapping: Arduino Uno R3 to HC-SR04

This mapping targets the Arduino Uno R3 (ATmega328P). We use Pins 9 and 10 because they are standard digital I/O pins, leaving the hardware interrupt and PWM pins free for motor drivers if this is a rover build.

HC-SR04 Pin Arduino Uno R3 Pin Wire Color (Standard Convention)
VCC 5V Red
Trig Digital Pin 9 Yellow
Echo Digital Pin 10 Green
GND GND Black

Step-by-Step Wiring and Compilable Code

Follow these steps to wire the circuit and upload the robust firmware. This code avoids third-party libraries like NewPing to show you exactly how the hardware timing works, while implementing a moving average filter and explicit error handling to prevent the serial monitor from flooding with garbage data.

Wiring Steps

  1. De-energize the board: Unplug the Arduino Uno from your PC or wall adapter.
  2. Power the sensor: Connect the HC-SR04 VCC to the Arduino 5V pin, and GND to Arduino GND. (Do not use the 3.3V pin for VCC; the transducers will not oscillate reliably).
  3. Connect the Trigger: Run a jumper from HC-SR04 Trig to Arduino Digital Pin 9.
  4. Connect the Echo: Run a jumper from HC-SR04 Echo to Arduino Digital Pin 10. Note: If you later switch to an ESP32, you must put a 1kΩ/2kΩ voltage divider on this line.
  5. Verify connections: Tug gently on the Dupont connectors. The HC-SR04 header pins are notoriously brittle and can snap inside the plastic housing if forced.

Robust C++ Firmware (Target: Arduino Uno R3)

/*
 * Target Board: Arduino Uno R3 (ATmega328P)
 * Sensor: HC-SR04 (5V Logic)
 * Description: Robust distance measurement with temperature compensation,
 *              moving average filter, and explicit error handling.
 */

#define TRIG_PIN 9
#define ECHO_PIN 10
#define MAX_DISTANCE_CM 400
#define SPEED_OF_SOUND_CM_US 0.0343 // at 20°C
#define SAMPLE_SIZE 5

unsigned long durations[SAMPLE_SIZE];
int sampleIndex = 0;

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  
  // Ensure Trig is LOW on startup
  digitalWrite(TRIG_PIN, LOW);
  Serial.println("HC-SR04 Initialized. Waiting for stable readings...");
}

void loop() {
  // 1. Clear the trigger pin
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  
  // 2. Send 10us HIGH pulse to trigger the 40kHz burst
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  // 3. Read the echo pin. Timeout set to max distance + buffer.
  // Max time = 400cm / 0.0343 * 2 (round trip) = ~23323 us. We use 25000us.
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 25000);
  
  // 4. Error Handling & Data Validation
  if (duration == 0) {
    Serial.println("Error: Echo timeout (0). Check wiring or object out of range.");
    delay(100);
    return;
  }
  
  // 5. Calculate distance and apply to moving average array
  float distanceCm = (duration * SPEED_OF_SOUND_CM_US) / 2.0;
  
  // Reject physical impossibilities (sensor ringing / blind zone)
  if (distanceCm < 2.0) {
    Serial.println("Error: Inside 2cm blind zone.");
    delay(100);
    return;
  }
  
  durations[sampleIndex] = duration;
  sampleIndex = (sampleIndex + 1) % SAMPLE_SIZE;
  
  // 6. Calculate moving average
  unsigned long totalDuration = 0;
  for (int i = 0; i < SAMPLE_SIZE; i++) {
    totalDuration += durations[i];
  }
  float avgDistanceCm = ((totalDuration / SAMPLE_SIZE) * SPEED_OF_SOUND_CM_US) / 2.0;
  
  // 7. Output
  Serial.print("Raw: ");
  Serial.print(distanceCm, 1);
  Serial.print(" cm | Avg: ");
  Serial.print(avgDistanceCm, 1);
  Serial.println(" cm");
  
  // 60ms delay prevents echo interference (sensor needs time for sound to dissipate)
  delay(60);
}
Callout Tip: Temperature Compensation
The speed of sound changes by roughly 0.6 m/s for every 1°C change in air temperature. If your project operates in an unheated garage (e.g., 5°C), the hardcoded 0.0343 constant will introduce a ~2.5% error. For high-precision tank leveling, add a DS18B20 temperature sensor and calculate the exact speed of sound dynamically using the formula: V = 331.3 + (0.606 * Temp_C) (source: Engineering Toolbox).

Debugging: First Three Things to Check When It Fails

When the serial monitor spits out garbage or flatlines, do not immediately blame the sensor. The HC-SR04 is a dumb analog front-end; 90% of failures are timing or power issues on the microcontroller side. Here is the ranked troubleshooting path.

1. Symptom: Serial outputs Error: Echo timeout (0) continuously

What it means: The pulseIn() function waited for the Echo pin to go HIGH, but it never did within the 25ms timeout window. The Arduino reference for pulseIn() confirms it returns 0 on timeout.

  • Cause A (Most Likely): The Trig pin is not firing, or the 5V VCC is missing. Measure the VCC pin on the sensor with a multimeter. If it reads < 4.5V, your USB cable is suffering from voltage drop. Swap the cable.
  • Cause B: You are using an ESP32 or 3.3V board with a standard HC-SR04. The 3.3V Trig pulse is not high enough to trigger the module's internal logic gate. Fix: Use a logic level shifter or switch to the HC-SR04+.
  • Cause C: The Echo pin wire is broken or seated in the wrong breadboard row. Verify continuity with a multimeter in beep-test mode.

2. Symptom: Distance reads exactly 431.5 cm (or max range) regardless of object

What it means: The Echo pin went HIGH, but never went LOW before the timeout. The sensor fired the 40kHz burst, but the receive transducer is picking up ambient noise, or the internal comparator is latched.

  • Cause A (Most Likely): Acoustic interference. Another HC-SR04 in the room is firing, or a high-frequency inverter (like a cheap LED driver or bug zapper) is emitting 40kHz noise. Fix: Isolate the sensor or increase the delay() between readings to 100ms.
  • Cause B: The object is highly absorptive (like heavy acoustic foam or thick curtains) and angled away, scattering the sound wave so no echo returns. Fix: Tape a piece of hard cardboard to the target object to test.

3. Symptom: Readings are wildly unstable (jumping from 15cm to 300cm)

What it means: The microcontroller is catching the rising edge of the echo, but the signal is noisy, or the power rail is sagging when the transducer draws peak current (up to 15mA during the burst).

  • Cause A (Most Likely): Power supply brownout. Fix: Solder a 100µF electrolytic capacitor directly across the VCC and GND pins on the back of the HC-SR04 PCB. This local energy reservoir prevents voltage sag during the TX burst.
  • Cause B: Missing the 60ms inter-reading delay. If you ping the sensor too fast, the previous sound wave bounces around the room and triggers the next reading. Fix: Ensure delay(60) is at the end of your loop.

Extending and Simplifying the Build

Once you have stable baseline readings, you will likely need to adapt the circuit for your specific application. Here is how to scale the design up or strip it down.

How to Simplify (The Minimalist Approach)

If you are battery-constrained and just need a simple "object present" boolean (e.g., for a trash-can lid opener), drop the moving average array and the serial printing. Replace the math with a simple threshold check:

if (distanceCm < 30.0) {
  digitalWrite(RELAY_PIN, HIGH); // Open lid
} else {
  digitalWrite(RELAY_PIN, LOW);
}

Additionally, put the ATmega328P to sleep using the LowPower library, waking it via a hardware interrupt on a physical push-button, taking a single ping, and going back to sleep. This drops average current draw from ~45mA to under 50µA.

How to Extend (Multiplexing and Waterproofing)

Multiple Sensors: You cannot wire multiple HC-SR04 Trig pins together; they will cross-talk and deafen each other. Instead, use a single Echo pin and separate Trig pins, firing them sequentially with a 100ms gap. Alternatively, use an I2C multiplexer (like the TCA9548A) paired with I2C-based ultrasonic sensors (like the RCWL-1601) to save GPIO pins.

Liquid Level Sensing: The standard HC-SR04 will die within days if mounted over a water tank due to condensation shorting the exposed PCB traces. Upgrade to the JSN-SR04T. It uses the exact same 40kHz physics and identical 4-pin interface (VCC, Trig, Echo, GND), but the transducer is sealed on a 2.5-meter cable. Crucial note for the JSN-SR04T: Its blind zone is larger (approx. 20cm to 25cm). You must mount it high enough above the maximum water line to account for this.

By understanding the analog limitations of the 40kHz transducers and strictly managing your microcontroller's timing and power delivery, the Arduino and HC-SR04 combination remains one of the most cost-effective and reliable distance-measurement setups available on the bench today.