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

terryking228:
Hi Tim,

Great work! I will be trying this out Real Soon!!!

There are quite a few "HC-S04" labelled modules around. Have you tested any variety??

Here are a couple I have: http://goo.gl/VZkSy

The one labelled SRF-06 is said by the supplier to have temperature compensation. It does have a different circuit board / chips.

I'll send you one of each of the HC-SR04 and SRF-06 modules if you want to test them and see if you see any differences.. drop me a PM or email terry@yourduino.com

I'm writing a library for these: http://goo.gl/FWMjI and I'd love to try some cool mapping ideas with your code...

DISCLAIMER: Mentioned stuff from my own shop...

OTHER QUESTION: Your example is for Arduino IDE 1.0 Does the library work for both 0023 and 1.0??

I have (3) HC-SR04's from two different sources. One from Amazon and two direct from China. All work fairly similar. I believe the SRF05 is compatible in mode 1 (2 wire) with NewPing, adding support for PING))) would be fairly easy (1 wire) as would mode 2 (1 wire) of the SRF05. Then there's the SRF02, which is a totally different animal as it uses either I2C or serial so it would need a totally different library. It appears the SRF-06 is a 2 wire device so my guess would be that it would work very well with the NewPing library. I'd love to test your sensors, as I'd like to grow NewPing into a library used for all ultrasonic sensors. I'll PM you.

How I can see this working going forward is to make the NewPing library compatible with all the different ultrasonic interfaces (1 wire, 2 wire, I2C and serial) and compatible with as many sensor models as possible.

I'm an Arduino newb, coming in after the 1.0 release. So, I never used the 0023 platform. I would think at this point supporting 0023 wouldn't be too important. I know there's some old sketches out there that won't compile in 1.0 and using my library may be useful. But, it seems like everything has moved to 1.0 fairly quickly and almost all libraries I see are all written only for 1.0 these days with 0023 stuff being un-linked or listed as legacy, don't use. I wouldn't have a huge problem installing 0023 to address compatibility problems, but not sure if it's really needed at this point.

Tim

Report back: Success!!! I tried your sample sketch on IDE 23 and 1.0. I got an error with 23, it said it could not find NewPing.h. I then put the same library and sketch into 1.0 and it compiled fine. Next, I modified the sketch to print the output from the sensors and also added a delay to slow the data to a readable level.

Here is the modified sketch that I tested with my 2 sensors.

#include <NewPing.h>

NewPing sonar1(12, 13, 200); // Sensor 1: trigger pin, echo pin, maximum distance in cm
NewPing sonar2(10, 11, 200); // Sensor 2: same stuff

#define pingSpeed 100 // Ping frequency (in milliseconds), fastest we should ping is about 35ms per sensor
unsigned long pingTimer1, pingTimer2;

void setup() {
  Serial.begin(9600); 
  // Do other stuff here
  pingTimer1 = millis() + pingSpeed; // Sensor 1 fires after 100ms (pingSpeed)
  pingTimer2 = pingTimer1 + (pingSpeed / 2); // Sensor 2 fires 50ms later
}

void loop() {
  if (millis() >= pingTimer1) {
    pingTimer1 += pingSpeed; // Make sensor 1 fire again 100ms later (pingSpeed)
    int in1 = sonar1.ping_in();
    Serial.println(in1);
  }
  if (millis() >= pingTimer2) {
    pingTimer2 = pingTimer1 + (pingSpeed / 2); // Make sensor 2 fire again 50ms after sensor 1 fires
    int in2 = sonar2.ping_in();
    Serial.println(in2);
    // Both sensors pinged, process results here
  }
  delay(1000);
  // Do other stuff here, notice how there's no delays in this sketch, so you have processing cycles to do other things :)
}

First off, yes, it's designed for Arduino 1.0 only. I'm not sure if at this point development for old versions is really required.

Anyway, I have a bit of a problem with your sketch. You use a delay! Really, you should try to avoid using a delay at all cost, and the sketch is built to not need to use it. The entire problem with the other ping/ultrasonic libraries out there is that because they can add up to a second of delay, it breaks your event-driven sketch.

If you want to slow it down, simply change the pingSpeed, that's what it's there for. It appears you want to only do a ping once a second, so set pingSpeed to 1000 and remove the delay. My guess is that your goal is to have both sensors ping as close to the same time as each other, then print the results, and do that once per second. Here's the proper event-driven code to do that:

