1

I need your suggestions and help with my Ping Sensor. I have an Arduino Uno with a Parallax Ping sensor and a motor.

My ping sensor is set to to start the motor when the nearest object is <= 10cm. That works fine. Now, I'd like the motor to start only if the values of the sensor are progressing in a decreasing manner i.e 15, 14, 13, 12, 11, 10 but do nothing if the values are progressing in a increasing manner.

I was thinking if I stored the last 3 pings as variables and using an if/else statement but I don't know how to implement it.

How can I implement this, or if you have any other suggestion, please help me. Thank you

Here is what i have so far:

/* Ping))) Sensor

   This sketch reads a PING))) ultrasonic rangefinder and returns the
   distance to the closest object in range. To do this, it sends a pulse
   to the sensor to initiate a reading, then listens for a pulse
   to return.  The length of the returning pulse is proportional to
   the distance of the object from the sensor.

   The circuit:
    * +V connection of the PING))) attached to +5V
    * GND connection of the PING))) attached to ground
    * SIG connection of the PING))) attached to digital pin 7

   http://www.arduino.cc/en/Tutorial/Ping

   created 3 Nov 2008
   by David A. Mellis
   modified 30 Aug 2011
   by Tom Igoe

   This example code is in the public domain.

 */

// this constant won't change.  It's the pin number
// of the sensor's output:
const int pingPin = 7;
const int motorPin = 9;
//Yomis code
//int pings[3] = {-1,-1,-1};
//int i;
//bool Increasing(int p1, int p2, int p3)
//{
//  if(p1 < p2 && p2 < p3){
//    return true;
//  }
//}
//
//bool Decreasing(int p1, int p2, int p3)
//{
//  if(p1 > p2 && p2 > p3){
//    return true;
//  }
//}
//
//end of yomis code
void setup() {
  // Set up the motor pin to be an output:

  pinMode(motorPin, OUTPUT);

  // initialize serial communication:
  Serial.begin(9600);
}


void loop()
{

  // establish variables for duration of the ping,
  // and the distance result in inches and centimeters:
  long duration, inches, cm;

  // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(1000);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(2000);
  digitalWrite(pingPin, LOW);

  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);

  // convert the time into a distance
  inches = microsecondsToInches(duration);
  cm = microsecondsToCentimeters(duration);

  Serial.print(inches);
  Serial.print("in, ");
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();

  delay(1000);

//        //Yomis code       
//        if (cm <= 10)
//        {
//          Serial.println();
//          Serial.println(pings[0]);
//              Serial.println(pings[1]);
//              Serial.println(pings[2]);
//          Serial.println();
//          if(Increasing(pings[0],pings[1],pings[2]))
//          {
//            int Speed1 = 100;  // between 0 (stopped) and 255 (full speed)
//            int Time1 = 3000;  // milliseconds for speed 1    
//            analogWrite(motorPin, Speed1);  // turns the motor On
//          }    
//        }
//        else
//        {
//          analogWrite(motorPin, LOW);
//        }
//      
//        //End of yomis code

  if (cm <= 10) {

    int Speed1 = 150;  // between 0 (stopped) and 255 (full speed)
    int Time1 = 2000;  // milliseconds for speed 1

    int Speed2 = 250;   // between 0 (stopped) and 255 (full speed)
    int Time2 = 3000;  // milliseconds to turn the motor off

    analogWrite(motorPin, Speed1);  // turns the motor On
    delay(Time1);                   // delay for onTime milliseconds
//    analogWrite(motorPin, Speed2);  // turns the motor Off
//    delay(Time2);                   // delay for offTime milliseconds
  }
  else if (cm > 10) {
    analogWrite(motorPin, LOW);  // turn the motor off
  }
  else {
    Serial.print(inches);
    Serial.print("in, ");
    Serial.print(cm);
    Serial.print("cm");
    Serial.println();

    delay(100);
  }

}

long microsecondsToInches(long microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.m
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}
0

The general idea of your code is fine, but it can be tweaked to make it more efficient.

Firstly, your function increasing(int p1, int p2, int p3) can be simplified to increasing(int *pingArray). This way you pass a reference to the function, rather than three ints. If you decide later that your pings array needs to hold more values you don't have to change the function arguements. However, if you're going to be experimenting with different lengths of the ping array you need to make sure you change the code within the function to reflect this. An easy way to stay on top of it is with #define:

#define MAX_PINGS 3       // Put this at the start of your code somewhere

int pings[MAX_PINGS];     // When you come to declare 'pings'
for (int x=0; x<MAX_PINGS; x++)
    pings[x] = -1;        // Whatever each element is initialised to.


// Your 'increasing' function
//   (... and the same for 'decreasing', if you decide to use it!)
bool increasing(int *pingArray)
{
    // CODE GOES HERE
    // and it doesn't matter how long pingArray / pings is
    // because you have 'MAX_PINGS' defined.
    return true;
}

// You call the 'increasing' function as you'd expect:
if (increasing(pings))
{ /* stuff */ }

Now to the concentrating:

Your initial work is good but from what I've read on ultrasonic sensors, and from experience with any generic sensor, is that the readings aren't going to be perfect. The real world is a noisy place and you aren't going to get nice, accurate distance readings. Finding the average is a simple technique, but you will need to find an optimal solution yourself. Once you have your nice row of average values you can then determine if the distance is increasing or decreasing or neither.

Thus you have two steps:

Average the distance

Create an array (dist, for now) and use it to find the average distance your sensor is from an object.

#define MAX_DIST 5

int dist[MAX_DIST];    // create an array, no need to initialise.
double distAverage;    // average distance, could be 'int' instead.
int distIterator = 0;  // 'dist' will be a CYCLIC BUFFER. woooOOOOooo.

// As you get the distance from your sensor, update 'dist' and
// then calculate the running average.

dist[distIterator] = microsecondsToCentimeters(duration);
distIterator++;
if (distIterator == MAX_DIST) distIterator = 0;
distAverage = 0.0;
for (int x=0; x<MAX_DIST; x++)
    distAverage += dist[x];
distAverage /= MAX_DIST;

I hope you understand the cyclic buffer principle - if you don't you can google it! A key point about the way the code above is constructed is the use of that #define MAX_DIST 5. You can change that single number to anything and your code will change along with it. You will discover that there is an optimum number; I can't tell you what it is (but I recommend it remains odd).

Now that you have a nice reliable distance measurement, you need to find if it is increasing or decreasing...

Average the averages

As you have worked out, you need an array of values to determine if the distance is increasing. Bring in, again, the cyclic buffer.

#define MAX_DIST 5     // these could also be 'const int's
#define MAX_AVRG 3     // but my preference is this way

int dist[MAX_DIST];    // create an array, no need to initialise.
int distIterator = 0;  // 'dist' will be a CYCLIC BUFFER. woooOOOOooo.
int distAvrg[MAX_DIST]; // create an array of averages.
int avrgIterator = 0;  // wwwwoooOOOOooo ? Not this time.

// There's no single 'distAverage' anymore; the result dumps straight
//into 'distAvrg[avrgIterator]'...

dist[distIterator] = microsecondsToCentimeters(duration);
distIterator++;
if (distIterator == MAX_DIST) distIterator = 0;
distAvrg[avrgIterator] = 0.0;
for (int x=0; x<MAX_DIST; x++)
    distAvrg[avrgIterator] += dist[x];
distAvrg[avrgIterator] /= MAX_DIST;
avrgIterator++;
if (avrgIterator == MAX_AVRG) avrgIterator = 0;

Now you have an array of your averages. Winner.

From here you can create your increasing/decreasing function. I'll leave most of this up to you, but consider the following:

  • The contents of distAvrg may not be perfectly smooth;
  • The longer any of the arrays, the more reliable the average;
  • The longer the array, the slower the response of the 'machine';
  • Using int rather than double for distAvrg may help 'numb' some of the fluctuations in averages;
  • Because distAvrg is a cyclic buffer, when analysing it make sure you start at the correct element! HINT: If you add your increase/decrease function after all the code above rather than amongst it, avrgIterator will always point to the oldest average value;
  • If the distAverage array gets beyond three of four elements, you should definitely pass it by reference to the relevant functions; this is what I did in the first codeblock I wrote. It's beyond this answer to explain why you should do it other than "it's more efficient". This is a general rule for any large chunk of data, but you'll learn more as you go along.

The simplest algorithm for determining whether the distAvrgs are increasing or decreasing is to run through from oldest to newest and count how many increases there are and how many decreases there are:

int direction = 0;
for (x=oldest ... x=newest)
{
    if (x > x+1) direction++;
    if (x < x+1) direction--;
}

The above is pseudo-code - you'll have translate it yourself. You end up with a value of direction that is a positive number (increase), a negative number (decrease), or zero. The actual value of direction will help you determine the speed of the increase/decrease, if it's relevant.


A possible solution:

** EDIT ** As pointed out by @EdgarBonet, double and float are 'expensive', in that they take up a lot of computing cycles to be dealt with. The Arduino website recommends avoiding them if you can. The alternative is to use scaled ints. For example, multiplying the return value from microsecondsToCentimetres() by 10 will give you the equivalent of one decimal place's worth of extra precision. No other code needs to change unless you're outputing the value on an LCD or similar and want it in centimeters. Dividing by 10 is conveniently the distance in millimeters, which is why I picked it, but there's no reason you can't scale by a different factor. Just be aware that there is a maximum (and mirrored minimum) value that int can hold, but I doubt you're going to hit it unless you scale by an unnecessarily high factor.

Let's say MAX_AVRG is 6. This gives five changes between distances to look at. The simplest function for determining an increase is:

bool increasing(int * DIST)
{
    int dir = 0;

    for (int x=1; x<MAX_AVRG; x++)
    {
        if (DIST[x] > DIST[x-1])   // change this to < for DECREASE
            dir++;
        else
            dir--;
    }

    if (dir > 0)
        return TRUE;
    else
        return FALSE;
}

The 'decrease' version is identical, save for the single commented line. The for loop starts at the second sample and checks against the previous sample, which is why x starts at 1, not 0 as 'usual'. The return value will be true if distAv has increased more times than it has decreased, and vice versa for decreasing().

Let's say distAve = { 1, 2, 1, 3, 4, 5 }. In the function:

distAve   1   2   1   3   4   5
            ^   ^   ^   ^   ^
start       |   |   |   |   |    dir = 0
increase   -'   |   |   |   |    dir = 1
decrease       -'   |   |   |    dir = 0
increase           -'   |   |    dir = 1
increase               -'   |    dir = 2
increase                   -'    dir = 3

dir > 0 therefore INCREASE.

But what if distAve = { 5, 4, 3, 2, 1, 6 } ? If increasing() will now return false because there are four decreases vs one increase, except the final distance indicates that the sensed object has ended up at an increased distance than when it started.

int distance(int * DIST)
{
    int overallDistance = 0;
    for (int x=1; x<MAX_AVRG; x++)
        overallDistance += (DIST[x] - DIST[x-1]);
    return overallDistance;
}

Now you have an explicit value for the overall distance change.

  • 1) There is no point in replacing const by #define. 2) float is expensive in the Arduino, better try to use integers, even long if needed. – Edgar Bonet Jun 19 '15 at 19:17
  • I agree with @EdgarBonet that it's silly or pointless to replace the const ints by #defines (as noted in his link, “const int constants do not necessarily occupy any storage” and are compile-time constants). However, I recommend replacing const ints by enum'd constants, eg: enum { pingPin=7, motorPin=9}; – James Waldby - jwpat7 Jun 20 '15 at 21:39
  • @EdgarBonet: (1) I believe #define immediately describes to the reader the fact that you're simply substituting an otherwise meaningless number for a self-descriptive name - it's completely analogous to giving a variable a name rather than having to remember its address. I see your view that there's no point replacing const with #define, but tell me why one shouldn't use #define instead of const in their code. (2) Double-precision may be costly, but it's up to the coder to decide whether it's worth it or not. I agree scaling to utilise unsigned or long is lighter. – CharlieHanson Jun 22 '15 at 16:54
  • @jwpat7: I see no benefit whatsoever of an anonymous enum in place of either const or #define for an arbitrary list of integers such as pin associations or array lengths. Yes, you can save a tiny bit of typing if you have some consecutive pins but that's blisteringly trivial. – CharlieHanson Jun 22 '15 at 17:16
  • chaaarlie2, enum is a specific way of defining integer constants. #define, on the other hand, is a non-type-checked way of defining textual substitutions. const int is a slightly-oxymoronic way of defining a "constant variable". – James Waldby - jwpat7 Jun 22 '15 at 18:48
0

Okay but why is everyone arguing about const and #define. That's clearly not the real problem that the person who posted the question is asking, so i'm kind of lost. @chaaarlie2. What exactly does averaging the averages do, since the distances have already been averaged to find the correct distance.

  • Say you have six average (mean) distances. If each is greater than the previous then the distance is increasing, and vice-versa. However, if the six mean distances do not immediately indicate a specific direction, you take an average of the five differences between each distance. In this case I am using "average" in the broader sense: you could simply take a count of how many 'increases' vs 'decreases' and use the larger of the two to determine direction, or you could sum the distance-difference between each sample, eg. {5, 4, 3, 2, 1, 20} - five decreases, but an overall increase of 15. – CharlieHanson Jun 23 '15 at 10:16
0

I would use subtraction to find a decreasing series you can subtract the new ping output from the previous one in every loop this way you can scale to a longer series without allocating more memory for the array

assuming you have defined somewhere:

/*use whatever number you want here*/
#define SERIES_LENGTH 3

declare globally:

int prevPingSample;
int decCount = 0;

than in your loop:

int pingSample = analogRead(sensorPin) //or however else you sample the sensor
if (prevPingSample > pingSample)
{ /*if the new reading is smaller than the previous -  the series is decreasing*/
 decCount = decCount + 1;
} 
if (decCount >= SERIES_LENGTH)
{
    //move motor code
}
else 
{
    decCount = 0;
}
//and whats left is to update the previous ping sample for the next loop
prevPingSample = pingSample;
0

A simpler solution. Use a variable to detect the trend and another for previous distance. Initialized to false and a very large number respectively.

Once a new distance is available the two variables are updated. If the trend is decreasing and the distance is less than your desired distance activate the motor.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.