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.
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
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!
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
robtillaart:
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 !!
I knew this was going to happen...
I originally had a longer comment about the speed of sound in my library, but removed it as it seemed a little too preachy and bordered on a rant. Basically, it talked about how there's actually some debate as to the speed of sound, if you can believe it. We can measure the speed of light and time within the smallest margins to track your location down to 3 meters with cheap GPS technology, but there's a debate on how fast sound travels and what influences that speed.
Google reports the speed of sound as 340.29m/s. The speed Google reports is only true when the temperature is 59 degrees (not a typical temp in most situations). Wikipedia gives the speed as 343.2m/s in "dry air" at 68 degrees, yet the humidity, pressure, altitude, etc. makes no difference in the speed (only the temperature affects the speed). The actual speed of sound at 73 degrees (a fairly typical testing temperature) is 344.838m/s. See this page for details: Speed of sound in air temperature barometric pressure calculator without no table air density of air formula temperature calculation mach 1 acoustic impedance room temperature propagation air density sea level velocity ideal gas 20 degrees or 21 degrees Celsius C cold warm - sengpielaudio Sengpiel Berlin
I'm going to use 73 degrees as the standard, as I consider 73 a good average temperature in most situations and works out to a number very close to an integer: 57.998. So, the first problem with converting time to distance is everyone has a different opinion on how fast sound travels, and it really is different for different temperatures. Using 58 for US_ROUNDTRIP_CM is actually almost perfectly accurate at 73 degrees. Your 58.77 value would only be accurate if it was 59 degrees, which I believe is a little cold for real-world situations. If that's your current temperature, by all means, change US_ROUNDTRIP_CM to 58.77.
The next problem is that I believe there's some overhead or delay either before the ping or reading the echo. Some sensors' documentation state this delay, but most do not. Actual measurements confirm this, showing times being way off at close distances but getting more accurate at longer distances. This indicates that there's some processing overhead going on, which makes small readings off, and longer readings more accurate. I've actually already done this testing and calculation and for my sensors I would add 97 microseconds to make the results closer to accurate over the full range of the sensor.
So, a more accurate conversion from microseconds to cm assuming 73 degrees would be cm=(microseconds+97)/57.998. The adding of this 97 microseconds will be addressed at some point in my library, once I have compiled enough sensors to actually confirm this is a fairly fixed value for multiple devices by different manufactures.
Next... There's the issue of the actual pulseIn timing code in Arduino. I'm not 100% confident the technique is "sound" (no pun intended). While I've tried to streamline the pulseIn function, I still don't like it. It's done in a sequential in-line manner with a loop instead of event-driven. How it does the timing is based on the processing cycles for the loop, not time. I intend on looking into this further and hopefully writing an event-driven version of pulseIn that is still (basically) accurate but also doesn't send the program off into a loop rendering the processor useless while it waits for a ping echo.
Next... These sensors really can't get a measurement out to 500cm. It's more like 400cm or less. Some quote 500cm, so I figured I should make that the max, just in case. So, while it's true calculation errors get larger at longer distances, in the real-world it's not quite as drastic.
Finally, there's the issue of using floating point values which require additional libraries to be compiled taking up program space and additional processor cycles at execution time. That's why I used integers instead of fractions. It seems silly to make US_ROUNDTRIP_CM 57.998, when the accuracy of the sensor is +/- 0.3cm, we're guessing at the temperature, we know the sensor has some overhead, and the timing code is in question.
If we could assume that pulseIn was perfectly accurate, the correct full calculation would be as follows (it would also require a temperature sensor):
cm=(us+97)/(331.5*sqrt(1+(c/273.15))); // us=microseconds, c=degrees celsius
So in conclusion... This is why I used 58 for US_ROUNDTRIP_CM. It's also why I have a paragraph after this define in NewPing.h which says:
// ---------------------------------------------------------------------------
// Ballpark numbers for US_ROUNDTRIP_CM and US_ROUNDTRIP_IN were used. Feel free to change if more
// accurate readings for your testing temperature and situation are needed. The values don't need
// to be integers. But, using integers will save program space. And honestly, we're talking
// microseconds here. If we're off by a few dozen microseconds one way or another does it really
// matter? The HC-SR04 isn't that accurate to begin with to fuss too much with this.
// ---------------------------------------------------------------------------
So, if your testing environment is 59 degrees, feel free to change US_ROUNDTRIP_CM to 58.77 and take the performance and program space hit. Or, leave it at 58 and enjoy the fairly close to accurate results.
I warned you this topic was preachy and a rant.
Hi,
Great subject; I really like this kind of detailed discussion about a good subject... Thanks for the work writing the library.
Tim, one of the sensors we were talking about has a built-in temperature sensor: This one: http://goo.gl/Ox2rP
This is the one that has both UART mode and Pulse/echo mode. The temperature measurement only works in UART mode.. The docs I have say:
---------------------( COPY )--------------------------
Temperature function only in UART mode. TX send 0X50 trigger temperature measurement. When temperature measurement is completed, the module will return a byte temperature ( tDATA ), The actual temperature is tDATA-45 . For example, by TX sending 0X50 , RX received 0x45 , then the temperature at this time is [69 ( 0x45 to 10 decimal value) -45] = 24 degrees C
-----------------( END COPY )----------------------
Of course temperature can be measured with a DS18B20, or etc also...
Maybe you can actually do temp compensation?? Maybe only important for OutdoorBots...
Sensors should be there in 2 or 3 days...
Mostly I'm ok with rough figures from sonars.
It is usually just
@teckel
Thanks for the explanation!, good point as I did not investigate the temperature in it.
yet the humidity, pressure, altitude, etc. makes no difference in the speed (only the temperature affects the speed).
Humidity does make a difference, too. Furthermore the effect of temperature and humidity depends on the frequency of the sound.
see this publication for the math details - http://www.rane.com/pdf/ranenotes/Enviromental_Effects_on_the_Speed_of_Sound.pdf - (updated link)
To get a optimal pulseIn you should play with Timer1 as it can count in HW in steps of 1/16 us. That's as precise as Arduino gets. Use an IRQ to get the edges of the pulse and do a diff on the countervalues. See some of my experiments in this thread - http://arduino.cc/forum/index.php/topic,96971.45.html -
robtillaart:
@teckel
Thanks for the explanation!, good point as I did not investigate the temperature in it.yet the humidity, pressure, altitude, etc. makes no difference in the speed (only the temperature affects the speed).
Humidity does make a difference, too. Furthermore the effect of temperature and humidity depends on the frequency of the sound.
You're correct with humidity, I misspoke. I meant to say at some point that humidity makes a slight difference. You could add humidity into the calculations, but most of that could be discounted in the margin of error of the sensor itself. And, the distance the sensor can read makes humidity mostly trivial under most situations.
I guess my point was really that using close whole integers is probably good enough for most purposes. If not, use the ping function which outputs the time in microseconds and do your own calculations using a temperature sensor and optionally even a humidity sensor if your readings need to be that precise. But, if your readings really need to be that precise, you probably should use a different ranging sensor.
With all this said, I do plan on looking further into this overhead delay and possibly adding a constant in the calculations if it's found that this delay is a constant. I need to build a testing jig with distance measurements to accurately test multiple sensors in a very controlled manor. But, I'm not even going to do that until the pulseIn is rewritten as that could have an effect on these readings. I could also add a microsecond to cm and inch conversion where you could also specify the temperature from a temperature sensor. I do plan on sticking with using integers for basic conversions to save space and speed. If more precise values can be verified, I'll add those to the code as an option for those who are already using floating point calculations in their sketch or don't have a code space issue.
Tim
wrt floating point, keep the code in the integer/long range. Optionally do the math in millimeters (multiply all with 10) and bring it back to cm in the end (divide by 10) That gives higher precision without the overhead of the float. Did that in one of the conversion variations (prev post).
wrt Humidity, we discussed a year(?) ago measuring the depth of a water pit (well?). There the humidity was close to 100% so it affected the ping sensor readings to the max, so in the end the working allways depends on the context
I could also add a microsecond to cm and inch conversion where you could also specify the temperature from a temperature sensor. I do plan on sticking with using integers for basic conversions to save space and speed.
imho set the temperature seperately to choose the divider to use from a small lookup table. Temp does not change that often, so conversion time will be minimized, no need to do the lookup agaiin and again.
(snippet, you get the idea)
byte table[] = { ...,57,58,59,60, ...}
div = 58; // default
rnd = div/2; //rnd is short for rounding
setTemperature(int F)
{
F = F/20;
div = table[F];
rnd = div/2;
}
unsigned int convert_cm(unsigned int echoTime)
{
return (echoTime + rnd)/ div;
}
robtillaart:
wrt floating point, keep the code in the integer/long range. Optionally do the math in millimeters (multiply all with 10) and bring it back to cm in the end (divide by 10) That gives higher precision without the overhead of the float. Did that in one of the conversion variations (prev post).wrt Humidity, we discussed a year(?) ago measuring the depth of a water pit (well?). There the humidity was close to 100% so it affected the ping sensor readings to the max, so in the end the working allways depends on the context
This is what the ping function is for, so you can do your own calculations based on microseconds as you see fit. The ping_cm and ping_in are designed to be fast, lite and "good enough" for most situations. After all, these kinds of sensors are typically used to provide "too close", and "clear ahead" readings, not really exact measurements. If I could make an accurate calculation, I would. It's simply not possible to be accurate to the mm, so a calculation like that would be pointless. It's silly to have a ping_mm function if it's off by 15mm, no? My goal is to be accurate to the cm, which with these sensors and how pulseIn works will be a real challenge. mm resolution is a pipe dream, I suggest a laser sensor if that's what you need.
Basically, if you need some unique calculation that's accurate down to the mm, use the ping function and real-world calibration from your own unique sensor. But, be warned. Even if your formula is accurate, and you account for temperature and humidity, your result will still be off by as much as a cm or even more as there's other problems with how the readings are timed and the margin of error in the sensor itself.
And once again, this is why I made the statement that I did in my code, that I'm using integers and if you want to use something else, change the numbers. Or better yet, use the ping function and use your own conversion function for your needs.
Tim
Tim, This is an approach that could perhaps be taken more often with libraries in general: Expose relevant internal (previously private) variables for the user who wishes to do additional calculations.
Maybe the library could even be structured so that if a user does that, the code for further/final calculations in the "usual" application is not compiled?
I'm still semi-illiterate about library details and conventions so this is just conjecture.
@Terry
Every published library is open, so users can allways "roll their own" variation of the library. I think Tim does a very good job keeping the lib as simple as possible because that means small footprint and performance most of the time. My additions and ideas is just searching the "edge of the possible"
@Tim
You should use the link to this thread in your library so people can reread the arguments somewhere in the future. e.g. add a header like
//
// FILE: newPing.cpp
// AUTHOR: Tim Eckel
// DATE: 2012-05-xx
// VERSION: 1.1
//
// PUPROSE: new implementation of ping)))
// LINK: http://...
// HISTORY:
// 1.0: ...
// 1.1: ...
Hi, Rob..
You are always (cut-paste) on the "edge of the possible"
Many Arduino users have no idea of what the source of a library looks like, or why it has that structure, or how to force it to recompile. They are happy (and capable) using a documented library. But maybe if they could access the library functions at both a lower and higher level it would be useful. Maybe.
I'm not sure what side of the divide I'm on, or even where it is...
I really glad you guys are digging into this.
terryking228:
Tim, This is an approach that could perhaps be taken more often with libraries in general: Expose relevant internal (previously private) variables for the user who wishes to do additional calculations.Maybe the library could even be structured so that if a user does that, the code for further/final calculations in the "usual" application is not compiled?
I'm still semi-illiterate about library details and conventions so this is just conjecture.
Terry, that's exactly how I see it. I think the primary goal of the NewPing library is to measure the ping/echo time and distance as easily, quickly and accurately as possible. But, when we get into advanced distance conversion, the requirement to consider temperature and to a lesser extent humidity take it outside the scope of an ultrasonic/ping library as other sensors are required. For those with this need, the NewPing library provides the user with the low-level ping/echo timing. And those with this need and knowledge of what other sensors are needed should already know the equation they need to use.
Not that I'm going to wash my hands of this and off-load it. There's just more primary issues, like making pulseIn event-driven and a generally more accurate echo time sensor first.
Tim
robtillaart:
@Terry
Every published library is open, so users can allways "roll their own" variation of the library. I think Tim does a very good job keeping the lib as simple as possible because that means small footprint and performance most of the time. My additions and ideas is just searching the "edge of the possible"@Tim
You should use the link to this thread in your library so people can reread the arguments somewhere in the future. e.g. add a header like//
// FILE: newPing.cpp
// AUTHOR: Tim Eckel
// DATE: 2012-05-xx
// VERSION: 1.1
//
// PUPROSE: new implementation of ping)))
// LINK: http://...
// HISTORY:
// 1.0: ...
// 1.1: ...
Thanks for the suggestions!
Tim
Thanks to Terry King and YourDuino I did a round of range testing with 6 ultrasonic/ping sensors and the results were not as I expected. I tested with 3 different models, (4) SF04, (1) SRF05, (1) SRF06. The testing environment was in an air conditioned room at 75 degrees. I tested at distances of 5cm, 10cm, 25cm, 50cm, and 100cm. Longer distances were not tested in this round. I believe the accuracy within 100cm or important as that's within the primary measurement distance. Beyond 100cm it's not as much of an issue in most situations. Once the pulseIn echo code is reworked, I'll do round 2 of testing out to a further distance.
The good news is that the sensors were all very consistent. With all the SF04 and SRF05 generating nearly identical results (makes sense as the PCBs are nearly identical). The SRF06 uses different components and yields slightly different measurements as a result. It does appear to be a slightly better sensor (makes less clicking noise, hardly no lag, and results closer to actual speed of sound). The maximum distance of the SRF06 seemed to be slightly lower, but that will be answered in round 2 of testing.
The results don't fit at all with the speed of sound calculations previously being used. For the SF04 and SRF05 sensors, the calculation from microseconds to cm is:
cm = (microseconds + 37) / 48
For the SRF06 sensor, the calculation is:
cm = (microseconds + 5) / 51
Using these calculations, I could accurately measure any distance in the 5cm to 100cm range with all 6 sensors. Albeit, not even close to speed of sound at this temperature, which should be right around cm = microseconds / 58. If it was simply a long sensor lag, the additional microseconds added in the equation would be much higher, which they're not.
I have two theories as to why this is happening:
At this point, I think diving into the pulseIn code and replacing it is a wise choice. Then at least I'll have some other information to figure out the source of the time discrepancy.
Tim