HC-SR04

Cheap ultrasonic range finder

Hello World

Import programNucleo_UltrasonicHelloWorld

A hello world program for the HC-SR04

Library

Import libraryHC_SR04_Ultrasonic_Library

Works with interrupts

Datasheet

http://www.micropik.com/PDF/HCSR04.pdf

Notes

The HC-SR04 is one of the lowest cost Sonar-based distance sensor options available (as low as $3 US). There seem to be several hardware versions and/or clones that work the same as far the functions of the two pins (trigger and echo). It operates off of 5V DC power at around 15 ma (not active is 2 ma). The detection range is 3-400 cm with around a 15 degree beam width. Like most Sonars, it more readily detects large hard objects that reflect sound, so it might not see something soft like a fluffy cat. It uses a short 40 kHz ultrasonic ping that humans can't hear and then listens for the ping signal to return after reflecting off an object. The time delay for the reflected signal to echo back is used to measure the distance using a simple scaling calculation based on the speed of sound in air.

There are two tranducers, one to transmit and one to receive. Two transducers turn out to be cheaper, since a higher voltage is needed to transmit and switching modes using only one transducer takes a lot of analog circuitry.

Small Sonar sensors are often used in robots to detect objects. Sometimes they are used along with an IR-based sensor to improve the likelihood of object detection. Some robots and other devices even add a mechanical bump or limit switch. To detect objects in different directions, some robots rotate sensors on a turret or use several sensors mounted facing out at different angles .

sonar
HCSR04 from Sparkfun

sonarback
Electronics on the back of the Sparkfun HC-SR04 include an unmarked microprocessor? and a quad op amp. Other versions from other sources can have totally different hardware. The only HC-SR04 schematic available seems to have some different transmit parts and a different microcontroller.

Operation

td

As seen in the timing diagram above, only two signal pins are used. Trigger starts a measurement cycle and sends out a short ultrasonic pulse (eight cycles at 40Khz) and then listens for a reflected signal (echo). Several cycles at 40Khz are needed for the analog receiver circuit to detect the reflected signal. The width of the echo pulse output pin indicates distance. A hardware timer would typically be used to measure the echo pulse width. A simple divide operation can then scale the value to cm or inches, if needed.

The device should not be triggered again until waiting for the longest possible echo return time delay (maximum detection distance times speed of sound in air). This prevents any echos from the previous ultrasonic pulse interfering with the next pulse measurement or the new outgoing pulse being heard as the echo from the previous pulse. Something a bit over 10 measurements per second is typical on most small sonar sensors. The HC- SR04 data sheet suggests a min cycle time of 60 ms.

Hello World Demo

Wiring

mbed LPC1768HC-SR04
Vu (5V)Vcc
GndGnd
p6trig
p7echo

With my sensor and breadboard setup, an optional decoupling capacitor (10-100uf) across the power pins at the sensor helped reduce noise a tiny bit on measurements. This may also be a function of the noise on the host PC's 5V USB power supply.

Using the Timer

The simple approach is to use the mbed timer APIs to measure the width of the echo pulse using software polling to detect edges on the echo pulse. When echo goes high, start the timer and when echo goes low, stop the timer and read it. Software delay times to sample echo and start and stop the timer will be estimated using the timer and included in the timing calculations to obtain the distance measurement. On the LPC1768, the software delays of 3us did not have a significant effect (<1mm) on distance measurements.

#include "mbed.h"

DigitalOut trigger(p6);
DigitalOut myled(LED1); //monitor trigger
DigitalOut myled2(LED2); //monitor echo
DigitalIn  echo(p7);
int distance = 0;
int correction = 0;
Timer sonar;

int main()
{
    sonar.reset();
// measure actual software polling timer delays
// delay used later in time correction
// start timer
    sonar.start();
// min software polling delay to read echo pin
    while (echo==2) {};
    myled2 = 0;
// stop timer
    sonar.stop();
// read timer
    correction = sonar.read_us();
    printf("Approximate software overhead timer delay is %d uS\n\r",correction);

//Loop to read Sonar distance values, scale, and print
    while(1) {
// trigger sonar to send a ping
        trigger = 1;
        myled = 1;
        myled2 = 0;
        sonar.reset();
        wait_us(10.0);
        trigger = 0;
        myled = 0;
//wait for echo high
        while (echo==0) {};
        myled2=echo;
//echo high, so start timer
        sonar.start();
//wait for echo low
        while (echo==1) {};
//stop timer and read value
        sonar.stop();
//subtract software overhead timer delay and scale to cm
        distance = (sonar.read_us()-correction)/58.0;
        myled2 = 0;
        printf(" %d cm \n\r",distance);
//wait so that any echo(s) return before sending another ping
        wait(0.2);
    }
}


This code works, but it eats up a lot of processor time taking measurements. Interrupts using the echo signal edges frees up processor time and would also provide a bit more accurate timing measurements. Last, everything should be setup in a class with a simple include file. Keeping the distance calculations in integer (and not float) mode would also speed up the code a bit.

Using the Timer and Interrrupts

The mbed API InterruptIn is often used in timer code. It can force a function to be called when an external I/O pin changes state from low-to-high (rise) and/or high-to-low (fall) using interrupts. This is a handy, efficient, and accurate way to start, stop and read timers using external signals.

Using interrupts to detect changes in the Sonar's echo pin along with the timer will free up processor time for other operations. There is an existing HC-SR04 library on the mbed component page for this device that uses timer interrupts to control the sensor. It sets up timers to measure the echo pulse with interrupts and uses another timer (with the mbed timeout API) to trigger continuous measurements automatically. A positive edge on echo interrupts and starts the timer, and a negative edge on echo interrupts and reads the timer. The main program is free to do other tasks and can periodically check the last sonar distance value and activate another function, if needed. Here is a demo version using the same pins (for the mbed LPC1768). Hopefully enough hardware timers are available on the mbed platform to use this approach in the application. Keep in mind the RTOS (if used) also needs one timer for time slice interrupts. There are other sonar sensors that do not require a timer, but they are a bit more expensive.

Import programLPC1768_HCSR04_HelloWorld

Sonar demo with timer interrupts

#include "mbed.h"
#include "ultrasonic.h"

 void dist(int distance)
{
    //put code here to execute when the distance has changed
    printf("Distance %d mm\r\n", distance);
}

ultrasonic mu(p6, p7, .1, 1, &dist);    //Set the trigger pin to p6 and the echo pin to p7
                                        //have updates every .1 seconds and a timeout after 1
                                        //second, and call dist when the distance changes

int main()
{
    mu.startUpdates();//start measuring the distance
    while(1)
    {
        //Do something else here
        mu.checkDistance();     //call checkDistance() as much as possible, as this is where
                                //the class checks if dist needs to be called.
    }
}


This code puts the distance in mm (not cm). It divides by 6 (and not 5.8) to use faster integer operations. For most applications this would not matter, but a bit more accuracy on the absolute measurements in mm could be obtained by scaling the integer operation (and still avoiding slower float operations). So the line in the library file

_distance = (end - start)/6;


could be replaced by something like

_distance = (end-start);\\
_distance = (_distance)<<2+_distance)/29; // (distance*5)/29 is distance/5.8