#include <NewPing.h>

NewPing sonar1(11, 12, 200); // Sensor 1: trigger pin, echo pin, maximum distance in cm
NewPing sonar2(9, 10, 200); // Sensor 2: same stuff

#define pingSpeed 1000 // Ping frequency (in milliseconds), fastest we should ping is about 35ms per sensor
unsigned long pingTimer1, pingTimer2;
int in1, in2;

void setup() {
  Serial.begin(9600); 
  pingTimer1 = millis() + pingSpeed; // Sensor 1 fires after 1 second (pingSpeed)
  pingTimer2 = pingTimer1 + 35; // Sensor 2 fires 35ms later
}

void loop() {
  if (millis() >= pingTimer1) {
    pingTimer1 += pingSpeed; // Make sensor 1 fire again 1 second later (pingSpeed)
    in1 = sonar1.ping_in();
  }
  if (millis() >= pingTimer2) {
    pingTimer2 = pingTimer1 + 35; // Make sensor 2 fire again 35ms after sensor 1 fires
    in2 = sonar2.ping_in();
    // Both sensors pinged, process results here
    Serial.print(in1);
    Serial.print(" - ");
    Serial.println(in2);
  }
  // Do other stuff here, notice how there's no delays in this sketch, so you have processing cycles to do other things :)
}

This code will ping both sensors once per second as close in time to each other as possible, then output the results. It also will never get in a situation where it could have a 2 second delay. It will always fire exactly every 1 second.

Hope that helps better understand the event-driven programming method and why it's so important for real-world applications.

Tim

You've got two virtually-identical constructors; you could eliminate code by defaulting "max_cm_distance" to "MAX_SENSOR_DISTANCE", couldn't you?

int NewPing::convert_cm(int echoTime) {
	if (!echoTime) return false;
	else return echoTime / US_ROUNDTRIP_CM;
}

If "echoTime" is zero, then "echoTime / US_ROUNDTRIP_CM" is also zero; no need to test up front - slightly longer execution time, shorter code.

Agree with AWOL's proposal , especially as the original code mixes type (false=bool and ints)

you can even make it a macro:

#define convert_cm(echotime) ((echoTime) / US_ROUNDTRIP_CM)

I'm not really sure the "volatile" qualifiers are necessary, except for "_echoInput", either.

"pulseInNew" may as well be "inline"

Hi, I got it to compile under 0023. Modify the beginning of newping.h like this:

---------------------( COPY )--------------------------
#ifndef NewPing_h
#define NewPing_h
#endif

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
-----------------( END COPY )----------------------
This includes the right system .h file...

Hmmm. Maybe that ends up with the #endifs wrong?? But it works? Maybe someone 1/3 my age who knows about this could check it....

Maybe the Arduino check should go first??

..."And the first one now will later be last, for the times they are a'changing "

AWOL:
You've got two virtually-identical constructors; you could eliminate code by defaulting "max_cm_distance" to "MAX_SENSOR_DISTANCE", couldn't you?

I created two constructors to be compatible with existing ping/ultrasonic libraries that only pass 2 variables. I would expect users to use the one that sets the maximum distance. I also wouldn't expect someone to use both in the same sketch, so there's no wasted compiled code space as only one would be compiled. I would love to consolidate the two, as they're basically identical. But, I lack the knowledge of C++ to accommodate a constructor that has a different number of variables to pass while keeping the same constructor name. I come from very long PHP programming background but haven't touched C in at least 20 years. In PHP I would do something like this:

function NewPing(trigger_pin, echo_pin, max_cm_distance=MAX_SENSOR_DISTANCE) {...}

AWOL:

int NewPing::convert_cm(int echoTime) {
if (!echoTime) return false;
else return echoTime / US_ROUNDTRIP_CM;

}



If "echoTime" is zero, then "echoTime / US_ROUNDTRIP_CM" is also zero; no need to test up front - slightly longer execution time, shorter code.

Inexperience on my part and confusion with other languages. I think of false as not the same as zero. false !== 0. If false === 0 (strict) in Arduino, that changes many things.

Tim

function NewPing(trigger_pin, echo_pin, max_cm_distance=MAX_SENSOR_DISTANCE)

