Detecting objects with HC-SR04

If you need to measure distances or detect objects, but you don’t have a large budget, the HC-SR04 Ultrasonic Sensor may be something for you.

Autonomous vehicles, HC-SR04 sensors

Step 1: Detecting objects with HC-SR04
Step 2: Multiple HC-SR04 with Arduino using interrupts
Step 2b: Multiple HC-SR04 with Arduino using only 1 interrupt pin
Step 3: Choosing the right vehicle
Step 4: Automatic steer correction with HC-SR04
Step 5a: Vehicle accessories – SSD1306 status display
Step 5b: Vehicle accessories – Rotary encoder illuminated button
Step 6: Autonomous vehicles, putting it all together
Alternative 1: Steer correction with HC-SR04: Single sensor, stop and scan
Alternative 2: Steer correction with HC-SR04: Servo controlled car

Echo location

The HC-SR04 is a breakout board that uses an “ultrasonic sensor and receiver” to measure the distance to an object. This is done by generating an “ultrasonic sound wave” (humans can’t hear this) that bounces of an object and returns to the sender. The sensor counts the time between the signal sent and received to determine how far the object is.
This is similar to the sonar sensors on ships, or the way sea creatures like dolphins, or land creatures like bats detect obstacles. This method is often referred to as “echo location”.

Echo location in bats
Echo location in bats

The HC-SR04 (from here on referred to as the sensor) works in a similar way by sending an ultrasonic pulse from the transmitter (labelled as “T” in the picture below) and counting the amount of time it takes for this pulse to be returned to the receiver (labelled as “R”). It then generates a data signal for the user to determine the distance.

HC-SR04 boards
HC-SR04 Ultrasonic sensor
HC-SR04 send and receive
HC-SR04 send and receive

Technical details

The HC-SR04 can detect (measure) objects at a distance from 3 centimetres to 4 meters (that is about 1 inch to 13 feet) with an accuracy of about 0.3cm (about 0.12 inch). It can measure in an angle of 15°. It works on 5VDC and draws about 15mA, so roughly a 75mW power consumption.

The sensor has 4 pins with the following connections:

  • Power supply for 5VDC (Vcc)
  • Ground (Gnd)
  • Trigger to start measurement (Trig)
  • Echo to read the result (Echo)

Trigger and echo

The way this sensor is used, is by setting a start pulse on the “trigger” pin. The senor will start the measurement and returns the result to the “echo” pin.

The start signal on the “trigger” pin needs to be

  1. at least 2µs LOW (0V) before it can be high again
  2. 10µs HIGH (+5V) to be considered a start pulse

Once a valid start pulse is detected on the trigger pin, the sensor will send 8 pulses of 40kH, and counts how long it takes for the signal to return. Lastly, it will send 1 pulse on the “echo” pin of which the HIGH time (+5V) represents the time (in microseconds) it took for the signal to be sent, bounced back on an object and be received by the sensor.

Trigger and echo signal HC-SR04
Trigger and echo signal HC-SR04

Calculating distance

If you know the speed of sound, which is approximately 343m/s, and you know the time it takes for a sound wave to travel from A to B, you can calculate the distance from A to B by the formula

distance = speed of sound * time

Since in this case the time is the pulse travelling to the object AND bouncing back, you need to divide the time by 2, so

distance = speed of sound * (time / 2)

The time from the sensor is in microseconds (µs) and the speed of sound is in meter per second (m/s).
With 1s = 1,000,000µs and 1m = 100cm, we can calculate the distance in centimetres (cm) by the formula

distance = (343 / 1000000 * 100) * (time / 2)
or
distance = 0.0343 * (time / 2)

Or if you prefer to calculate in inches, with
1 inch = 2.54cm – and so – 1 cm = 1/2.54 inch – or – 0.3937 inch
the formula is

distance (inch) = 0.0343 * (time / 2) * (1/2.54)
or
distance (inch) = 0.0135 * (time / 2)

A little example:
An object is about 20cm from the sensor.
The echo signal is high for 1180µs.
distance = 0.0343 * (1180 / 2) = 20.237 cm

Timing diagram: 1180µs = 20.24cm
Timing diagram: 1180µs = 20.24cm

Using the HC-SR04 with Arduino

Using the sensor with Arduino boards is pretty simple. Just send a trigger on any (free) pin of at least 10µs, and read the width of the echo pulse with the function pusleInLong().
You don’t need any additional libraries, but there are some available.

HC-SR04 on an Arduino Uno
HC-SR04 on an Arduino Uno

Code build-up

