Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

A method to ignore anomalies in an ultrasonic distance sensor application

4.75/5 (7 votes)
27 Oct 2014CPOL5 min read 37.7K   338  
Describes an algorithm to ignore spurious ultrasonic sensor readings.

Introduction

This tip article is the result of work done by @JMMS-Karunarathne and @MikeMeinz in August, 2014.

The example source code is from an application to report water levels in storage tanks for a water company. A Shenzhen Dianyingpu Technology Company (DYPsensor) DYP-ME007Y-TX Ultrasonic sensor, an Arduino UNO board and an AZ Displays, Inc. ACM1602K LCD Display were used in this project.

The DYP-ME007Y-TX Ultrasonic sensor provides a distance measurement. Our application reports water tank levels. The same sensor can be used in other applications that require sensing distance like robots, vehicle proximity, etc.

Approximately every 100ms, the DYP-ME007Y-TX Ultrasonic sensor sends four 8-bit bytes to the Arduino UNO. The first character is 0xFF which identifies it as the first character. The second and third characters are a 16-bit unsigned integer of the distance from the sensor to an object in millimeters. The fourth character is a simple check character equal to the least significant 8-bits of the sum of the first three characters.

We found during our testing that the sensor would occasionally return anomalous readings. In reviewing the readings, we determined that a mathematical average or median of several readings would almost always provide incorrect values. We chose to employ a "voting" scheme whereby the reading that occurred most after "n" samples would be the one that we used. Readings occur approximately every 100ms. We chose to read thirty samples, round the values from millimeters to centimeters and determine which centimeter value occurred most often. This yielded the most accurate reading approximately every three seconds.

Using the code

Note: The example program includes code to write results to a LCD Display unit. It is included in the example program but not explained in this tip article.

In this tip article, we demonstrate how the bytes are read from the DYP-ME007Y-TX Ultrasonic sensor using serial communications and how anomalous readings are ignored.

In the example program, the following #define specifies how many samples are to be taken before returning a reading, declares the maximum index value, declares the array that will hold the sample values collected, and the array that will hold the number of times that a specific sample value occurred (i.e. votes).

C
#define MaxSamples 30 
int MaxSamplesIndex=MaxSamples - 1;
float testValues[MaxSamples];
int testVotes[MaxSamples];

The DYP-ME007Y-TX Ultrasonic sensor TX pin is attached to Pin 9 on the Arduino UNO board. Pin 9 is the pin on which the four-byte sensor value is received. The RX pin on the sensor is attached to Pin 8 on the Arduino UNO board. The Arduino SoftwareSerial library is used to read the values one 8-bit byte at a time.

C
#include <SoftwareSerial.h> 

#define TX_PIN  8
#define RX_PIN  9
SoftwareSerial mySerial =  SoftwareSerial(RX_PIN,TX_PIN);

GetDistanceInMeters()

GetDistanceInMeters() is called continously by GetSensorValue() until a four byte value has been successfully read from the serial port. The GetDistanceInMeters() function returns the sensor value (rounded to centimeters) as a floating point value in meters or it returns -1. The sensor returns a value of zero when an "Out of Range" condition exists.

A newly read byte is placed into read_buffer[3] after the prior contents of the read_buffer[] array has been shifted one byte to the left. GetDistanceInMeters() exits and is immediately called again if any of these conditions exists:

  • A byte is not available to be read.
  • read_buffer[0] is not equal to 0xFF.
  • read_buffer[3] is not a valid cyclical redundancy check character.
C
float GetDistanceInMeters()
{
byte readByte; 
byte checkCalc; 
word distance; 
// Is there a character available to read
if (mySerial.available() < 1) 
    { 
    return -1.0;   
    } 
// Read one character (byte)
readByte = mySerial.read(); 
// Move any bytes in the buffer to the left
for (byte idx = 0; idx <= 2; idx++) 
    { 
    read_buffer[idx] = read_buffer[idx + 1]; 
    } 
// Add the new character to the end of the buffer
read_buffer[3] = readByte;    
// If the first character in the buffer is now 0xff, then we have a reading to use
if (read_buffer[0] != 0xff) 
    { 
    return -1.0; 
    }; 
// Calculate the check character
checkCalc = read_buffer[0] + read_buffer[1] + read_buffer[2]; 
// Ensure that the check character read matches the computed check character
if (read_buffer[3] != checkCalc) 
    { 
    return -1.0; 
    }; 
// Compute the distance in millimeters
distance = (read_buffer[1] * 256) + read_buffer[2];  // Distance in mm
// Round to cm
distance += 5; // add 5 to the cm position
distance = distance / 10; // Divide by 10 to drop the cm position.
// Clear the read buffer 
  for (byte idx = 0; idx <= 3; idx++) { 
    read_buffer[idx] = 0; 
  }
// Divide by 100 to convert (int)cm to (float)m 
return distance / 100.0; // Convert cm to meters
}