Yup, that's pretty much how you would do it in NewPing.h.

 NewPing(int trigger_pin, int echo_pin, int max_cm_distance = MAX_SENSOR_DISTANCE);

Even if you couldn't consolidate in this way, you should always factor the common code into a single function.

I think of false as not the same as zero

The C/C++ paradigm is that zero is false, and everything else is true.

terryking228:
---------------------( COPY )--------------------------
#ifndef NewPing_h
#define NewPing_h
#endif

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
-----------------( END COPY )----------------------

Wouldn't it be?:

#ifndef NewPing_h
#define NewPing_h
 
#if defined(ARDUINO) && ARDUINO >= 100
	#include <Arduino.h>
#else
	#include "WProgram.h"
#endif

Also, did you try this with v1.1 which uses the shift registers?

Tim

robtillaart:
Agree with AWOL's proposal , especially as the original code mixes type (false=bool and ints)

you can even make it a macro:

#define convert_cm(echotime) ((echoTime) / US_ROUNDTRIP_CM)

After learning that false===0 in C++, I've had to rework some things. Basically, I want a ping to return false (zero) if there's no ping echo within the distance allowed. Also, if there is a ping echo, the result needs to be >0. We don't want to confuse a <1 inch ping echo with infinity as this difference would be very important to what you're using the sensor for.

In an case, I've reworked a bunch of the code to accommodate this and to give expected results. Here's the macro that's used for both cm and inch conversion:

#define _convert(echoTime, conversionFactor) (max(echoTime / conversionFactor, (echoTime ? 1 : 0)))

echoTime is obviously the microsecond time and conversionFactor is either US_ROUNDTRIP_CM or US_ROUNDTRIP_IN. It may appear that it's overly complex with code reduction possible. But, I need to make sure 0="no ping echo" and we never get a 0 for a really close range (happens with inches). If there's a better way do write this macro, let me know.

Tim

AWOL:
I'm not really sure the "volatile" qualifiers are necessary, except for "_echoInput", either.

"pulseInNew" may as well be "inline"

Without the "volatile" it won't compile, which is why I used it. Feel free to correct me if I'm wrong.

You're right, I should have made pulseInNew inline, in a hurry and it was late I guess. I've made it inline now. But, that's a part of the code I need to totally rework as I'd really like to see it totally event driven (or an option to have it event driven at least). I really hate delays or small loops that wait for an event without processing something else.

Tim

An echoPing can only be positive as any distance is positive .

That makes it ideal to use the negative values as error codes, but those should be in the primary ping function

something like ..

...
  if (time > MAXTIME) return -100;
  if (other error) return -200;
  // etc
  return time;
}

thinking along that line, the conversion function to cm should only accept unsigned ints for echoTime.

unsigned int NewPing::convert_cm(unsigned int echoTime)
{
  return (echoTime + US_ROUNDTRIP_CM/2) / US_ROUNDTRIP_CM;   // added rounding
}

Without the "volatile" it won't compile, which is why I used it.

Except for the echo pin, you could cast it away.
It isn't necessary for the data direction registers or the trigger pin.

robtillaart:
An echoPing can only be positive as any distance is positive .

That makes it ideal to use the negative values as error codes, but those should be in the primary ping function

True, but the logic is not with the ping echo, it's with the conversion to inches and cm. And this is where it can be zero as the sensor can read something closer than an inch, which yields a zero. Technically, it should never happen with cm. But, I'd rather go the safe route.

Making errors negative is basically what I'm doing, but with zero as the error value. Which is why the code is written that way, to accommodate the zero error indicator. Making the error a negative doesn't remove the need for that code.

Tim

AWOL:

Without the "volatile" it won't compile, which is why I used it.

Except for the echo pin, you could cast it away.
It isn't necessary for the data direction registers or the trigger pin.

When i remove the volatile I get the following error on compile:

error: invalid conversion from 'volatile uint8_t*' to 'uint8_t*'

I didn't just add them on a whim, the compiler told me to or it wouldn't compile.

Tim

No-one said you added them on a whim, but you can get rid of the error message with a simple cast.
The only reason to have volatile qualifier is for polling loops on registers, and the only register you poll is the one associated with the echo pin.

AWOL:
No-one said you added them on a whim, but you can get rid of the error message with a simple cast.
The only reason to have volatile qualifier is for polling loops on registers, and the only register you poll is the one associated with the echo pin.