In this example, pin 2 will be used as the output pin that will send the trigger (start) signal to the sensor.
Pin 4 will be used as the input pin on which the data from the echo pin of the sensor will be received.

Step 1 – Define the pins you want to use

// Define the output pin for the trigger signal
pinMode(2, OUTPUT);
// Define the input pin for the echo signal
pinMode(4, INPUT);

Step 2 – Send a start signal (trigger)

The trigger needs to be 2µs low, then 10µs high for the sensor to tell it needs to start a measurement. This can be done by using the functions “digitalWrite(<pin>, <HIGH | LOW>)” to set the output low or high, and “delayMicroseconds(<µs>)” to determine the time in µs the signal must remain at that level.

// Set trigger signal ...
digitalWrite(2, LOW);   // set pin 2 LOW
delayMicroseconds(2);   // Wait 2µs
digitalWrite(2, HIGH);  // Set pin 2 HIGH
delayMicroseconds(10);  // Wait 10µs
digitalWrite(2, LOW);   // Set pin 2 low again

Step 3 – Calculate the distance

When the sensor has send its reply, the distance can be calculated.

// ... and wait for echo
traveltime_in_µs = pulseInLong(4, HIGH);   // Measure pulse width HIGH
// Calculate the distance (in cm)
distance_in_cm = 0.0343 * (traveltime_in_µs / 2);

Values higher than 400cm (23323µs) and lower than 2cm (117µs) are considered “out of range” and are very likely unreliable.

Simple code

Below is a very simple example of how you can measure the distance to an object with Arduino (Uno) and an HC-SR04 ultrasonic sensor.

#define triggerPin 2      // Pin number for the trigger
#define echoPin 4         // Pin number for the echo
#define soundSpeed 343.0  // Speed of sound in m/s (343m/s at 20°C with dry air) ; optional use DHT22 for auto calibration

long echoTime = 0;
float distance = 0;

/****************************************************************
      SETUP
****************************************************************/
void setup()
{
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for Serial
  }
  Serial.println("--- Serial monitor started ---");

  pinMode(triggerPin, OUTPUT);  // Set trigger pin as output
  pinMode(echoPin, INPUT);      // Set echo pin as input
}


/****************************************************************
      LOOP
****************************************************************/
void loop()
{
  // Initiate next trigger
  digitalWrite(triggerPin, LOW);    // Make sure trigger pin is low
  delayMicroseconds(2);             // for at least 2µs
  digitalWrite(triggerPin, HIGH);   // Set trigger pin HIGH
  delayMicroseconds(10);            // for at least 10µs
  digitalWrite(triggerPin, LOW);    // Set LOW again

  // Measure the HIGH time of the echo pulse
  echoTime = pulseInLong(echoPin, HIGH);

  // and calculate the distance (in cm)
  distance = float(echoTime) / 2 * (soundSpeed / 10000.0);
  
  // Print on serial monitor
  Serial.print("echo time = ");
  Serial.print((String)echoTime);
  Serial.print(" --> Distance = ");
  Serial.print((String)distance);
  Serial.println(" cm");

  // And wait for a while
  delay(500);
}

Using libraries in stead

If you are new to this and feel a little bit overwhelmed, there are a few libraries available to help with this. One of the most popular is NewPing which can be found on the Arduino Playground.

A few code tweaks

If you are up for a bit more challenge, the next part shows a few code tweaks, so the code becomes more usable for real projects.

  1. The speed of sound is influenced by the temperature and relative humidity.
  2. Polling for a measurement every 500ms by using methods like delay(), will pause the program resulting in very inefficient code.
  3. The function pulseInLong() makes the code wait for a pulse, which can take some valuable time if the object is out of the sensor’s reach.

Tweak 1: Take temperature and humidity into account

The speed of sound is not perfectly 343 m/s all the time. This average speed is at 20°C and dry air. So if the temperature or humidity change, the speed of sound will slightly change as well, and can be calculated with the formula below
(T is Temperature in °C and RH is Relative Humidity in %)

speed of sound = 331.4 + (0.606 * T) + (0.0124 * RH)

so the distance can be calculated by

distance = (331.4 + (0.606 * T) + (0.0124 * RH)) * (time / 2)

Example: T = 25°C and RH = 80%
speed of sound = (331.4 + (0.606 * 25) + (0.0124 * 80)) = 347.542 (m/s)

DHT22 with Arduino

For measuring the temperature and relative humidity, you can use a DHT22 (or similar) and use these measurements to calculate the speed of sound accurately.

// Using the library SimpleDHT for the DHT22
dht22.read2(&tempDHT22, &rhDHT22, NULL);

