Lesson 04Easy30 minOʻZ · РУ · ENIn the simulator

Measuring distance with the HC-SR04

How the HC-SR04 turns an echo into centimetres: trigger and echo pulses, why the formula divides by 58, pulseIn() timeouts, what fools the sensor, and a median filter.

Videoon YouTube

Key points

  • The ECHO pulse lasts as long as the sound's trip to the object and back.
  • Distance in cm = pulse length in microseconds / 58, for sound at about 343 m/s (20 °C).
  • pulseIn() with a 25 ms timeout returns 0 when no echo comes back; the sketch turns that into 400.
  • Wait about 60 ms between pings, so that old echoes can die away.
  • The median of three readings removes a single wrong reading.

The HC-SR04 measures distance with sound. It sends a short burst of ultrasound and measures how long the echo takes to come back. The farther away the object is, the longer the wait.

How an echo becomes a distance

The HC-SR04 sends a 40 kHz sound, the wall sends an echo back, and the time t in microseconds gives the distance: d = t / 58 cm.

Sound travels through air at about 343 m/s at 20 °C. When it hits a wall, part of it bounces back. If you know how long the sound was away, you know how far it went. Bats use the same idea to find their way in the dark.

The HC-SR04 uses sound at 40 kHz. People hear only up to about 20 kHz, so you hear nothing. Its two round "eyes" are a small speaker, marked T (transmitter), and a microphone, marked R (receiver).

The four pins

Pin What it does On the robot car
VCC Power, 5 V L298N +5V
TRIG Input: a short pulse here starts a measurement D9
ECHO Output: a pulse as long as the sound's trip D10
GND Ground A GND pin of the Uno

The sensor is made for 5 V. Never connect VCC to the battery.

FromToWhy
Battery +L298N +12VMotor power: about 6 V from four AA cells
Battery -L298N GNDThe battery’s minus
L298N +5VUno 5VThe driver’s regulator powers the Uno: about 4.4 V
L298N GNDUno GNDCommon ground: the signals need the same 0 V
Uno D5L298N ENALeft motor speed (PWM); the ENA jumper is removed
Uno D4L298N IN1Left motor direction
Uno D7L298N IN2Left motor direction
Uno D8L298N IN3Right motor direction
Uno D12L298N IN4Right motor direction
Uno D6L298N ENBRight motor speed (PWM); the ENB jumper is removed
Left motor M+L298N OUT1
Left motor M-L298N OUT2
Right motor M+L298N OUT4Swapped on purpose: this motor is mounted mirrored
Right motor M-L298N OUT3Swapped on purpose: this motor is mounted mirrored
HC-SR04 VCCL298N +5V5 V for the sensor
HC-SR04 GNDUno GNDGround
Uno D9HC-SR04 TRIGStarts a measurement: a 10 µs pulse
Uno D10HC-SR04 ECHOA pulse as long as the echo’s trip there and back
Parts for this lesson

One measurement, step by step

  1. The Uno sets TRIG LOW for 2 microseconds (µs), so that the pulse starts from a clean LOW.
  2. It sets TRIG HIGH for 10 µs and then LOW again. This pulse means "measure now".
  3. The sensor sends a burst of eight pulses of 40 kHz sound.
  4. As soon as the sound has left, the sensor sets ECHO HIGH.
  5. When the echo comes back, the sensor sets ECHO LOW.
  6. The length of the HIGH pulse on ECHO is the time the sound needed to reach the object and come back.

The sketch

DistanceMeter.ino
// Distance meter: measures with the HC-SR04 and prints centimetres to the Serial Monitor.
// The median of three readings throws away a single wrong echo.
// HC-SR04: VCC -> 5 V, GND -> GND, TRIG -> D9, ECHO -> D10

const byte TRIG = 9, ECHO = 10;

// Distance in centimetres; 400 when no echo came back (nothing within about 4 m).
int readDistanceCm() {
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);            // at least 10 microseconds starts a measurement
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, 25000UL);  // echo pulse length, 0 after 25 ms
  if (us == 0) return 400;
  return us / 58;                      // there and back at 343 m/s: 58 us per cm
}

int median3(int a, int b, int c) {
  if ((a <= b && b <= c) || (c <= b && b <= a)) return b;
  if ((b <= a && a <= c) || (c <= a && a <= b)) return a;
  return c;
}

void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  Serial.begin(9600);
}

void loop() {
  int a = readDistanceCm();
  delay(60);                           // let the last echo die away
  int b = readDistanceCm();
  delay(60);
  int c = readDistanceCm();
  Serial.print("Distance: ");
  Serial.print(median3(a, b, c));
  Serial.println(" cm");
  delay(60);
}

readDistanceCm() does steps 1 to 6 and turns the pulse into centimetres. median3() picks the middle one of three readings. loop() measures three times, 60 ms apart, and prints the middle value. setup() makes TRIG an output and ECHO an input, and starts Serial. The delay(60) at the end of loop() keeps the same pause between the last ping of one pass and the first ping of the next. The next sections explain each number.

Why divide by 58?

At 343 m/s, sound travels 0.0343 cm in one microsecond. So one centimetre takes about 29 µs. But the sound must go there and back: 2 cm of travel for every centimetre of distance. So each centimetre of distance adds about 58 µs to the ECHO pulse:

distance in cm = ECHO pulse in µs / 58

ECHO pulse Distance
580 µs 10 cm
1,160 µs 20 cm
5,800 µs 100 cm
23,200 µs (about 23 ms) 400 cm, the sensor's limit

