Sonar range finder: HC-SR04

 

The HC-SR04 is a simple range finder with an advertised capability of measuring from 2 cm to 400 cm (~13 feet.)  It costs less than $2.  A microprocessor like the Arduino, Raspberry Pi (any model), or ESP8266 can be used to control it.  The device sends out a pulse of ultrasonic sound and measures the time for the echo to be received.  In the picture below, the sound is transmitted from one of the two turrets and is echo is received by the other.  Since the speed of sound is known, the distance can be calculated.  The device specifications are here.  The RCWL-1601 and US-100 are newer devices that are similar.

                 

One problem is that the HC-SR04 runs on 5 Volts DC and its output signal voltage is also 5v.  While this is suitable for most Arduino models, newer microprocessors like the Raspberry Pi and ESP models use 3.3v.  Applying 5v to a 3.3v rated input will damage them.  A voltage divider is needed to step down the 5v to 3.3v.  An example of a voltage divider on a Raspberry Pi Zero is shown below.  The Pi has a convenient 5v output to power the range finder.  

Exercises:

1.    If you take multiple readings, are they the same? 

o   Take the average to get better estimates

2.    Eliminate bogus readings

o   Use Standard deviation to discard noisy readings

3.    Accuracy: measured vs. actual distance. 

o   Plot measured reading vs. actual distance

o   Plot error vs. distance

o   Determine limits of accuracy: Max distance, minimum

4.    Can the accuracy be improved ?

o   toilet paper roll core

5.    Properties of reflecting materials

o   Find stealth materials!!

o   Angle the reflection to avoid detection

6.    Other factors that affect accuracy

o   Size of target

o   Temperature

o   Cross wind

o   Moving targets

7.    SONAR theory

o   What is Ultrasonic sound?

o   Sonar in nature: Bats, Dolphins, Whales

o   Do Submarines say Marco/Polo?

o   How is RADAR different?

8.    Understand the code

o   Derive the ‘Speed of sound’ constant

o   Use different pins on the Pi

o   Add a web server to make the range finder mobile

9.    Understand Voltage dividers

10.  Range finder applications

o   Car parking in garage

o   Avoiding obstacles in a robot

o   Measuring the level of water in doggie bowl

o   Use multiple range finders to determine target location

 

'''

Driver for distance sensor HC-SR04.  Tested on a Raspberry Pi

 

Speed of sound = 1234.8 Km/hour = 1234.8/3600 Km/sec = 1234.8/3600*10^5 cm/sec

Divide by 2 for round trip

'''

 

import RPi.GPIO as GPIO

import time

 

TRIG = 23           # Pin 16, GPIO port 23 for trigger

ECHO = 24           # Pin 18, GPIO port 24 for echo response

 

# Tech doc recommends allowing time for echo to complete after each trigger

MinIdleTime = 0.06  # 60 ms, min time between measurement cycles

 

class Ranger:

    def __init__(self):

        GPIO.setmode(GPIO.BCM)      # GPIO pin numbering method  "Broadcom SOC channel"

        GPIO.setup(TRIG, GPIO.OUT)

        GPIO.output(TRIG, False)    # reset

        GPIO.setup(ECHO, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

        self.lastTrigger = time.time() - MinIdleTime    # effective time of last trigger

 

    # get one distance reading

    def get(self):

        if GPIO.input(ECHO) != 0:   # 'Echo' pin is already high

            self.__init__()         # attempt to fix it

            return -1               # sensor fault, check for loose connections

 

        getStart = time.time()      # start time for this reading

        idleTime = getStart - self.lastTrigger  # time between trigger applications

        if idleTime < MinIdleTime:  # too fast?

            time.sleep(idleTime)

            getStart = time.time()  # update start time, getStart += idleTime

 

        self.lastTrigger = getStart # time of this trigger (now)

        GPIO.output(TRIG, True)     # trigger, start new request

        time.sleep(0.00002)         # trigger duration 20uS, recommended value is 10uS

        GPIO.output(TRIG, False)    # turn off trigger

 

        while True:                 # wait for pulse start

            pulseStart = time.time()

            if pulseStart-getStart > 0.02# avoid infinite loop

                return -2           # error, took too long, sensor fault

            if GPIO.input(ECHO) != 0:

                break

 

        while True:                 # wait for pulse end

            pulseEnd = time.time()

            pulseDuration = pulseEnd - pulseStart

            distance = pulseDuration * 17150    # based on 1234.8 kmph

            if distance > 500:      # > 500 cm?

                return -3           # error, too far to be accurate

            if GPIO.input(ECHO) == 0:

                return distance     # in cm

 

    # get many distance readings

    def getSet(self, size=100):

        return [self.get() for _ in range(size)]

 

    # remove outliers (2 sigma), return average reading

    def getAverage(self, size=100):

        import numpy as np

        distances = self.getSet(size)

        while True:

            nValues = len(distances)

            mean = np.mean(distances)

            maxDeviation = np.std(distances) * 2    # max permitted error is 2 standard deviations

            distances = [d for d in distances if d >= mean-maxDeviation  and  d <= mean+maxDeviation]

            if len(distances) == nValues:

                return mean             # no outliers were found

 

if __name__ == '__main__':

    print("Starting...")

    ranger = Ranger()

    for _ in range(1200):       # 20 mins

        distance = ranger.get()

        if distance < 0:

            print("Sensor error: %s" % distance)

        else:

            print("Distance: %.2f cm (%.2f in)" % (distance, distance/2.54))

        time.sleep(1)