0 - Introduction
The HC-SR04 is one of the most popular distance sensors you can buy for 5V microcontrollers. Known for it’s accuracy, ease of use and cheap price, it is the best option out there if you have an Arduino Uno or Mega. This sensor is great for any project that involves obstacle detection, autonomous navigation or even auto parking.
Before you start make sure that you the Arduino IDE installed (tutorial on how to install it here) and also that you have one Arduino Uno (buy here) or Mega (buy here), Nano does not work, must be a 5V microcontroller, and also that you have the HC-SR04 sensor (buy here).
If you want to, you can check the datasheet for the sensor here.
1 - Code
Let’s start by making the class that will handle the sensor. We will need two pins (trigger and echo), the last value read, last read time and a cache time.
Why do we need a cache? This sensor, if read too fast, can return zeroes instead of the actual measurement. The datasheet recommends 10 milliseconds between reads, so that’s what we added, if you try to read two times in less than 10 milliseconds you get the same value twice and the arduino does not interact with the sensor, only after 10 ms will it call ‘getReading’ again.
Now that you understand why we need a cache, let’s make the functions. We will have a private function called ‘getReading’ that will actually change the pins and get the measurement from the sensor, then we will have two public functions, one to get the distance in millimeters and one for centimeters, those two functions will check how much time has elapsed before calling ‘getReading’ again. Lastly, you can create a constructor that takes in the pins and cache time and a setter for the cache time, so that you can change it while your Arduino is running.
#ifndef _hcsr04_h_
#define _hcsr04_h_
#include <Arduino.h>
class HCSR04
{
private:
int m_trig; // trigger pin
int m_echo; // echo pin
double m_lastread; // cached reading
uint64_t m_lastreadtime; // last reading time
uint16_t m_cachetime; // cache invalidation time
// Private, gets the distance in mm
float getReading();
public:
/* @brief Creates an HCSR04 class with the given pins
* @param trig Trigger pin, will be set as out
* @param echo Trigger pin, will be set as in
* @param cachetimems Min time in ms to get a new reading */
HCSR04(int trig, int echo, uint16_t cachetimems = 10);
/* @brief Get's the distance in millimeters
* @warning By default, value is cached for 10ms */
float distmm();
/* @brief Get's the distance in centimeters
* @warning By default, value is cached for 10ms */
float distcm();
/* @brief Sets a new cache time
* @warning Values smaller than 10ms can result in readings returning 0 */
void setCacheTime(uint16_t newval_ms);
};
#endif
On the constructor, initialize all the variables and set the trig pin mode to output and the echo pin to input:
#include "hcsr04.h"
HCSR04::HCSR04(int trig, int echo, uint16_t cachetimems)
{
m_trig = trig;
pinMode(m_trig, OUTPUT);
m_echo = echo;
pinMode(m_echo, INPUT);
m_lastread = 0;
m_lastreadtime = 0;
m_cachetime = cachetimems;
}
The function ‘getReading’ is what actually talks to the arduino. In this function we switch the trigger pin to high for a couple of microseconds and then we read the duration that the echo pin stayed high, then to calculate the distance in millimeters, you can use the formula in the datasheet or just copy the value in the code below:
float HCSR04::getReading()
{
digitalWrite(m_trig, LOW); // Set trigger to low
delayMicroseconds(2); // Wait 2us
noInterrupts(); // Disable interrupts
digitalWrite(m_trig, HIGH); // Set trigger to high
delayMicroseconds(10); // Wait 10us
digitalWrite(m_trig, LOW); // Set trigger to low
unsigned long t = pulseIn(m_echo, HIGH, 23529.4); // Get time (3rd arg is timeout +/- 4 meters)
interrupts(); // Enable interrupts
return (float)t / (float)5.88235; // Number in HCSR04's datasheet
}
The ‘distmm’ function is what you will call to get a reading from the sensor. This function handles the caching of values, so that we don’t overload our sensor. Various datasheets recommend between 10 and 60 milliseconds of caching time, for me 10 worked fine and even 8 and 9, but with some misses sometimes.
To check if a reading was cached for long enough, we just subtract the current time with the previous reading time and check if it was bigger than the caching time, if it was, save a reading and also the current time:
float HCSR04::distmm()
{
uint64_t time = millis(); // get current time
// check if cache is old enough
if ((time - m_lastreadtime) > m_cachetime)
{
m_lastread = getReading(); // cache new reading
m_lastreadtime = time; // save curr time
}
return m_lastread; // return cached value
}
The function ‘distcm’ just calls ‘distmm’ and divides it’s value by ten and ‘setCacheTime’ set’s a new time in milliseconds:
float HCSR04::distcm() { return distmm() / 10.0; }
void HCSR04::setCacheTime(uint16_t newval_ms) { m_cachetime = newval_ms; }
3 - Wiring
Connecting the sensor is very easy, plug VCC to 5V, GND to ground and then Trig and Echo to any free digital pins.
You can run this project on Wokwi to test it out before building it IRL.
And that’s all. Thanks for reading and stay tuned for more tech insights and tutorials. Until next time, and keep exploring the world of tech!
