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

robtillaart:

	// Ping started, wait for the ping echo to return.
while (*_echoInput & _echoBit)
	if (maxLoops2-- == 0) return NOECHO;

// Calculate ping time, 16 = clock cycles of routine overhead.
return clockCyclesToMicroseconds(numloops * CLOCK_CYCLES + 16);


Comparing against 0 is faster and makes code a few bytes smaller

This is with the loop counting ping method, which I don't suggest using, and is mostly pulseIn with slight changes to work correctly with ultrasonic sensors.

Also, your code doesn't quite work, as it must count the loops to figure out the distance in the final loop. Also, numloops is not defined. I tried to implement the corrected version of your code suggestion, but it was actually 2 bytes longer, so one would expect a little slower as well.

I was, however, able to optimize the loop counting ping method to save around 44 bytes. See below:

// Alternate timer, uses loop counting to time the ping echo.
unsigned int NewPing::ping() {
	unsigned long numloops = 0;
	unsigned long cpu_speed = microsecondsToClockCycles(10);
	unsigned long maxLoops = (_maxEchoTime + MAX_SENSOR_DELAY) * cpu_speed / CLOCK_CYCLES / 10;
	unsigned long maxLoops2 = _maxEchoTime * cpu_speed / CLOCK_CYCLES / 10;
	*_triggerOutput &= ~_triggerBit;
	delayMicroseconds(2);
	*_triggerOutput |= _triggerBit;
	delayMicroseconds(10);
	*_triggerOutput &= ~_triggerBit;
	while (*_echoInput & _echoBit)
		if (numloops++ == maxLoops) return NO_ECHO;
	while (!(*_echoInput & _echoBit))
		if (numloops++ == maxLoops) return NO_ECHO;
	numloops = 1;
	while (*_echoInput & _echoBit)
		if (numloops++ == maxLoops2) return NO_ECHO;
	return clockCyclesToMicroseconds(numloops * CLOCK_CYCLES);
}

robtillaart:
Still don't like this one...

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

can be improved by (1) adding rounding and (2) working in millimeters as proposed before.

The granularity of measurement = 4 micros(), in 4 micros sound travels approx 1.35 millimeter, or about 1/7 of a centimeter. that's almost 3 bits of precision, assuming the last 2 are noise your lib should be able to measure 0.5cm == 5mm steps (1/10th inch) without problems.

If you'd like to suggest an alternative, please provide specific code as I can't read your mind and won't try to guess either.

Also, keep in mind that mm conversion is worthless as the sensor is not accurate enough to measure down to the mm (only accurate to 6-7mm). Further, doing the math down to the mm level would not be more accurate because temperature is not being considered and the effect of temperature can be a few cm. In other words, a more complex formula will not yield more accurate results, simply take more program space with no benefit.

Not sure why you're not happy with just changing US_ROUNDTRIP_CM to a fraction or get the microsecond result from ping() and use your own magic conversion code. Am I missing something? Using the sensors in the real world I can't get 1/10th inch results, how exactly are you doing this? Or, is it just a theory you have?

Tim

Thanks for pointing out the error in my code - 3 posts back - numloops get nowhere incremented :blush:

please provide specific code as I can't read your mind

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

add rounding

#define NewPingConvert(echoTime, conversionFactor) (max( (echoTime+conversionFactor/2) / conversionFactor, (echoTime ? 1 : 0)))

Using the sensors in the real world I can't get 1/10th inch results, how exactly are you doing this? Or, is it just a theory you have?

Yes, it is theory to search for what can be reached with the sensors/ code.

You made a good point that the fluctuations due to temperature are large so working in mm makes less/no sense.

robtillaart:
add rounding

#define NewPingConvert(echoTime, conversionFactor) (max( (echoTime+conversionFactor/2) / conversionFactor, (echoTime ? 1 : 0)))

Now I believe I understand what you're getting at. You want to round, say, 10.6cm up to 11cm instead of doing the normal truncate of 10.6cm down to 10cm. I guess what I was thinking was that it doesn't make much sense to round when dealing with cm because the margin of error is already almost a cm. But with inches, I can see where rounding could be logical. I'll do some testing, but probably incorporate it.

I've also been optimizing the loop counting timer and I've got it down to only 8 to 18 bytes longer than the micros() timer. But, the loop counter will never work for event-driven, so I believe eventually I'll remove it from the source.

Tim

