Unit D - Sensors & Buses

21. HC-SR04 Ultrasonic Distance

Trigger a 40 kHz burst, time the echo with pulseIn, convert microseconds to centimetres, and handle timeouts.

Estimated time 4 hours

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.

HC-SR04 Ultrasonic Distance instructional connection diagram

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.

HC-SR04 module sending an ultrasonic burst to a target and receiving the echo; one-way distance labelled under the round-trip path
Time of flight: sound travels to the target and back. ECHO HIGH time covers the full round trip (2d).
PinRole
VCC5 V supply (typical module)
GNDCommon ground with the Uno
TRIGArduino OUTPUT - start a measurement
ECHOArduino 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:

Timing diagram showing a short TRIG HIGH pulse of about 10 microseconds and a longer ECHO HIGH pulse whose width is the duration measured by pulseIn
TRIG starts the measurement; pulseIn times how long ECHO stays HIGH (round-trip microseconds).
StepWhat happens
1Waits for echoPin to go HIGH (start of the echo pulse)
2Starts timing
3Waits for echoPin to go LOW (end of the echo pulse)
4Stops timing and returns the HIGH duration in microseconds
TimeoutIf 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.

TimeoutApprox. 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.

Three-step diagram converting echo duration in microseconds to one-way distance in centimetres using times 0.0343 then divide by 2, with a worked example of 2000 us giving 34.3 cm
Worked path: 2000 us × 0.0343 = 68.6 cm round trip; divide by 2 → 34.3 cm one-way.
StepCalculationMeaning
1duration * 0.0343Round-trip path length in cm
2/ 2One-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 approximation

Temperature 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

Breadboard wiring for lesson 21: HC-SR04 Ultrasonic Distance
Breadboard layout for this lesson. Match colours and pins before powering the circuit. Click the image for a larger view.
  1. VCC -> 5 V
  2. GND -> GND
  3. TRIG -> D9
  4. ECHO -> D10 (5 V echo is fine on Uno; level-shift for 3.3 V boards such as many ESP32 modules)
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.
const 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

  1. The 10 µs HIGH on TRIG starts one measurement.
  2. pulseIn(..., 30000UL) returns the echo HIGH time in µs, or 0 after a 30 ms timeout so loop does not hang forever.
  3. distance_cm = duration * 0.0343 / 2 converts round-trip µs to one-way centimetres at about 20 °C.
  4. Print one decimal with Serial.print(distance_cm, 1).

Test and record evidence

Expected result: With a flat target in range, Serial prints Distance: xx.x cm that changes as you move the target. With no target or a target beyond the timeout range, it prints Out of range or no echo.

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.
Extension challenge: Take five successful readings (skip duration == 0) and print their median. Optional: use a temperature reading to replace 0.0343 with a calculated cm/µs constant.

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.