Gotcha. I assumed there was some very good reason why it was asking for a volatile qualifier. The only value now with a volatile qualifier is *_echoInput

With that said, it didn't change my code size at all, so I suspect the compiler optimized the code identically both with and without the volatile qualifier. Or, maybe I just got lucky and the compiled code is just coincidentally the same length.

Tim

@teckel,

I had the sensors already plugged in on a bread board so, I wiped the code additions together so I could see the results. Thank you for pointing out the ability to time pings using the bit of code you already had in the sketch, in my rush I missed the clear comments that you had in the sketch for timing the pings. I will not use delay on my next usage! :slight_smile:

Thanks for the work!

True, but the logic is not with the ping echo, it's with the conversion to inches and cm. And this is where it can be zero as the sensor can read something closer than an inch, which yields a zero. Technically, it should never happen with cm. But, I'd rather go the safe route.

thats why you should include rounding in the conversion functions (or do mililmeters, or 1/16th)

I had a closer look at the conversion functions and you should revisit them:

Speed of sound is 340.29 m/sec => US_ROUNDTRIP_CM = 58.77

that means that US_ROUNDTRIP_CM should be rounded to 59 iso 58 and US_ROUNDTRIP_IN 149 iso 148?

These rounding errors add up !!

Small experiment with different conversion functions

void setup()
{
  Serial.begin(9600);
}

unsigned int x;

void loop()
{
  for (int i=0; i<=30000; i+=1000)
  {
    Serial.print(i);
    Serial.print(",\t");
    
    x = convert_cm0(i);
    Serial.print(x);
    Serial.print(",\t");
    
    x = convert_cm1(i);
    Serial.print(x);
    Serial.print(",\t");
    
    x = convert_mm(i);
    Serial.print(x);
    Serial.print(",\t");

    x = convert_cm2(i);
    Serial.print(x);
    Serial.println();
  }
  while(1);
}

// reference
unsigned int convert_cm2(unsigned int echoTime)
{
  unsigned long us_roundtrip_100meter = 587734; 
  unsigned long distance = (echoTime*1e4 + us_roundtrip_100meter/2)/ us_roundtrip_100meter;
  return (unsigned int) distance ;
}

// original with rounding
unsigned int convert_cm0(unsigned int echoTime)
{
  return (echoTime+29)/ 58;   // added rounding
}

// using 59 ?
unsigned int convert_cm1(unsigned int echoTime)
{
  return (echoTime + 29)/ 59;   // added rounding
}

// use millimeters 
unsigned int convert_mm(unsigned int echoTime)
{
  return (echoTime*10L + 294)/ 588;   // added rounding
}

Output

0,	0,	0,	0,	0
1000,	17,	17,	17,	17
2000,	34,	34,	34,	34
3000,	52,	51,	51,	51
4000,	69,	68,	68,	68
5000,	86,	85,	85,	85
6000,	103,	102,	102,	102
7000,	121,	119,	119,	119
8000,	138,	136,	136,	136
9000,	155,	153,	153,	153
10000,	172,	169,	170,	170
11000,	190,	186,	187,	187
12000,	207,	203,	204,	204
13000,	224,	220,	221,	221
14000,	241,	237,	238,	238
15000,	259,	254,	255,	255
16000,	276,	271,	272,	272
17000,	293,	288,	289,	289
18000,	310,	305,	306,	306
19000,	328,	322,	323,	323
20000,	345,	339,	340,	340
21000,	362,	356,	357,	357
22000,	379,	373,	374,	374
23000,	397,	390,	391,	391
24000,	414,	407,	408,	408
25000,	431,	424,	425,	425
26000,	448,	441,	442,	442
27000,	466,	458,	459,	459
28000,	483,	475,	476,	476
29000,	500,	492,	493,	493
30000,	517,	508,	510,	510

The 58 code gives 7 cm too much at 510 cm (~1.4 % error)
The 59 code gives 2 cm too little at 510 cm (~0.5% error)
The mm code gives no error at 510 cm (<= 0.2%)

The "mm" code needs long math where the "59"code only needs int math,

conclusion =>
use 59 iso 58 as it has a lower a systematic error (factor 3) with the same codesize & speed.

Same exercise to be done for inches too :slight_smile: