Click here to Skip to main content
Click here to Skip to main content
Go to top

A method to ignore anomalies in an ultrasonic distance sensor application

, 2 Sep 2014
Rate this:
Please Sign up or sign in to vote.
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).

#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.

#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.
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.
// 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.

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.

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.

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.

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()

License

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

Share

About the Author

Mike Meinz
Retired
United States United States
I’m retired. When I started my career, programming projects consisted of plugging wires into plug boards to create punch card processing applications to be run on electrical accounting machine like the IBM 402, 407, 085, 088, 514, 519, etc. From there, I moved to writing SPS and Autocoder applications on an IBM 1401 with 4K of memory eventually upgraded to 16K of memory. After many years of migrating my skills to various languages on various hardware platforms, I became an Information Technology Director where I didn’t need to program anymore. So, starting in 1996, I volunteered my time with a local community cable television organization and built some applications to help them run their operations. Originally in Clipper Summer 1987 and later Clipper 5.2, I migrated and enhanced those applications to VB .NET 2003 in 2003. I retired from my full-time job in 2010. Since then, I have continued to support the local community cable tv organization's applications. In 2013, I migrated the VB .NET 2003 Solution to VB .NET 2012 so that it can run on 64-bit computers and interact with Microsoft Office 2010. The upgrade went smoothly. In mid 2013, I developed a VB .NET 2012 application for them to download election results data from the Secretary of State's web site, format the results and send them to a VizRT character generator for on-air display.

Comments and Discussions

 
QuestionNice work - Quick question Pinmemberrather_b_sailing2-Sep-14 7:47 
AnswerRe: Nice work - Quick question PinmemberJMMS Karunarathne2-Sep-14 8:31 
GeneralRe: Nice work - Quick question Pinmemberrather_b_sailing2-Sep-14 8:43 
GeneralRe: Nice work - Quick question PinmemberMike Meinz2-Sep-14 9:15 
QuestionI vote 5. Simple as it should be. PinmemberDavid Serrano Martínez2-Sep-14 3:05 
AnswerRe: I vote 5. Simple as it should be. [modified] PinmemberMike Meinz2-Sep-14 3:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 2 Sep 2014
Article Copyright 2014 by Mike Meinz
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid