Ultrasonic Ranging Sensor - HC-SR04

This sensor can be used to ultrasonically range distances between 2cm - 400cm with an accuracy of up to 3mm.


For circuit, pin and interface specification see this datasheet

Usage
The sensor is used by sending a trigger signal to the sensor and then listening on the echo pin for the response. The sensor will drive the echo pin high for a duration equivalent to the measured range.

Extra information
Due to some overhead in the applications run in the Appivo platform, it is not possible to achieve 3mm accuracy. The accuracy will be closer to a couple of cm with occasional erroneous measurements. This means that the best practice is to perform a measuring cycle where several measurements are performed and then filtered for outlying values and then averaged.

Sample code
In order to read the value from the sensor with consistent times, it is best to utilise a technique called busy-waiting. This means that you continuously poll the device to see if it has changed state.

pseudo-code

  • pulse trigger pin for at least 40 µs
  • wait for the echo pin to go high
  • note the current system time
  • wait for echo to go low
  • not the current system time
  • use the time difference to calculate the distance

Javascript
//get the gpio module and use it to get the trigger pin
//(in this case GPIO 4) and the echo pin (in this case GPIO 6)
var gpio = context.getModule("gpio");
var trig = gpio.getPin("GPIO 4");
var echo = gpio.getPin("GPIO 6");

//pulse the trigger pin for 1ms
trig.pulse(1);

//busy-wait for the echo pin to go high and use a loop-counter as a
//safe-guard to avoid an infinite loop if the state change is missed
var cnt = 0;
while (echo.isLow() && cnt < 10000) {
    ++cnt;
}

//this means the loop-counter was what got us out of the loop
//so the read failed, return a NaN to indicate this
if (cnt >= 10000) {
    return NaN;
}

//if we got here, we successfully saw echo go high, store system time
var start = context.getNanoTime();

//busy wait for the echo pin to go low, since the pin will stay low after 
//it has gone low, there is no way we can miss this state change
//so no loop-counter i needed
while (echo.isHigh()) ;

//we saw echo go low so store the time again
var end = context.getNanoTime();

//calculate the difference between start and end in µs
var duration_us = (end - start) / 1000;

//according to the data sheet, distance in cm = echo high duration µs / 58
var distance_cm = duration_us / 58;

As mentioned in extra information, it’s good practice to perform several measurements and then filter the results for any outliers in order to get a good reliable result. Following is an example of filtering using standard deviation to remove outliers that are not within 2 σ of the average value (this will include roughly 95% of all the measured values)

Javascript
//this controls within how many sigmas the value needs to be
var MAX_DEVIATION = 2;

//get the points to filter from the function arguments
var points = args.points;

//this function will be used with Array.reduce to calculate the sum
//of an array of numbers
var sumFunction = function (accumulator, value) {
     return accumulator + value;
};

//calculate the sum of the points
var sum = points.reduce(sumFunction, 0);

//calculate the average value
var average = sum / points.length;

//create a new array containing the distance between each point and
//the average of all the points
var distances = points.map(function(value) {
     return Math.pow(value - average, 2); 
});

//calculate the sum of all the distances
var distanceSum = distances.reduce(sumFunction, 0);

//calculate the standard deviation of the points
var sigma = Math.sqrt(distanceSum / (points.length - 1));

//calculate the minimum allowed value and the 
//maximum allowed value
var min = (avg - MAX_DEVIATION * sigma);
var max = (avg + MAX_DEVIATION * sigma);

//filter the points to only be the ones between the
//maximum and minimum allowed values
var validPoints = points.filter(function(value) { 
  return min < value && value < max;
});
return validPoints;