PROTON Plus PICBASIC COMPILER
Ultrasonic Range Finding.
Range Finding Software
Now that we know how the circuit works, we can look at the PICmicro BASIC code that brings everything together and produces a usable range reading on the LCD. The full listing for the program is shown below.
Set TX1 '
1st half of cycle
Exit the loop
' Display distance in inches
The BASIC program that ties everything together is surprisingly simple in its operation, but has been carefully crafted to allow more than enough accuracy.
The program is centred around one of the PICmicro’s hardware timers. TIMER1, or TMR1 to give it its official name, is a 16-bit timer that, once enabled, will start counting on every instruction cycle (FOSC/4). When the timer overflows. i.e. reaches a count of 65536, a flag will be set (TMR1IF) and the timer will start counting from 0 onwards again. TIMER1 can be both read and written to, thus allowing a precise measurement of time to be measured. TIMER1 has two modes of operation, counter or timer, we’re using it as a timer in this program.
At this point it may be a good idea to have the PIC16F877’s datasheet at hand, which can be downloaded, free of charge, from www.microchip.com. Another great free download from Microchip is the midrange reference manual, which is packed full of useful information relating to all aspects of the PICmicro’s architecture and operation, including detailed information relating to the PICmicro’s several timers.
The program starts by enabling TIMER1 and assigning a 1:1 prescaler to it, this means that the timer will increment on every instruction cycle. Different prescaler ratios can also be attached to TIMER1 allowing it to increment on every 2, 4, 8, 16, 32, 64 or 128 instruction cycles. However, in our application, we need microsecond timing, so a 1:1 ratio is perfect.
After clearing the TIMER1 overflow flag (TMR1IF), the program then calls the PING subroutine which transmits a 40KHz signal 8 times from PORTB.4. Timing within the PING subroutine is deliberately accurate, as the amount of cycles taken by each instruction dictates the frequency and duration of the output signal.
Once the ping has been transmitted from the transducer, TIMER1 is cleared and a loop is formed to detect the returning echo. The echo pin (PORTB.0) will be pulled low if a signal was detected, and TIMER1 will be halted and its value transferred to the variable PULSE_LENGTH, then the loop will be exited using a BREAK command. If an echo signal is not detected within the full range of TIMER1 (65535), the loop will be exited anyway by checking the TIMER1 overflow flag, which will clear the variable PULSE_LENGTH.
We have now captured the Time of Flight value from the transmitted ping to it’s reception (if any). So we can convert the time into distance using some simple experimentation and a little arithmetic.
We know from the earlier discussion that the speed of sound here on earth (in my office anyway) is 344 Metres per Second, or 3440 centimetres per second, and we know how long in microseconds it took for the echo to be received, so by dividing the time taken; by the speed of sound, we can come up with the distance in centimetres to the target object. Well not quite… we do have some overheads to take into account such as the duration of the initial 40KHz ping, sound propagation, and latency caused by software and hardware etc. This is where some experimentation comes in.
As an example, the distance from the top of the transducer pair sitting on my bench to the ceiling is exactly 150cm or 59 inches, as measured using an old fashioned tape measure. The raw value produced in PULSE_LENGTH (derived from TIMER1) is 8642. It actually varies from 8642 to 8666 but this will be ironed out with the division. This value is a close approximation of how many microseconds it took for the round trip of the 40KHz ping plus the overheads mentioned above. The setup I used is shown below.
Dividing the raw distance of 8642 by 150 gives a value of 57.6, so without using floating point, our calculation to convert to centimetres is a simple division by 58: -
PULSE_LENGTH / 58
57.6 is closer to 58 than it is to 57, so a division by the closest integer is sufficient with the integer math that we’re using.
To calculate the value in inches, I used the same principle. I divided the raw distance value contained in PULSE_LENGTH (8642) by 59, which is the imperial representation of 150 centimetres. This gave me a value of 146.4, so if we divide by its closest integer of 146, we’ll get the distance measured in good old inches.
PULSE_LENGTH / 146
As you can tell, I’m not a mathematician or I would have calculated the overheads instead of experimenting, but isn’t that the whole point of this discussion, and anyway where’s the fun in knowing beforehand what’s going to happen.
The relevant distance is displayed on the LCD using the standard PRINT command, only if the value of PULSE_LENGTH is greater than zero. If PULSE_LENGTH is equal to zero then this means that TIMER1 overflowed because no echo was received, and the text “OUT OF RANGE” is displayed.