NewPing Library: HC-SR04, SRF05, SRF06, DYP-ME007, Parallax PING))) - v1.7

Hi.

I tried to modify the NewPing15Sensors but i still get the same problem of

0=123cm 1=2cm 2=0cm
0=117cm 1=3cm 2=0cm
0=126cm 1=3cm 2=0cm
0=122cm 1=3cm 2=0cm
0=122cm 1=3cm 2=0cm
0=125cm 1=3cm 2=0cm
0=117cm 1=4cm 2=0cm

This is my code, some idea?

#include <NewPing.h>

#define SONAR_NUM     3 // Number or sensors.
#define MAX_DISTANCE 200 // Maximum distance (in cm) to ping.
#define PING_INTERVAL 100 // Milliseconds between sensor pings (29ms is about the min to avoid cross-sensor echo).

unsigned long pingTimer[SONAR_NUM]; // Holds the times when the next ping should happen for each sensor.
unsigned int cm[SONAR_NUM];         // Where the ping distances are stored.
uint8_t currentSensor = 0;          // Keeps track of which sensor is active.

NewPing sonar[SONAR_NUM] = {     // Sensor object array.
  NewPing(5, 6, MAX_DISTANCE), // Each sensor's trigger pin, echo pin, and max distance to ping.
  NewPing(8, 9, MAX_DISTANCE),
  NewPing(11, 12, MAX_DISTANCE)
};

void setup() {
  Serial.begin(115200);
  pingTimer[0] = millis() + 75;           // First ping starts at 75ms, gives time for the Arduino to chill before starting.
  for (uint8_t i = 1; i < SONAR_NUM; i++) // Set the starting time for each sensor.
    pingTimer[i] = pingTimer[i - 1] + PING_INTERVAL;
}

void loop() {
  for (uint8_t i = 0; i < SONAR_NUM; i++) { // Loop through all the sensors.
    if (millis() >= pingTimer[i]) {         // Is it this sensor's time to ping?
      pingTimer[i] += PING_INTERVAL * SONAR_NUM;  // Set next time this sensor will be pinged.
      if (i == 0 && currentSensor == SONAR_NUM - 1) oneSensorCycle(); // Sensor ping cycle complete, do something with the results.
      sonar[currentSensor].timer_stop();          // Make sure previous timer is canceled before starting a new ping (insurance).
      currentSensor = i;                          // Sensor being accessed.
      cm[currentSensor] = 0;                      // Make distance zero in case there's no ping echo for this sensor.
      sonar[currentSensor].ping_timer(echoCheck); // Do the ping (processing continues, interrupt will call echoCheck to look for echo).
    }
  }
  // The rest of your code would go here.
}

void echoCheck() { // If ping received, set the sensor distance to array.
  if (sonar[currentSensor].check_timer())
    cm[currentSensor] = sonar[currentSensor].convert_cm(sonar[currentSensor].ping_result);
}

void oneSensorCycle() { // Sensor ping cycle complete, do something with the results.
  for (uint8_t i = 0; i < SONAR_NUM; i++) {
    Serial.print(i);
    Serial.print("=");
    Serial.print(cm[i]);
    Serial.print("cm ");
  }
  Serial.println();
}

Thanks.

vcberta:
Hi.

I tried to modify the NewPing15Sensors but i still get the same problem of

0=123cm 1=2cm 2=0cm
0=117cm 1=3cm 2=0cm
0=126cm 1=3cm 2=0cm
0=122cm 1=3cm 2=0cm
0=122cm 1=3cm 2=0cm
0=125cm 1=3cm 2=0cm
0=117cm 1=4cm 2=0cm

I assume the problem is that you're getting 0cm from the third sensor (sensor 2)? It seems that sensor 0 and sensor 1 are giving realistic results. Am I assuming correctly? You don't specifically state the problem, only provide the results so one must assume what you're reporting as a problem.

If so, sensor 2 is reporting 0cm probably because you're using pin 11. As the multi-sensor sketch uses the timer 2 interrupt, there could be issues using pins 3 and 11. This is mentioned in the v1.3 release notes here:

New in v1.3 - Released 6/8/2012:
Big feature addition, event-driven ping! Uses Timer2 interrupt, so be mindful of PWM or timing conflicts messing with Timer2 may cause (namely PWM on pins 3 & 11 on Arduino, PWM on pins 9 and 10 on Mega, and Tone library).

