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

Rewrote the pulseIn to use micros() instead of loop counting. With that change, I got accurate data that matches exactly with the speed of sound. So, the problem was with pulseIn.

Beautiful, Tim. You have challenged lots of assumptions with this and it's really good news that you are understanding what's happening. We'll all be listening for your next round!

Rewrote the pulseIn to use micros() instead of loop counting.

Be aware that micros() always round its value to a multiple of 4.

Counting the loops in pulsein works quite well, however if there are interrupt handled during this it will definitely give a wrong (too low) reading. This could (partially) explain why you need a smaller divisor in your formulas.
This "trick" is used especially to be able to read pulselengths smaller than 4 micros() precission.

Hope to see the code soon!

robtillaart:

Rewrote the pulseIn to use micros() instead of loop counting.

Be aware that micros() always round its value to a multiple of 4.

Counting the loops in pulsein works quite well, however if there are interrupt handled during this it will definitely give a wrong (too low) reading. This could (partially) explain why you need a smaller divisor in your formulas.
This "trick" is used especially to be able to read pulselengths smaller than 4 micros() precission.

Hope to see the code soon!

Let's put it this way. The pulseIn function is from 460 to 7120 microseconds off because of the way ultrasonic sensors work. Basically, the echo port state takes that long to settle and pulseIn counts that time. So, I think I'll settle for only 4 microseconds off. :wink:

I may release the code in the source both ways so if you want to try it the other way, you can uncomment it.

Tim

New in version 1.2:

Lots of code clean-up thanks to Adruino Forum members. Rebuilt the ping timing code from scratch, ditched the pulseIn code as it doesn't give correct results (at least with ping sensors). The NewPing library is now VERY accurate and the code was simplified as a bonus. Smaller and faster code as well. Fixed some issues with very close ping results when converting to inches. All functions now return 0 only when there's no ping echo (out of range) and a positive value for a successful ping. This can effectively be used to detect if something is out of range or in-range and at what distance. Now compatible with Arduino 0023.

Download NewPing v1.2

By default, it uses micros() to do the ping echo timing. But, I also included alternate code that uses loop counting (similar to what pulseIn uses). It's streamlined, but follows the same basic concept as pulseIn.

Let me know what you think, and if you have any issues or suggestions.

Tim

Nice job Tim
It took some time to find points for improvement :wink:

All functions now return 0 only when there's no ping echo

That calls for a

#define  NOECHO  0

makes the code more readable

(style remark)
adding a few blank lines in your code might improve readability

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

// Alternate, uses loop counting to time ping echo.
unsigned int NewPing::ping() 
{
	unsigned long maxLoops = microsecondsToClockCycles(_maxEchoTime + MAX_SENSOR_DELAY) / CLOCK_CYCLES;
	unsigned long maxLoops2 = microsecondsToClockCycles(_maxEchoTime) / CLOCK_CYCLES;

	// Use port registers to trigger a ping.
	*_triggerOutput &= ~_triggerBit;
	delayMicroseconds(2);
	*_triggerOutput |= _triggerBit;
	delayMicroseconds(10);
	*_triggerOutput &= ~_triggerBit;

	// Set a timeout and wait for the echo pin to clear 
	while (*_echoInput & _echoBit)
		if (maxLoops-- == 0) return NOECHO;

        // Wait for the ping to start.
	while (!(*_echoInput & _echoBit))
		if (maxLoops-- == 0) return NOECHO;

	// 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); 
}

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.

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