GetSensorValue()

The GetSensorValue() function is called when the main loop of the program wants a value for display on the LCD display unit. It first calls InitializeTestValues() to initialize the arrays used for storing the thirty values and then executes a for loop to retrieve thirty samples from GetDistanceInMeters(). Within the for loop is a do loop that calls GetDistanceInMeters() repeatedly until a value is received. When the value is received the AddReading() procedure is called to record this value and an associated "vote". When all thirty samples have been retrieved, the return statement calls ReturnHighestVote to find and return the reading that occurred most often.

C
float GetSensorValue()
{
float current_reading;
InitializeTestValues();
// Get the next MaxSample values from the sensor
for (byte idx = 0; idx <= MaxSamplesIndex; idx++) 
    { 
    do
       {
       current_reading = GetDistanceInMeters(); // Get value in meters from the sensor
       } while (current_reading == -1.0);
    AddReading(current_reading);
    delay(50);
    }
return ReturnHighestVote();
}

AddReading()

The AddReading() procedure is called by GetSensorValue() to record a vote for the new value. AddReading() steps through the testValues[] array and if it finds an empty array element (signified by -1), it puts the new value into that element and records the first vote for that value in the testVotes[] array. If it finds an array element that is equal to the new value, it adds one to the corresponding element in the testVotes[] array. In both cases, it executes a break statement to exit the for loop when it no longer needs to loop.

C
void AddReading(float x)
{
// Either put the value in the first empty cell or add a vote to an existing value.
for (int idx = 0; idx <= MaxSamplesIndex; idx++)
    {
    // If an empty cell is found, then it is the first time for this value.
    // Therefore, put it into this cell and set the vote to 1
    if (testValues[idx] == -1)
        {
        testValues[idx] = x;
        testVotes[idx] = 1;
        // Exit the For loop
        break;
        }
    // If the cell was not empty, then check to see if the testValue is equal to the new reading
    if (testValues[idx] == x)
        {
        // Add a vote because we got the same reading
        testVotes[idx] = testVotes[idx] + 1;
        // Exit the For loop
        break;
        }
    }
}

ReturnHighestVote()

The ReturnHighestVote() function is called by the return statement in GetSensorValue(). It steps through the testVotes[] array looking for the highest number of votes. When the first unused array element is found, a break statement terminates the loop and the highest value found is returned.

C
float ReturnHighestVote()
{
float valueMax = 0;
int votesMax=0;
for (int idx = 0; idx <= MaxSamplesIndex; idx++)
    {
    if (testValues[idx] == -1)
        {
        break;
        }
    if (testVotes[idx]>votesMax)
        {
        votesMax = testVotes[idx];
        valueMax = testValues[idx];
        }
    }
return valueMax;
}

InitializeTestValues

InitializeTestValues is called by GetSensorValue() to initialize the values in the testValues[] and testVotes[] arrays. A value of -1 in the testValues[] array indicates an element that is unused.

C
void InitializeTestValues()
{
// Initialize test values arrays
for (int idx=0;idx<=MaxSamplesIndex;idx++)
  {
  testValues[idx]=-1;
  testVotes[idx]=0;
  }
}

Bibliography

Arduino.ru Forum: Waterproof ultrasonic sensor DYP-ME007Y - A 22 July 2014 post on the Arduino.ru forum. This post provided information on how to retrieve the sensor values via serial communications. The post incorrectly calculates the distance using 0xFF (255) instead of 256 as a mulitplier. Also, the post incorrectly uses the term CRC for the check character.

History

  • Version 1: 1 September 2014.
  • Version 2: 2 September 2014. Added delay(50); into GetSensorValue()
  • Version 3: 25 October 2014. Added buffer clear in GetDistanceInMeters()

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)