Also, as of v1.4 there's no need to use two Arduino pins. Just specify the same trigger and echo pins in your sketch and wire from the Arduino pin to the trigger pin on the sensor, then from the trigger to echo pin of the sensor. So, for 3 sensors you would only need to use 3 Arduino pins instead of 6 as you have it wired now. Your pin assignment code would look something like this:

NewPing sonar[SONAR_NUM] = { 
  NewPing(5, 5, MAX_DISTANCE),
  NewPing(8, 8, MAX_DISTANCE),
  NewPing(12, 12, MAX_DISTANCE)
};

Tim

Thanks, Tim, for the reply.
I am trying to get around short false readings from the Parallax ping sensor. I have the same thing with v 1.4 of NewPing as I do with the std. ping library. Around the middle of the servo sweep I get short readings, typically 15 cm, for a couple of servo moves. Curiously, the ping will read below 15 cm during this dead band. My space has no influence on the position of servo. I have moved it all around to see if I am getting bad readings from my environment. NewPing works fine on both sides of the dead band. I have removed the servo from the project electrically (disconnect power and signal) and I still get low readings around the center of the sweep when I move it by hand. I have tried it on both servos and ping sensors. I have no idea how the servo position is effecting the ping sensor even when it is not moving and disconnected.
My project is to sweep 360 deg around my sculpture pedestal with 2 servos and 2 pings. When someone comes close the wav shield plays a short file depending on distance and position. Any distance reading will trigger another wav file and if you walk the "right" way, the whole song will play. I got everything working and started to test when I discovered the short bad distances. I reduced the sketch to just the std. libraries and the short readings persist. I can manipulate them with delays and code but never get rid of them all. I hope that this description will aid you in suggesting how best to use NewPing. And ANY thoughts on the dead band of the servos would be greatly appreciated! Many Thanks!!

Well, I found the source of the false short ping readings. I had placed the 2 sensors 5 & 10 cm away from the Arduino Uno (with wav shield) on my pedestal. Something on the board emits a directional noise that the sensors heard and misreported the distance as short. When I moved the sensors 20 cm away and put a piece of wood between them and the computer, the noise no longer spoofed the Ping sensors. Thanks, everyone, for helping with suggestions. Tim - I am going to implement your new ping library next. I will post my results after testing.

Hello,
First of all thank your published work.
I got my HC SR04 today and searched on the internet how to get all working. I came across your library and another source from trollmaker.com (I have seen someone posted it before) It worked flawlessly. After that I wanted to try your library. I wired all the same using pins 12 and 11 but all I got in serial monitor was junk all in one row.
I used your sketch in the example menu.
I have a UNO r3 clone and using the newest arduino IDE
I'm a beginner so please be patient :grin:

cappyjack:
Well, I found the source of the false short ping readings. I had placed the 2 sensors 5 & 10 cm away from the Arduino Uno (with wav shield) on my pedestal. Something on the board emits a directional noise that the sensors heard and misreported the distance as short. When I moved the sensors 20 cm away and put a piece of wood between them and the computer, the noise no longer spoofed the Ping sensors. Thanks, everyone, for helping with suggestions. Tim - I am going to implement your new ping library next. I will post my results after testing.

Glad to hear you got it working with the PING))) sensor. I didn't test the library with this sensor and trusted a user who said it worked.

Tim

tehcereal:
Hello,
First of all thank your published work.
I got my HC SR04 today and searched on the internet how to get all working. I came across your library and another source from trollmaker.com (I have seen someone posted it before) It worked flawlessly. After that I wanted to try your library. I wired all the same using pins 12 and 11 but all I got in serial monitor was junk all in one row.
I used your sketch in the example menu.
I have a UNO r3 clone and using the newest arduino IDE
I'm a beginner so please be patient :grin:

Did you specify the correct BAUD rate as set in the sketch? The NewPing example sketches uses 115200 while I believe the Trollmaker sketch uses 9600 BAUD. If you don't select the correct speed as set in the sketch, you'll get garbage. You could also set the BAUD rate in the NewPing sketch to 9600 as well, just as long as you set the same speed both in your sketch and in the serial monitor you will see the results.

Also, with NewPing you can use just one Arduino pin instead of two. So, you can wire both the trigger and echo to Arduino pin 10, for example, which would look something like this example sketch:

http://code.google.com/p/arduino-new-ping/wiki/NewPing_Single_Pin_Sketch

Also, I would not suggest using the Trollmaker example as a basis for a sketch; There's several problems with it, including size and speed. It's not even okay as a training example, because the HIGH notch is not to sensor specs at 1000 uS (should be 10uS). It also doesn't insure that the trigger pin is LOW before starting the HIGH notch. Basically, while it may work as an example, it's really not correct and you'll have problems when you try to implement it in a larger sketch.

Tim

I tried the "one pin sketch" and it worked with the higher BAUD. It was probably the wrong BAUD fault for the garbage in the original sketch. I see what you mean for the trollmaker sketch, your seems much precise and it uses 15% less memory.
So stupid of me, not looking enough careful at the sketch.

I've been developing a new method in NewPing to do multiple pings, ignore pings that seem to be in error, and average the results. I've implemented it several different ways, from a simple mean average, removing the min and max, and creating an array of ping distances with processing happening in a second pass. To be honest, they all seem to give very similar (if not identical) results. The exception is the simple mean average, which can give inaccurate results if the ping sample is small.

Because the other algorithms yield basically the same results, I've been focusing on using whatever algorithm results in the smallest code and uses the least amount of stack space. I've got everything working quite well. When using this method there's only a code penalty of around 300 bytes (only if you use the new average method, other methods create the same program size as before).

The algorithm looks at the first and second ping, if they're within 2cm it averages the two. If the first two pings are greater than 2cm, it discards the first ping result. It then averages the rest of the pings as long as they're within 2cm of the running average. The only way this algorithm gives an inaccurate result is if there were two erroneous pings in a row, and these two were also the first two pings (very rare). It's not the perfect, but seems to give almost identical results as fancy multiple pass digital noise filtering algorithms.

I'll be doing more testing and releasing it in the next several days.

Tim

Only a day has passed and I've gone a totally different direction with the new multiple ping (digital filter) method. Come to find out, the algorithm I was focusing on yesterday was missing something. Once added, the code size went up over 100 bytes. Because of this, I threw out the code and switched to a more advanced digital filtering. The new algorithm saves the ping results in an array, insertion sorts the array, ignoring the NO_ECHOs finds the median, and finally calculates the mean from results within 4cm of the median.

It's a more effective digital filter and the code is tight. I'm working on making it even better by trying to implement an online median algorithm which would eliminate the need to sort (although the insertion sort currently being used is also an online algorithm and extremely small).

Still tweaking and testing (shaved 44 bites from the compiled code while I was writing this post).

Tim

my robot is working fine with just one single sonar and using that same old ping library
all i did was calibrating the time of servo movement with the time taken by sonar to take a reading

digitalWrite(trigPin, LOW);                   // Set the trigger pin to low for 2uS
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);                  // Send a 10uS high to trigger ranging

  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);                   // Send pin low again
  int distance = pulseIn(echoPin, HIGH);        // Read in times pulse
  distance= distance/58;  // Calculate distance from time of pulse
  delay(10);                            // Wait 50mS before next ranging

Here's the video of robot

bilal_ahmad_21:
my robot is working fine with just one single sonar and using that same old ping library
all i did was calibrating the time of servo movement with the time taken by sonar to take a reading

digitalWrite(trigPin, LOW);                   // Set the trigger pin to low for 2uS

delayMicroseconds(2);
 digitalWrite(trigPin, HIGH);                  // Send a 10uS high to trigger ranging

delayMicroseconds(10);
 digitalWrite(trigPin, LOW);                   // Send pin low again
 int distance = pulseIn(echoPin, HIGH);        // Read in times pulse
 distance= distance/58;  // Calculate distance from time of pulse
 delay(10);                            // Wait 50mS before next ranging

I can see that your robot has problems at times which NewPing would fix. Using the code listed will cause problems. While you don't specifically ask for help, you must be wanting help resolving roblems I see in your video with your robot.

Switching out that code for my NewPing library will allow for faster pings and no long ping timeouts that I can see sometimes with the video of your robot. It doesn't happen often because you have a wall around the area it's in. But, if you didn't have that wall and it was in the wild, it would oddly pause at times. I'm sure you've experienced this as I can see it in your video. NewPing fixes this.

Further, you're not using event-driven programming so your robot is doing nothing when it could be doing something else (for example, multiple ping sensors or different types of sensors at the same time). NewPing has an interrupt-driven ping method that allow you to do other things at the same time you're waiting for a ping echo. For example, with NewPing your sketch could create a ping every 29ms while you rotate the servo. When it received a ping echo it would trigger an interrupt that would turn your robot. There would be no delays in your sketch so more processing could be done without resorting to slowing down the ping rate.