In the sketch, us / 58 divides whole numbers, so the result is a whole number too. The part after the point is dropped: 1,199 / 58 gives 20, not 20.7. For a robot, steps of 1 cm are precise enough.

pulseIn() and its 25 ms timeout

pulseIn(ECHO, HIGH, 25000UL) waits for ECHO to go HIGH, measures how long it stays HIGH, and returns that time in microseconds. UL marks 25000 as an unsigned long, the type of the timeout.

The third number is the timeout: 25,000 µs, or 25 ms. If the pulse has not ended by then, pulseIn() stops waiting and returns 0. Why 25 ms? An echo from 4 m, the sensor's limit, takes about 23 ms. So 25 ms covers every echo in range, with a small margin. Without a timeout, pulseIn() waits up to one second, and during that time the sketch does nothing else.

Even with the timeout, pulseIn() makes the sketch wait while the sound travels: about 6 ms for a wall 1 m away, and the full 25 ms when no echo comes. For a robot, such short waits are acceptable. Long waits are a problem, as the lesson Programs that never wait shows.

When nothing is within about 4 m, no echo ends the pulse in time, and pulseIn() returns 0. The sketch turns that into 400:

if (us == 0) return 400;
return us / 58;

400 means "nothing within about 4 m". It is a large number on purpose: a check such as distanceCm < 25 then treats "no echo" as "the way is clear".

Why wait 60 ms between pings?

After a ping, the sound keeps bouncing around the room for a while. If the next ping starts too soon, an old echo from a far wall can arrive and look like a new, near one. So the sketch waits 60 ms between pings. In 60 ms, sound travels about 20 m. By then, the old echoes are too weak to fool the sensor.

The median of three

Sometimes one reading is simply wrong. An echo gets lost and you read 400. Or an old echo arrives and you read too little. One wrong number can make a robot turn for no reason.

So the sketch measures three times, 60 ms apart, and keeps the middle value, called the median. median3() first checks whether b lies between a and c, in either order. If it does, b is the median. If not, it checks a in the same way. If neither is in the middle, c must be.

Readings Sorted Median Average
52, 51, 53 51, 52, 53 52 52
52, 400, 51 51, 52, 400 52 about 168

One wrong reading cannot get through, as long as the other two agree. An average, in contrast, is ruined by it. The price is speed: each printed result needs three pings, so a new line comes only four or five times a second, instead of about sixteen.

What the sensor cannot see

  • Too close or too far. It measures from about 2 cm to about 4 m. Closer than 2 cm, the readings are wrong.
  • Beside the robot. It listens in a narrow cone, about 15 degrees wide. It sees what is ahead, not what is beside the robot. If two objects are in the cone, you get the nearer one.
  • Soft surfaces. Curtains, sofas, foam and clothes absorb sound. The echo is weak or missing.
  • Slanted surfaces. A wall met at a steep angle reflects the sound away, as a mirror reflects light. You read 400, or a distance that is too long if the sound comes back by another wall. The simulator's sensor also ignores a wall that it meets at a steep angle.
  • Small or thin objects. A pen or a chair leg sends back too little sound and can be missed.

Temperature

The speed of sound changes by about 0.6 m/s for every degree Celsius. The number 58 assumes about 20 °C. Here is what the sketch shows for a wall exactly 1 m away:

Air temperature Speed of sound The sketch shows about
0 °C about 331 m/s 104 cm
20 °C about 343 m/s 100 cm
35 °C about 352 m/s 98 cm

For a robot that avoids walls, a few centimetres do not matter. For a measuring tool that you use in winter and in summer, they do.

Reading the Serial Monitor in the arena

Serial.begin(9600) in setup() opens the serial link at 9600 baud. Each pass of loop() then prints one line, such as Distance: 57 cm.

Start the robot in an arena and open the Serial Monitor. What you will see:

  • A new line four or five times a second.
  • The number is the distance to whatever is in front of the sensor.
  • If nothing is within about 4 m, the line says Distance: 400 cm.
  • The robot stands still, because this sketch never drives the motors.

The arena's live distance readout shows the distance too, so you can check the sketch's numbers against it. If every line says 400 although a wall is close, check the wiring. A sensor without power, or with a loose TRIG or ECHO wire, never answers. On a real robot, open the Serial Monitor in the Arduino IDE and choose 9600 baud, the same number as in Serial.begin().

Next

The Obstacle avoider uses readDistanceCm() while the robot drives. The lesson Programs that never wait shows how it measures and moves at the same time. The Keep distance robot uses the same function to follow your hand.

Check yourself

1The ECHO pulse lasts 1740 microseconds. How far away is the object?

1740 / 58 = 30, so the object is about 30 cm away.

2Why does the sketch give pulseIn() a timeout of 25 ms?

An echo from 4 m, the sensor's limit, takes about 23 ms. Without a timeout, pulseIn() could wait up to one second, and the sketch could do nothing else.

3Three readings are 48, 400 and 47. What does median3() return?

It returns 48, the middle value, so the lost echo (400) is ignored. The average would have been 165.

Task

Make the Uno's L LED (D13) light up when the measured distance is below 50 cm, and go dark otherwise. Run the sketch in an arena and compare the LED with the Serial Monitor. If nothing is that close to the robot, change 50 to a value just above the distance on the screen and check that the LED lights.

Hint

Store the median in a variable first, so that you can both print it and compare it.