// Calculate the speed of sound
speed_of_sound = 331.4 + (0.606 * tempDHT22) + (0.0124 * rhDHT22));

// Send trigger for HC-SR04 
/* insert code for trigger here */

// Wait for echo
traveltime_in_µs = pulseInLong(echoPin, HIGH);   // Measure pulsewidth HIGH

// Calculate the distance (in cm)
distance_in_cm = speed_of_sound * (traveltime_in_µs / 2);

Tweak 2: Efficient polling

To replace the delay() function, simply write a polling function that counts the time passed. This can be done with the function millis()

// Definition of the variables
unsigned long lastMillis;   // is uint32_t
int pollTime = 500;

// Continuous loop
void loop()
{
  // Has pollTime passed ?
  if (millis() - lastMillis >  pollTime) {
    /* Add code for trigger and echo here */
    lastMillis = millis(); 
  }
  ...

Tweak 3: Getting the data

The function “pulseInLong(<pin>, HIGH)” will wait for the signal on the <pin> to become high, and back low. When an object is out of range, this time can be longer than 20ms, which is very long with time critical applications. So you may want to replace this method with something that won’t pause the code. One way to do this, is by using interrupts.

Since interrupts are a bit more complex they will not be covered in detail in this tutorial, but you can find more information on interrupts with Arduino on the Arduino reference page.

Below you can find a sample code to get you started with the HC-SR04 using interrupts.

Interrupts with Arduino Uno

The Arduino Uno has 2 interrupt pins: D2 and D3.
In this example, pin D4 is used for trigger (no interrupt pin required)
and pin D2 is used for echo, which is INT0 (interrupt 0).

#define triggerPin 4      // Pin number for trigger signal
#define echoPin 2         // Pin number for echo signal (interrupt pin)
#define pollTime 50       // How many milliseconds between polls
#define soundSpeed 343.0  // Speed of sound in m/s

volatile unsigned long startTime;
unsigned long travelTime;
float distance;
unsigned long lastPoll;

void setup()
{
  pinMode(triggerPin, OUTPUT);   // Pin 4 as triggerpin (output)
  pinMode(echoPin, INPUT);       // Pin 2 [INT0] as echopin (input)
  lastPoll = millis();
  // Attach function call_INT0 to pin 2 when it CHANGEs state
  attachInterrupt(0, call_INT0, CHANGE );  // Pin 2 -> INT0
}

void loop()
{
  // Poll every x ms
  if (millis() - lastPoll > pollTime)
  {
    doMeasurement();
    lastPoll = millis();
  }
}

void doMeasurement()
{
  // First measurement(s) will be 0
  noInterrupts();   // cli()
  distance = travelTime * (float)soundSpeed / 20000.0;
  interrupts();     // sei();

  // Initiate next trigger
  digitalWrite(triggerPin, LOW);    // LOW
  delayMicroseconds(2);             // for 2µs
  digitalWrite(triggerPin, HIGH);   // HIGH
  delayMicroseconds(10);            // for 10µs
  digitalWrite(triggerPin, LOW);    // Set LOW again
}

// Interrupt handling for INT0 (pin 2 on Arduino Uno)
// Keep this as FAST and LIGHT (cpu load) as possible !
void call_INT0()
{
  byte pinRead;
  pinRead = PIND >> 2 & B0001;    // same as digitalRead(2) but faster

  unsigned long currentTime = micros();  // Get current time (in µs)
  if (pinRead)
  {
    // If pin state has changed to HIGH -> remember start time (in µs)
    startTime = currentTime ;
  }
  else
  {
    // If pin state has changed to LOW -> calculate time passed (in µs)
    travelTime = currentTime - startTime;
  }
}

Conclusion

The HC-S04 is a low cost ultrasonic sensor, that is very easy to implement in your projects. It’s widely available and can be purchased per 10 with great discounts. This makes it an ideal sensor for any hobbyist who is low on budget.


References
Arduino Boards: Arduino Uno R3 – Arduino Nano V3 – Arduino Micro
Arduino Software: Arduino IDE
Arduino Playground: Library NewPing
Arduino forum: Arduino Micro Interrupts 6
Arduino Reference: Port RegistersattachInterrupt()interrupt()noInterrrupt()pulseIn()pulseInLong()Servo library
Random Nerd Tutorials: Complete Guide for Ultrasonic Sensor HC-SR04 with Arduin
Circuit Digest: Arduino Interrupts Tutorial
Electro Noobs: Arduino register control, timers and interruptions
Wikipedia: Speed of sound – Echolocation