Better Resolution and Accuracy with the HC-SR04 and FlashForth

Previously, I wrote about how to control the HC-SR04 ultrasonic distance sensor with FlashForth to measure distances. The implementation I discussed had some significant limitations in terms of accuracy and resolution due to the use of ticks to time sensor pulses. In FlashForth ticks are counted in milliseconds. Because sound travels at about 34.3 centimeters per millisecond, the accuracy and resolution of this implementation is limited to 17.15 centimeters. In this article, I will introduce new Forth words that increase the resolution by one hundred times, giving us greater accuracy and finer resolution in our measurements.

This article builds upon the previous article. Be sure to refer back to the previous article as well as the source code listing for the complete set of Forth words required.

A higher resolution timer

The microcontroller behind the Arduino Uno is capable of timing microseconds. The pulseIn() function in the Arduino standard library is an implementation of a microsecond timer. It works by counting clock cycles. To increase the resolution of our measurements, we will do the something similar by counting loop iterations.

: pulse-10us ( -- u )
  begin echo? dup if 1 >r then until
  begin r> 1+ >r #1 us echo? 0= until
  r>
;

The word pulse-10us 10 microsecond resolution, or something very close to that. This word works by incrementing a value on the return stack and then leaving it on the stack after the echo pin stops receiving the pulse. There is a 1 microsecond pause to get us as close to 10 microseconds as possible in our loop iterations. We could increase the speed of this by not using the return stack and achieve finer resolution, but we still won’t get microsecond resolution unless we refactor to call AVR assembly directly. Nonetheless, we have increased our resolution considerably. Enough to be useful.

Measuring the distance

With our new higher resolution pulse timer we need to calculate the distance as a double cell number.

: ddist? ( -- u ) pulse! pulse-10us sound-speed um* d2/  ;

The ddist? word uses the double cell operators um* and d2/ to calculate the distance by multiplying the pulse duration by the sound-speed constant and then dividing the result by 2 (because sound travels there and back and we only care about the return trip).

Presenting the new double cell distance values
: fmt-ddist ( d -- caddr u ) tuck dabs <# # # # [char] . hold #s rot sign #> ;

fmt-ddist takes a double length number and formats it as a string to give us a decimal representation of the measured distance.

: show-ddist ( u -- ) ddist? fmt-ddist type ." cm" ;

show-ddist ties everything together by capturing, formatting, and displaying the final result.

Testing and calibration

In the previous article I talked about improving the accuracy of the measurements by using temperature and barometric pressure to determine a more accurate speed of sound constant. Another alternative is to use a physical constant (like a tape measure) to calibrate the device. I placed the sensor next to a tape measure and called the show-ddist function.

show-ddist 206.486cm ok<#,ram>

Then, I moved the HC-SR04 sensor back one inch (as an American, I only own tape measures in freedom units) and called show-ddist again.

show-ddist 209.058cm ok<#,ram>
209.058 - 206.468 = 2.572

The difference between the two measurements is 2.572 centimeters. One inch is 2.54 centimeters. The result is very close. To calibrate, we can repeat this procedure while manipulating the sound-speed constant until we get an accurate reading.

Conclusion

With our new and improved timing word coupled with the ability to calibrate the measurements from the HC-SR04 we now have a Forth implementation accurate enough and with fine enough resolution to be useful. Still, there are probably things that can be done to improve the resolution further. One option is to implement a true microsecond timer using the ATMega328p-pu’s built in microsecond timer which is currently used by FlashForth to measure the CPU load. Another option is to devise a new pulse measuring word in AVR assembly that we can call from Forth that is able to count clock cycles with even finer resolution.