Hope that helps!

Tim

Okay, I believe I've got the new digital filtering method down to as lean as it's going to get with all the kinks worked out. For the coders out there, here's the method:

unsigned int NewPing::ping_ave(uint8_t it) {
	int uS[it], last, median;
	uint8_t i, j, cnt = 0;
	unsigned long total = 0;
	uS[0] = last = ping();   // Send first ping (done here to assist insertion sort).
	for (i = 1; i < it; i++) {                                                    // Ping iterations.
		if (last != NO_ECHO) cnt++;                                               // Array median tracking.
		delay(PING_AVE_DELAY - ((last == NO_ECHO ? _maxEchoTime : last) / 1000)); // Delay between pings.
		last = ping();                                                            // Send ping.
		for (j = i; j > 0 && uS[j - 1] < last; j--) uS[j] = uS[j - 1];            // Insertion sort (big to small).
		uS[j] = last;                                                             // Add last ping to array in correct order.
	}
	if (last != NO_ECHO) cnt++; // Array median tracking.
	median = uS[cnt >> 1];      // Find median.
	cnt = 0;                    // Reset counter.
	for (i = 0; i < it; i++) {                           // Loop through results.
		if (abs(median - uS[i]) < US_ROUNDTRIP_CM * 2) { // Exclude values outside +/-2cm range (digital noise).
			total += uS[i];                              // Building truncated mean.
			cnt++;                                       // Increment counter.
		}
	}
	return (total / cnt); // Return the ping distance mean minus digital noise (truncated mean).
}

To actually implement it you would need to define PING_AVE_DELAY to 29 and add the method to the class (I set the iterations [it] to default to 5). I know not everyone is adept at doing this, but this is a preview for those that are. Also, if there's any statistical gurus out there that want to poke holes in my code, feel free (statistics is not my thing).

The method uses two passes to calculate a truncated mean with outliner pings removed (digital noise). The first pass collects the pings, keeps track of successful pings, and sorts on-line using the very simple and fast insertion sort. Before the second pass it then calculates the median ping (with the unsuccessful pings filtered out).

Once the median value is found, it then does another pass to calculate the truncated mean. Outliner pings are those outside +/-2cm (these are not calculated as part of the mean). Because insertion sort is done on-line instead of in another pass (like a typical bubble sort), even the max of 255 ping iterations can be done without any perceived slow-down (although 255 is a little excessive, 3-9 is typically plenty). Insertion sort is very fast with a small quantity of values to sort. So fast that highly complex sort algorithms like quicksort resort to insertion sort when there's only a few values to sort (like in this case). Also, insertion sort has a very small stack footprint (only one variable) and extremely small code size (because it's so simple, 2 lines of code). It's really the only sort to use on Arduino.

In laymen terms, the new method does a user-specified number of pings as fast as it can (29ms per ping) ignores out of range pings, sorts the ping values, gets the middle distance ping value, filters out pings outside +/-2cm of the middle distance ping, and returns the average of these pings in microseconds.

unsigned int us = sonar.ping_ave(); // Defaults to 5 iterations, returns the truncated mean in microseconds.
unsigned int us = sonar.ping_ave(3); // Do 3 iterations, returns the truncated mean in microseconds.
unsigned int cm = sonar.convert_cm(sonar.ping_ave(9)); // Do 9 iterations and get the truncated mean in cm.

A few explanations about the method specific to the programming. First, some things may not appear as clean as they could be. In many cases this is done to make the code smaller once compiled. By making these changes around 100 bytes was saved on the final compiled code. I believe this is a fair compromise with the limited memory in the ATmega. Secondly, there are some items that may not be statistically perfect. An example is the median when there's an even number of values. The code only picks one median value, even if there technically should be the average of two. This again was done to reduce compiled code size and it also doesn't make much difference as there's a lot of fuzzy logic in a truncated mean anyway.

Basically, the function could be re-worked to be more clear, logical, and maybe slightly more statistically accurate. But, that's technically how the function started. I tweaked it over the course of a few days to try and squeeze as many bytes as I could out of it.

