Embedded

Ultrasonic Sensor Arduino

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: Trigger the HC-SR04 with a 10 µs pulse, measure how long its ECHO line stays HIGH with pulseIn(), then divide that round-trip time by 58 to get the distance in centimetres.

    The HC-SR04 is the sensor nearly every embedded engineer meets first, and for good reason: it turns the abstract idea of "measuring time to infer distance" into four pins and a dozen lines of code. But a surprising number of hobby builds get it subtly wrong — floating triggers, 5 V damaging a 3.3 V pin, or a distance formula copied without understanding. This guide walks through the wiring, the timing contract, and the maths, with the numbers that actually matter on the bench.

    How the HC-SR04 works

    The module is a self-contained time-of-flight system. When you pull its TRIG pin HIGH for at least 10 microseconds, the onboard controller emits an eight-cycle burst of 40 kHz ultrasound from the transmit transducer. The sound travels out, reflects off the nearest surface, and returns to the receive transducer. The module then drives its ECHO pin HIGH for exactly as long as the sound was in flight — the round trip. Your job is simply to measure that pulse width.

    ⚡ The speed of sound in dry air at 20 °C is about 343 m/s, or 0.0343 cm/µs. Because the echo covers the distance twice (out and back), distance = (pulse_µs × 0.0343) / 2, which simplifies to pulse_µs / 58.3.

    Time of flight: out and back HC-SR04 TX RX wall 40 kHz burst out echo returns d = (t × 0.0343) / 2 CourseTron · coursetron.com
    Time-of-flight principle: the controller times the gap between the burst and its echo.

    Wiring it to the Arduino

    The module has four pins: VCC, TRIG, ECHO, and GND. VCC needs a clean 5 V — the 40 kHz driver draws current spikes, so a brown-out on a shared regulator shows up as erratic readings. Connect TRIG to any digital output (say D9) and ECHO to any digital input (D10).

    HC-SR04 pinArduino UnoNote
    VCC5 VKeep leads short; add a 100 µF cap near VCC/GND
    TRIGD9 (output)Idle LOW
    ECHOD10 (input)5 V output — see level shifting below
    GNDGNDCommon ground is mandatory

    ⚠️ ECHO outputs a full 5 V pulse. On a 3.3 V board (ESP32, STM32, RP2040) that pin is not 5 V-tolerant — feed ECHO through a resistor divider (e.g. 1 kΩ / 2 kΩ dropping 5 V to ~3.3 V) or a logic-level shifter, or you will eventually damage the GPIO.

    Wiring: HC-SR04 to Arduino Uno HC-SR04 VCC TRIG ECHO GND Arduino Uno 5 V D9 (out) D10 (in) GND power trigger (idle LOW) echo (5 V pulse) common ground On 3.3 V boards, divide ECHO down first. CourseTron · coursetron.com
    Pin-to-pin wiring from the HC-SR04 to an Arduino Uno, matching the table above.

    The timing contract

    The datasheet specifies a strict handshake. First, make sure TRIG has been LOW for a few microseconds. Then pulse it HIGH for 10 µs and bring it back LOW. The module responds by holding ECHO HIGH for a width between roughly 150 µs (about 2 cm, the module's blind spot) and 25 ms (its ~4 m ceiling). If nothing is in range, ECHO stays HIGH for ~38 ms before timing out — which is why your reading code needs a timeout guard, not a blocking wait.

    TRIG pulse and ECHO width TRIG 10 µs HIGH ECHO width is proportional to distance CourseTron · coursetron.com
    A 10 µs trigger produces an ECHO pulse whose width (marker sweep) grows with distance.

    The distance-calculation code

    The canonical approach uses Arduino's pulseIn(), which blocks until ECHO goes HIGH, times how long it stays HIGH, and returns microseconds. Note the timeout argument (30000 µs ≈ 5 m) so a missing echo does not stall your loop for a full 38 ms:

    const int TRIG = 9, ECHO = 10;
    
    void setup() {
      Serial.begin(9600);
      pinMode(TRIG, OUTPUT);
      pinMode(ECHO, INPUT);
      digitalWrite(TRIG, LOW);
    }
    
    long readDistanceCm() {
      digitalWrite(TRIG, LOW);  delayMicroseconds(3);
      digitalWrite(TRIG, HIGH); delayMicroseconds(10);
      digitalWrite(TRIG, LOW);
      long us = pulseIn(ECHO, HIGH, 30000UL); // timeout 30 ms
      if (us == 0) return -1;                 // out of range
      return us / 58;                         // round trip -> cm
    }
    
    void loop() {
      long cm = readDistanceCm();
      if (cm < 0) Serial.println("out of range");
      else { Serial.print(cm); Serial.println(" cm"); }
      delay(60); // >=60 ms between pings avoids echo overlap
    }

    Two numbers are load-bearing. The / 58 comes directly from the physics above — 2 / 0.0343 ≈ 58.3 µs per centimetre. And the delay(60) enforces a minimum ping interval: fire again too soon and a late echo from the previous burst (reflecting off a far wall) corrupts the next reading. The datasheet recommends at least 60 ms.

    ?️ Air temperature matters more than beginners expect. Speed of sound rises ~0.6 m/s per °C, so a fixed 343 m/s constant drifts about 3% from 0 °C to 30 °C — roughly 3 cm of error at a 1 m range. For precision work, compensate with v = 331.4 + 0.6·T using a thermistor reading.

    Beyond the basics: filtering and non-blocking reads

    Raw ultrasonic data is noisy. Soft, angled, or small targets scatter the beam; the ~15° cone also picks up off-axis clutter. Two cheap wins: take 3–5 readings and use the median (not the mean — a single spurious 400 cm spike wrecks an average but not a median), and reject any sample outside the physical 2–400 cm window. For robotics you will also want to drop pulseIn() entirely and read ECHO with a pin-change interrupt plus micros(), so the CPU is free to run motor control while the echo is in flight.

    If you are prototyping a product rather than a toy, this is also where simulation earns its keep. Timing-sensitive firmware is exactly what a logic analyser or an instruction-set simulator catches before you cut a PCB — the same verification mindset the industry applies at scale with tools like Synopsys and Siemens EDA. For a guided path from breadboard to tape-out thinking, our partners at ChipXpert's VLSI training cover that progression in depth.

    Where to go next

    Once you can read a distance reliably, the same event-driven, timeout-guarded pattern carries over to almost every digital sensor. A natural companion is motion sensing — see our walkthrough on interfacing a PIR sensor with Arduino, which uses interrupts instead of polling. If you want to log these readings somewhere useful, Arduino serial plotting and data logging shows how to visualise and capture the stream. The full build-up sits inside our embedded systems courses.

    Ready to build real embedded projects from sensor to firmware? Join the CourseTron Embedded Systems with Arduino course and go from blinking an LED to interrupt-driven, production-grade sensor code. Explore the course →

    Frequently asked questions

    Why does my HC-SR04 read 0 or a huge random number?

    A reading of 0 (or -1 with the code above) means the echo timed out — the target is beyond ~4 m, too soft to reflect, or at too steep an angle. Wild jumps usually mean a floating TRIG, a missing common ground, or pings fired closer than 60 ms apart so echoes overlap.

    Can I use the HC-SR04 with a 3.3 V board like an ESP32?

    Yes, but the ECHO pin outputs 5 V and most 3.3 V GPIOs are not 5 V-tolerant. Drop ECHO through a resistor divider or a level shifter. TRIG can usually be driven directly, since 3.3 V reliably registers as HIGH at the module's input.

    How accurate is it, really?

    Under good conditions (flat, hard target, stable temperature) you can expect roughly ±1 cm from about 5 cm to 2 m. Accuracy degrades with temperature drift, soft targets, and beyond ~3 m. It is excellent for obstacle avoidance, not for precision metrology.

    What is the minimum measurable distance?

    About 2 cm. Below that the transmit and receive transducers are still "ringing" from the outgoing burst when the echo returns, so the module cannot separate them. This is the sensor's blind zone.

    Advertisement