v1.3 will remove the loop counting timing method from NewPing. I've found that the compiler optimizes the compiled code with inconsistent results. Sometimes the loop is 17 instructions, other times it's 25 instructions, without changing the loop code. I tried to nail it down, but it kept drifting all over the place every time I made a change to the code (even well outside the loop). I'm also worried about different Arduino hardware also changing the the number of instructions in the loops at compile time. I use Teensy hardware, and while it seemed to match the loop instructions with the Arduino Uno, I just no longer trust that it will the next time I compile, or if compiled for different hardware.

The goal all along was to only have one timer, I included both in v1.2 because I wasn't sure which would end up being best. And to be honest, the loop counting method could never be made event-driven, so it's days were always numbered as making NewPing totally event driven is one of my primary goals. And, now that the loop counting code is gone, that's exactly what I'm going to focus on, making NewPing event-driven. Also, building event-driven 2 and 3 sensor array functions in NewPing. Finally, a hybrid ping/servo scanner that incorporates the event-driven NewPing with event-driven servo control for fast and fluid scanning while still freeing up the ATmega to do other things.

Tim

Sometimes the loop is 17 instructions, other times it's 25 instructions, without changing the loop code.

have you tried to make these vars volatile so the compiler would not optimize?

I believe volatile would only be useful if the variables were being changed outside the loop so the compiler would assume they're not being changed and it makes them a constant. But in this case, they are indeed being changed inside the loop, so I can't imagine that the compiler would assume that. Actually, if it was assuming that, the code wouldn't work. Also, there are some values that could be a constant, like the max loop variables.

Seriously, 17 to 25 instructions is a HUGE swing. When I started seeing odd results I added code that would calculate and display the instruction count. 17, 19, 20, 23, 25... It was like there was no rhyme or reason to it.

Maybe I'll look into it some more. But, the loop counting wouldn't work with an event-driven routine anyway so it eventually had to go.

Tim

In the process of searching for the HC-SR04 in the forum, this thread has popped up as the most useful resource. I've downloaded the library and run the demo code which works just fine unmodified. One strange aspect is that when it's uploaded to my Uno (1.0), the first measurement appears valid, while all others run to zero. If I press the reset button, all is good until next code upload.

Should I jump to the newer version for my device and if so, what complications can I expect?

fred_dot_u:
In the process of searching for the HC-SR04 in the forum, this thread has popped up as the most useful resource. I've downloaded the library and run the demo code which works just fine unmodified. One strange aspect is that when it's uploaded to my Uno (1.0), the first measurement appears valid, while all others run to zero. If I press the reset button, all is good until next code upload.

Should I jump to the newer version for my device and if so, what complications can I expect?

There's no compatibility issues with moving to the latest version of the library. There's also some bug fixes in the later releases so I would highly suggest upgrading.

My guess is that something else is going wrong, however, as the demo code works but your code is not working correctly. There's no obvious reason that I can think of that would explain your situation. So, the best bet is if the latest version of the library still doesn't work, you should post your code source so others or myself can see what the problem is. I have an Uno and it's one of the platforms I use for testing (I also have a Teensy 2.0). I really only test with Arduino v1.0. I have Arduino 0023 on only one of my development machines and all I really do with 0023 is load the demo to make sure it works.

Tim

It's too early in the day for me to perform the upgrade, but I will do as suggested. "My code" is the demo code packaged with the library, unchanged in any way. The only action I had to perform for my sensor was to ensure that I had the correct pins connected, which was easy enough. I'll probably do the upgrade this afternoon and see how that affects the need to reset.

fred_dot_u:
It's too early in the day for me to perform the upgrade, but I will do as suggested. "My code" is the demo code packaged with the library, unchanged in any way. The only action I had to perform for my sensor was to ensure that I had the correct pins connected, which was easy enough. I'll probably do the upgrade this afternoon and see how that affects the need to reset.

Your sensor says SR04 on it, right?

If it's not working with the demo code, another possibility is the sensor. I have several SR04 sensors, but one is just not right. It likes to give 0cm readings, it's either hard of hearing, soft spoken, or there's some kind of logic problem. As punishment, and to insure I don't pull my hair out thinking it's my code, I wrote with a fine tip Sharpie on the sensors "Bad" - "Robot". To verify the problem wasn't my library, I tried another ping library as well as direct code I wrote without a library with the same result. It basically only works closer than about 50cm, then it gets all wonky. Of course, it was one of the sensors I got on eBay direct from China for $2, so it's kind of expected. I keep it around because I figure other people purchase these $2 sensors that are probably also defective. So, I should try to make the library work as best as I can with a defective sensor.