With that said, if there's something wrong, a suggestion, or another performance tweak that doesn't make the compiled code larger, please let me know (that's one of the reasons I'm posting the method before it's released).

Tim

Here's also a simplified version that just finds the median.

unsigned int NewPing::ping_median(uint8_t it) {
	int uS[it], last;
	uint8_t j, i = 0;
	uS[0] = NO_ECHO;
	while (i < it) {
		last = ping();           // Send ping.
		if (last == NO_ECHO) {   // Out of range ping.
			it--;                // Skip, don't include as part of median.
			last = _maxEchoTime; // Adjust "last" variable so delay is correct length.
		} else {                       // Ping in range, include as part of median.
			if (i > 0)                 // Don't start sort till second ping.
				for (j = i; j > 0 && uS[j - 1] < last; j--) // Insertion sort loop.
					uS[j] = uS[j - 1]; // Shift ping array to correct position for sort insertion.
			else j = 0;                // First ping is starting point for sort.
			uS[j] = last;              // Add last ping to array in sorted position.
			i++;                       // Move to next ping.
		}
		if (i < it -1) delay(PING_MEDIAN_DELAY - (last >> 10)); // Millisecond delay between pings.
	}
	return (uS[it >> 1]); // Return the ping distance median.
}

This saves 112 bytes of complied code size so it's worth considering. I could add both a ping_median() and ping_mode() but I feel that's a little excessive. Any statistic types out there may want to chime in on if a pure median calculation is good enough in removing digital noise. Or, if the addition of the truncated mean with removed +/-2cm outliners is worth the 112 byte loss. Median and truncated mean yield different cm results about 10% of the time, but which one is better or more correct? When they return two different values there's not one value that's clearly more accurate more consistently as far as I can tell.

I'm leaning towards the above ping_median method as a digital filter. It's 112 bytes smaller and seemingly just as effective (albeit sometimes different).

Tim

NewPing v1.5 released, here's what's new:

Added ping_median() method which does a user specified number of pings (default=5) and returns the median ping in microseconds (out of range pings ignored). This is a very effective digital filter. Optimized for smaller compiled size (even smaller than sketches that don't use a library).

Download NewPing v1.5

Tim

By the way, I went with just the median method for digital filtering. It's fast, the overhead is much smaller, and the results were just as effective as finding the mean of a centered sample. Using ping_median() is just as simple as using ping() too. There's nothing to figure out, just change ping() to ping_median() in your sketch and it will automatically do 5 pings and return the median ping. To specify a different number of samples, just specify the number of iterations you want like this: ping_median(9). Here's a sample sketch that uses ping_median:

#include <NewPing.h>

#define TRIGGER_PIN  12  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     11  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

void setup() {
  Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
}

void loop() {
  delay(50); // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
  Serial.print(sonar.ping_median(3) / US_ROUNDTRIP_CM); // Send 3 pins and return the median ping converted to centimeters.
  Serial.println("cm");
}

Enjoy!

Wanted to say thanks for the hard work. Really appreciate this library!

Fantastic job with this new library!

I am especially liking the median feature of v1.5 to eliminate rogue results, but how can this be integrated into the 15 sensor example sketch which does not use ping() but sonar.ping_timer(function) instead?

fairorgan:
Fantastic job with this new library!

I am especially liking the median feature of v1.5 to eliminate rogue results, but how can this be integrated into the 15 sensor example sketch which does not use ping() but sonar.ping_timer(function) instead?

To actually combine the two would be simple. However, in the process it would lose the interrupt aspect of the ping_timer() method. I've been toying with the idea, and considering new ways of doing it. One thing I've considered is a mass ping of all sensors at the same time followed by interrupt waiting for the ping echo. With 15 sensors there could be crazy cross-talk. But with 4 sensors it would probably work.

Also consider that with 15 sensors, if it were to poll each sensor 3 times it would take 3 times as long. Not sure how much processing time you have left after pinging 15 sensors. But, multiply the time it takes by 3 or 5 and you may no longer have time to do anything else, or the pings are just too slow to sense something in time if your robot is moving at a high enough speed.

Tim

Hi Tim,

I was actually only thinking of using two SR-04 sensors (at most three), but like your use of the array in the 15 sensor example for holding the data. Would I be better just duplicating the new ping_median once per sensor and not use an array nor interrupt method?

My project: I am controlling a robot’s head position via servos, which currently have either remote control (via joystick) or automatic random positioning of x and y axis. I would like to add the ability for close object tracking of the x axis, so thought the best way would be two ultrasonic sensors pointing slightly apart concealed on the head itself, and in the event of an object coming within say a couple of metres range, the head will stop random movements and track the object in x axis by moving the servo to keep the signal from both ultrasonic sensors the same, until either the object goes out of range or the robot gets bored.

Nick