21. HC-SR04 Ultrasonic Distance
Trigger a 40 kHz burst, time the echo with pulseIn, convert microseconds to centimetres, and handle timeouts.
Learning outcomes
- Explain HC-SR04 time-of-flight: trigger, burst, echo HIGH pulse
- Write a valid 10 µs TRIG pulse and read ECHO with pulseIn timeout
- Convert echo duration to distance using duration * 0.0343 / 2
- Choose a pulseIn timeout for the maximum range you care about
- Recognise when temperature changes the speed of sound
Parts and preparation
Uno, HC-SR04 ultrasonic module, jumper wires, and flat hard targets (book, wall, box). Optional: DHT11 or other thermometer for a later temperature-corrected challenge.
Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.
What the HC-SR04 does
The module measures distance by time of flight of sound. You send a short pulse on TRIG. The sensor then emits a 40 kHz ultrasonic burst. If the sound hits a target and returns, ECHO goes HIGH for a time equal to the round-trip travel. Soft, angled, small or distant targets may give weak or missing echoes. Two modules firing at once can interfere with each other.
| Pin | Role |
|---|---|
| VCC | 5 V supply (typical module) |
| GND | Common ground with the Uno |
| TRIG | Arduino OUTPUT - start a measurement |
| ECHO | Arduino INPUT - HIGH while the echo pulse is active (5 V logic on classic modules) |
Trigger sequence
A valid trigger is a short HIGH pulse on TRIG, usually about 10 µs. Clear the line first with a brief LOW so the module sees a clean edge:
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);What pulseIn measures
After the trigger, call pulseIn on the echo pin. With pulseIn(echoPin, HIGH, 30000UL) the Arduino:
| Step | What happens |
|---|---|
| 1 | Waits for echoPin to go HIGH (start of the echo pulse) |
| 2 | Starts timing |
| 3 | Waits for echoPin to go LOW (end of the echo pulse) |
| 4 | Stops timing and returns the HIGH duration in microseconds |
| Timeout | If HIGH never arrives (or the pulse never ends) within the timeout, returns 0 - no hang |
unsigned long duration = pulseIn(echoPin, HIGH, 30000UL);
float distance_cm = duration * 0.0343 / 2;
if (duration == 0) {
Serial.println("Out of range or no echo");
} else {
Serial.print("Distance: ");
Serial.print(distance_cm, 1);
Serial.println(" cm");
}Common pulseIn timeouts
The third argument is the maximum wait in microseconds. Longer timeouts allow farther targets but block loop() for longer if there is no echo. 30000UL (30 ms) is a common classroom default.
| Timeout | Approx. max one-way distance |
|---|---|
| 10000UL | ~1.7 m |
| 20000UL | ~3.4 m |
| 30000UL | ~5.1 m (most common in this course) |
| 50000UL | ~8.5 m |
Distance formula: duration * 0.0343 / 2
This line turns echo time into centimetres. Break it into physics, not magic.
1) Speed of sound near 20 °C is about 343 m/s. In centimetres per microsecond that is 34300 cm/s ÷ 1 000 000 = 0.0343 cm/µs. So in one microsecond, sound travels about 0.0343 cm.
2) duration from pulseIn is a round trip: sensor → object → sensor. The sound path is twice the one-way distance.
3) duration * 0.0343 gives the round-trip path length in cm. Divide by 2 for sensor-to-object only.
| Step | Calculation | Meaning |
|---|---|---|
| 1 | duration * 0.0343 | Round-trip path length in cm |
| 2 | / 2 | One-way distance to the object |
Worked number example
Suppose duration = 2000 µs (2 ms).
Round trip: 2000 × 0.0343 = 68.6 cm.
One-way distance: 68.6 / 2 = 34.3 cm.
Sensor ----> object (outgoing) then <---- (return). Total time is out plus back, so always divide by 2.
Equivalent formulas
Some sketches use a single divide. At ~20 °C, round-trip time is about 58.2 µs per centimetre (2 / 0.0343 ≈ 58.3). These are the same idea with different rounding:
float distance_cm = duration * 0.0343 / 2; // at ~20 °C
// or
float distance_cm = duration / 58.2; // ~1 cm per 58.2 µs round trip
// or
float distance_cm = duration / 58.0; // common approximationTemperature changes the speed of sound
Speed of sound depends on air temperature. A useful estimate is:
speed (m/s) ≈ 331.3 + (0.606 × temperature_°C)
At 0 °C ≈ 331 m/s (0.0331 cm/µs). At 20 °C ≈ 343 m/s (0.0343 cm/µs). At 40 °C ≈ 355 m/s (0.0355 cm/µs).
For class labs, 0.0343 is fine. For tighter accuracy, measure temperature (for example with a DHT11) and scale the constant:
float tempC = 20.0; // replace with a real sensor reading when available
float speed_m_s = 331.3 + (0.606 * tempC);
float speed_cm_per_us = speed_m_s / 10000.0; // m/s -> cm/µs
float distance_cm = duration * speed_cm_per_us / 2.0;Echo quality and filtering
Aim the sensor squarely at a broad, hard surface. Soft fabric, sharp angles and narrow poles often fail. Median or moving-average filters can reduce occasional spikes, but they cannot fix bad geometry or a timeout (duration == 0).
Wiring and safe build sequence

- VCC -> 5 V
- GND -> GND
- TRIG -> D9
- ECHO -> D10 (5 V echo is fine on Uno; level-shift for 3.3 V boards such as many ESP32 modules)
Worked sketch
Download .ino sketchconst byte trigPin = 9;
const byte echoPin = 10;
void setup() {
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
Serial.begin(9600);
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
unsigned long duration = pulseIn(echoPin, HIGH, 30000UL);
if (duration == 0) {
Serial.println("Out of range or no echo");
} else {
float distance_cm = duration * 0.0343 / 2.0;
Serial.print("Distance: ");
Serial.print(distance_cm, 1);
Serial.println(" cm");
}
delay(100);
}How the code works
- The 10 µs HIGH on TRIG starts one measurement.
- pulseIn(..., 30000UL) returns the echo HIGH time in µs, or 0 after a 30 ms timeout so loop does not hang forever.
- distance_cm = duration * 0.0343 / 2 converts round-trip µs to one-way centimetres at about 20 °C.
- Print one decimal with Serial.print(distance_cm, 1).
Test and record evidence
Practical evidence checklist
Common faults and checks
- Swap-check TRIG and ECHO.
- Aim squarely at a broad hard target; soft or angled surfaces often return no echo.
- Ignore readings below the module minimum (often a few cm) - they are not trustworthy.
- If every reading is 0, confirm 5 V and GND, then try a closer hard target and a longer timeout while testing.
Check your understanding
Q1. Why divide the converted path length by 2?
Show answer
pulseIn times the round trip; distance to the object is one way.
Q2. What does pulseIn return if the echo never arrives within the timeout?
Show answer
0.
Q3. Why is 0.0343 used in duration * 0.0343 / 2?
Show answer
Sound travels about 0.0343 cm per µs near 20 °C; that converts µs to cm before dividing by 2.
Q4. Why pass 30000UL to pulseIn?
Show answer
It caps the wait at 30 ms so a missing echo does not freeze the sketch.