Tim

It basically only works closer than about 50cm, then it gets all wonky

I bought some ebay sensors that I think were rated for 70cm. They seem to work to 65cm and then put out a "0" for distance. I got them cheap and for what I want to do it is ok, but I will have to deal with the 0 output when nothing is in the way.

cyclegadget:

It basically only works closer than about 50cm, then it gets all wonky

I bought some ebay sensors that I think were rated for 70cm. They seem to work to 65cm and then put out a "0" for distance. I got them cheap and for what I want to do it is ok, but I will have to deal with the 0 output when nothing is in the way.

The "0" distance is designed to be a "false" (all clear, no ping) reading (0==false). That makes it easy for you to do a condition on the output. Basically, if it returns a number, there's something in the way. If it returns false, it's all clear. You wouldn't want the library to report back 65cm if there was no ping, because how would you know if there was an object 65cm away or nothing in the way?

You shouldn't have to "deal with the 0 output". The library is giving you a 0 so you know the ping didn't find anything. The "0" is a feature, not something you should need to deal with. Keep in mind that the closest reading is >0, not 0. If you put your hand almost right against the sensor it will still give you a positive result.

In the next release the "0" is replaced with NO_ECHO. You "could" change NO_ECHO from 0 to 2850 (50cm x 57us/cm) or whatever if you really wanted to. It would then be possible to set the maximum distance to 50cm and NO_ECHO to 2850 and then a ping would return a result from 1-50 (never a zero). But keep in mind that if you get a 50cm, you wouldn't know if there was something 50cm away, or if it was all-clear. You wouldn't really know till 49cm that there was something in the way.

Maybe I'm missing something? Or maybe the reason for the "0" (false) result was unclear?

Tim

Maybe I'm missing something? Or maybe the reason for the "0" (false) result was unclear?

I did like that the output was 0 when nothing was in the way because it was at least predictable. I did not realize that you set-up the library to do that on purpose.

I like the idea of true/false based on 0 or non0 that will make it easier to work out the logic for dodging objects.

Keep up the good work!

teckel:
Your sensor says SR04 on it, right?

If it's not working with the demo code, another possibility is the sensor. I have several SR04 sensors, but one is just not right. It likes to give 0cm readings, it's either hard of hearing, soft spoken, or there's some kind of logic problem. As punishment, and to insure I don't pull my hair out thinking it's my code, I wrote with a fine tip Sharpie on the sensors "Bad" - "Robot". To verify the problem wasn't my library, I tried another ping library as well as direct code I wrote without a library with the same result. It basically only works closer than about 50cm, then it gets all wonky. Of course, it was one of the sensors I got on eBay direct from China for $2, so it's kind of expected. I keep it around because I figure other people purchase these $2 sensors that are probably also defective. So, I should try to make the library work as best as I can with a defective sensor.

Tim

The sensor does read HC-SR04 between the transducers, yes. I'm not sure that I'm getting a proper understanding of versions. A search for updates for my Uno gave me an IDE update from 1.0 to 1.0.1 which has been completed. Is there anything else to update/change? I was not able to find a reference to 0223 that made sense to me. Some references to linux but I'm using win7 platform.

With the demo code, it is still necessary to hit the on-board reset button to get data, but if it's the sensor that's faulty and that's the only shortcoming it is going to display, I can tolerate that for the present.

All the benefits of NewPing and the most beneficial to me is the ease of use. I'll be measuring about four times an hour, so speed isn't critical. A 24 inch range is well within the limitations of this device. The rest of this project is "fluff" and fine tuning and fun.

Is there more to an "update" than what I've described above? Where else to check version information if there is more to it?

thanks

fred_dot_u:
The sensor does read HC-SR04 between the transducers, yes. I'm not sure that I'm getting a proper understanding of versions. A search for updates for my Uno gave me an IDE update from 1.0 to 1.0.1 which has been completed. Is there anything else to update/change? I was not able to find a reference to 0223 that made sense to me. Some references to linux but I'm using win7 platform.

With the demo code, it is still necessary to hit the on-board reset button to get data, but if it's the sensor that's faulty and that's the only shortcoming it is going to display, I can tolerate that for the present.

All the benefits of NewPing and the most beneficial to me is the ease of use. I'll be measuring about four times an hour, so speed isn't critical. A 24 inch range is well within the limitations of this device. The rest of this project is "fluff" and fine tuning and fun.

