HC-SR04 - ultrasonic distance sensor

The HC-SR04 is a popular ultrasonic distance sensor. It is cheap and easy to use. The sensor has on speaker and one microphone. The speaker sends out a ultrasonic pulse and the microphone listens for the echo. The time between sending the pulse and receiving the echo can be used to calculate the distance.

Prerequisites

We assume that you have set up your development environment as described in the IDE tutorial.

We also assume that you have flashed your device with Jaguar and that you are familiar with running Toit programs on it. If not, have a look at the Hello world tutorial.

We recommend that you have finished the LED tutorial before starting this tutorial.

Setup

Connect the HC-SR04 as follows:

  • VCC to 3.3V/VCC or 5V/VIN (see below).
  • GND to GND.
  • TRIG to pin 33.
  • ECHO to pin 32, but see paragraph below.
  • Optionally, add a 100kΩ resistor (or higher) between pin 33 and GND.

The 100kΩ resistor is just a pull-down resistor that ensures that the TRIG pin isn't floating when not in use. When floating, the sensor could trigger, sometimes frequently, when our program isn't running.

There are multiple versions of the HC-SR04, each with different features and voltage requirements. The original HC-SR04 is designed to run on 5V, whereas the HC-SR04+ and the newer HC-SR04 from 2021 are designed to run on 3.3V, too.

The HC-SR04+ can be identified by the "+" printed on the back of the module. The newer HC-SR04 can be identified by the year 2021 printed on the back.

If you have the HC-SR04+ or the newer HC-SR04, then you can power the module with 3.3V, and you can connect the ECHO pin directly to the ESP32. If you have an original HC-SR04 module (without a "+"), then you should power it with 5V. Since the ESP32 is not 5V tolerant, you shouldn't connect the ECHO pin directly to the ESP32. There are two easy ways to reduce the voltage to a safe level:

  • Use an LED.
  • Implement a voltage devicer.

LEDs have forward voltage drop of around 2V, so the voltage on the ECHO pin will be around 3V, which is safe for the ESP32. See the ADC tutorial for a setup where we measure the LED voltage drop. The value of the 1kΩ resistor is relatively arbitrary, but ensures that the LED is used in a linear regime where we can trust that the signal is correctly received by the ESP32 and where the voltage drop is around 2V. It should, however, be at least 220Ω to ensure that the LED doesn't draw too much current.

HC-SR04 wiring diagram with an LED
HC-SR04 wiring diagram with an LED
HC-SR04 schematics with an LED
HC-SR04 schematics with an LED

A more typical way to reduce the voltage is to use a voltage divider. A voltage divider is a simple circuit that divides the voltage depending on the resistors used. We suggest to use a 1kΩ and a 680Ω resistor, which yields a voltage of around 3V on the ECHO pin. Using two similar resistors would also work but would yield a voltage of around 2.5V, which is close to the lower limit of the ESP32's detection limit for input-high (2.475V).

HC-SR04 wiring diagram with a voltage divider
HC-SR04 wiring diagram with a voltage divider
HC-SR04 schematics with a voltage divider
HC-SR04 schematics with a voltage divider

Measuring by hand

This section is mostly for educational purposes. If you just want to measure distances, you should use the hardware driver described below.

Conceptually, the HC-SR04 works as follows: after receiving a pulse on the TRIG pin (of ~10us), it sends out an ultrasonic pulse. At that moment it sets the ECHO pin high and waits for an echo. The moment it receives the echo, it sets the ECHO pin low again. The time between the ECHO pin going high and low thus corresponds to the time it took for the ultrasonic pulse to travel to the object and back. This time can be used to calculate the distance to the object.

Here is a simple program that measures the distance to an object and prints it to the console.

Save it as ultra.toit and watch it with Jaguar.

import gpio

TRIGGER ::= 33
ECHO ::= 32

measure-distance trigger echo:
  trigger-start := Time.monotonic-us
  trigger.set 1
  while Time.monotonic-us < trigger-start + 10:
    // Do nothing while waiting for the 10us.
  trigger.set 0

  while echo.get != 1: null
  echo-start := Time.monotonic-us
  while echo.get == 1: null
  echo-end := Time.monotonic-us
  diff := echo-end - echo-start
  return diff / 58

main:
  trigger := gpio.Pin TRIGGER --output
  echo := gpio.Pin ECHO --input
  while true:
    print "measured $(measure-distance trigger echo)cm"
    sleep --ms=500

The measure-distance function does all the work:

  • Set the trigger to 1 for at least 10μs.
  • Wait for the echo pin to go high.
  • Measure the duration of the echo-pin pulse.
  • Convert the duration in microseconds to centimeters.

Exercises

As long as the connections were done correctly it is difficult to damage your hardware by changing your program. Just make sure that the ECHO pin is always an input pin.

  • Given that the speed of sound is 343m/s, explain the constant 58 to convert from measured microseconds to centimeters.
  • Repeat the measurement multiple times and average it, to get a more accurate distance.
  • Find a material that absorbs ultrasonic waves and is thus "invisible" to the sensor.
  • What's the furthest distance the sensor is able to detect?

Measuring using the hardware

Measuring the length of the echo pulse in Toit works but it is a bit brittle. Toit is not designed to be a real-time language, and with all the capabilities of the ESP32 chip, it's rarely necessary to do real-time work. Case in point, there is a great ESP32 peripheral "RMT" (Remote Control Transceiver) that can do the time measurement in hardware for us.

The published hc_sr04 package uses the hardware and is thus more precise and uses less CPU.

Install the package as usual with jag pkg install:

jag pkg install github.com/lask/toit-hc-sr04@v2

See the package tutorial for more information about packages.

We can now simplify the code:

import gpio
import hc-sr04

TRIGGER ::= 33
ECHO ::= 32

main:
  trigger := gpio.Pin TRIGGER
  echo := gpio.Pin ECHO
  sensor := hc-sr04.Driver --echo=echo --trigger=trigger

  while true:
    distance := sensor.read-distance
    print "measured $distance mm"
    sleep --ms=500

Measuring the echo pulse is now done in hardware and the resulting values should be much more precise. The measurements are also unaffected by other containers that could use the CPU.