So...I thought I had it all worked out because I've used this cheapo, err, inexpensive, HC-SR04 (aka 19605-UT) ultrasonic sensor, from mpja.com amongst, in other projects using my MSCapture library which turns Arduino Pin 8 into a Timer 1 counting input -- remind me to rant about this sometime, especially since my library is perfect for grabbing IR remote control signals -- but for now.
See the data sheet here.
But, if you've been reading along, you know that the Servo motor control library is also a big fan of Timer 1 and thus that acre of digital real estate is no longer available. So I had to reinvent the wheel using a different mechanism with Timer 2 which has only an 8 bit resolution and no external count input.
Therein lies the HCSR04 library in my code bolus.
It uses three interrupts (you are surprised?), two from Timer 2 and one from an external pin change signal. The counter is run with the maximum pre-scale of /1024, giving a 64 micro-second resolution which is not quite as fine as one would like, but it turns out that the sensor itself is not quite as fine as one would like either, so it sorta works out. It counts the timer overflow interrupts to add 4 more bits to the timing range, which is somewhat more than enough to detect the SR04's no-signal failure signal.
Two digital pins, and power, are connected to the sensor. One pin is the Trigger output which can be any available digital output pin. It sends a short positive pulse, where the falling edge starts a sonar sample cycle. The other is the Ping input pin which goes high from the end of the sonar ping until it gets an echo response -- or for a loooong time if it misses the echo (more below). The Ping input currently has to be Arduino Pin 2 or 3 -- because I couldn't make sense of the doc for attachInterrupt(), I think one can use other pins but the code will need a light re-wanking.
The library class has these methods:/** default constructor **/
/** initialize the HC-SR04 sensor pins and timer interrupts
** leaves all the interrupts disabled
** use SR04.startPing() to start a sample cycle **/
void init( uint8_t trigpin, uint8_t pingpin );
/** Start a sensor ping cycle
** turns on TRIGPIN and enables interrupts
** Presumes that SR04init() initTimer2() have already been called. **/
/** return true if there is a new distance value available
** will clear itself, so a second call will return false... **/
/** return the last distance value from the sensor
** if it's 0x0000 we didn't get anything... **/
After the class is initialized, a call to startPing() will send a trigger pulse and wait patiently for the results. Under the covers, the Trigger output pin is set high and Timer 2 is started, a count interrupt is fired after two counts and used to turn off the Trigger pin, thus starting the Ping cycle. When, and if, the timer wraps around on 256 64uS counts (~16.4mS), the overflow interrupt fires and a global status variable is incremented -- this allows for an extended count range, in this case up to 4 bits or x16. When the Echo input pin goes low, the input pin interrupt fires, all the counts are counted up, and the available() interface will signal by returning true -- just once. When that happens getDistance() can return something useful.
The speced range of good distance data is from about 10 to 360 (in 64uS increments). I did not subtract the two-four initial trigger counts so you can do that if you want a bit more accurate close range measurement. If you multiply the count value by 1.1 you can get fairly close to the actual distance in CM.
However in a spot check, I did not get reliable distance counts beyond about 180, i.e. 200cm, so YMMV. Also it jumps around, failing for a number of cycles before coughing up an occasional good value.
A note on the bad values.... If there is no ping return received the sensor just keeps going until it gets tired. The spec says this should be 38mS after the trigger, but the reality seems to be closer to 150mS. So when there is nothing to sense, the time between cycles is quite long. When the ping goes missing, available() will eventually return true and the getDistance() method will return 0 -- this just makes it easier to see on a data plot. Should everything fail -- the counter will just keep counting up to it's 16x maximum and getDistance() will return 0x8000 (a negative number).
For a usage example look to the test.ino file. Connect the sensor to Gnd and +5v power, Trigger to Pin 2, and the Ping to Pin 3. Create a global HCSR04 object and call init(pinT, pinP) in setup(). Then call startPing(), available() in a loop, and getDistance() when available returns true.