Is there more to an "update" than what I've described above? Where else to check version information if there is more to it?

thanks

When you were talking about upgrading, I figured you meant upgrading the NewPing library, not Arduino. Arduino 1.0.1 is the latest, 0023 is the previous release before 1.0. Some people still use 0023 for older sketches/libraries that don't work on 1.0. You can run 0023, 1.0, and 1.0.1 on the same machine, so it does provide nice options for legacy code.

You said "I've downloaded the library and run the demo code which works just fine unmodified." Which sounds like the demo works fine, right? You suggest that you modified the demo, and then it doesn't work correctly. Which sounds like the problem is with the changes. Could you try the unmodified demo code again? If that works, I would need to know exactly what modifications you made, as they appear to be the problem.

Tim

I think I've been confusing in my expressions. The most recent activity was to update/upgrade from 1.0 to 1.0.1 and I've not had 023 ever. When I discovered NewPing, I felt it was an easier library to use and to understand than my limited experience with ultrasonic.h library.

The demo code was uploaded and the serial monitor displays one line of non-zero distance then displays continuing zero distance figures, regardless of real world conditions. I suspect the non-zero distance is an anomaly. If I press the reset button on the Uno, the serial monitor displays non-zero distance figures.

I'm going to load the code later today that uses the ultrasonic.h library to see if the results are the same. I'm suspecting that the device is at fault, as you suggested.

You may have the trigger and echo wires backwards. I have did it 2 or 3 times already and it didn't damage anything.

I've successfully created an interrupt-driven ping function and added it to my development NewPing. I'm probably going to add a few different interrupt types to satisfy different needs. This first one uses the timer1 interrupt. It works fine, but I'm not overly happy with one aspect and I'm hoping someone with experience has some insight.

In the sketch, it's currently being called like this:

void setup() {
  sonar.ping_timer1(); // This calls the ping
}

// Timer1 interrupt (I'm using prescale at 64 with the interrupt at 7 counts, in other words, every 28 microseconds this function is called).
ISR(TIMER1_OVF_vect) {
  if (sonar.ping_check()) {  // Check to see if we got a ping echo.
    Serial.print("Ping: ");
    Serial.print(sonar.convert_cm(sonar.ping_result));  // Print the result
    Serial.println("cm");
  }
}

Again, it works great. But, what I'd really like to do is clean it up a bit for the end-user. Instead, I'd like it to look like the following:

void setup() {
  sonar.ping_timer1(echoCheck); // This calls the ping and sets the interrupt function to "echoCheck".
}

void echoCheck() {
  if (sonar.ping_check()) {  // Check to see if we got a ping echo.
    Serial.print("Ping: ");
    Serial.print(sonar.convert_cm(sonar.ping_result));  // Print the result
    Serial.println("cm");
  }
}

Functionally, identical, just a little easier to understand for the end user. I understand the basic concept of adding the ISR(TIMER1_OVF_vect) in the library and setting it, but I just can't seem to get it to work without a compiler error. So, I went out looking for someone else who's done something similar and found the TimerOne library. The library is fairly simple so it's an easy read. Specifically, I'm interested in how he does the ISR(TIMER1_OVF_vect). I've tried to duplicate it, but it just won't compile. I could give my errors, but I've tried so many different ways that I've had maybe a half dozen different errors. I think I've nailed down where my problem is to here:

TimerOne Timer1;    // preinstatiate

ISR(TIMER1_OVF_vect)          // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
   Timer1.isrCallback(); 
}

Anyone know what this "preinstatiate" is? I would think I could in the case of NewPing do the following:

ISR(TIMER1_OVF_vect)          // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
   NewPing.isrCallback(); 
}

But, no. Anyone have any tips or could point me in the right direction on how I should implement having a function call set a function and wrap it insde an interrupt service routine?

Tim

15 minutes after I post all that, I think I have it figured out. I did some snooping around in Arduino.h and WInterrupts.c and I believe this works:

void (*intFunc)();

ISR(TIMER1_OVF_vect) {
	if(intFunc) intFunc();
}

With the ping_timer1 doing this:

void NewPing::ping_timer1(void (*userFunc)(void)) {
	intFunc = userFunc;

Note to self, go snooping in the Arduino code for tips in the future.

Tim

Hi Tim,
What object do you use to for your ultrasonic sensors to sense? Could you describe the conditions? I'm gonna try your library soon on my maxbotix sensor soon. It's wired the same way, so hopefully it works.