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.

' Ultrasonic range finding
' For use with the two op-amps and an LM393 comparator using a single power supply
' The program uses TIMER1 as an accurate duration counter.
' With this code, a resolution down to 1 inch can be realised.
' Written by Les Johnson for use with the PROTON+ Compiler Version 2.1 onwards.

Include "PROTON_4.INC"        ' Use the PROTON Development board with a 4MHz xtal
Device = 16F871               ' Fake a smaller device for a small routine

WARNINGS = OFF                ' Disable warning messages
Symbol ECHO = PORTB.0               ' Echo signals from comparator
Symbol TX1 = PORTB.4                ' 40KHz signal pin
Symbol TIMER1 = TMR1L.WORD          ' Create a 16-bit variable out of TMR1L/TMR1H
Symbol TMR1ON = T1CON.0             ' TIMER1 Enable/Disable
Symbol TMR1IF = PIR1.0              ' TIMER1 overflow flag

Dim PING_LOOP as Byte               ' PING Loop counter
Dim PULSE_LENGTH as Word            ' TOF (Time Of Flight) value
' Program starts here
Delayms 500                         ' Wait for PICmicro to stabilise         
INTCON = 0                          ' Make sure all interrupts are OFF
T1CON = %00000001                   ' Enable Timer1 with a prescaler of 1:1
TRISB = %00000001                   ' Set ECHO pin as input, all others as outputs
Cls                                 ' Clear the LCD  
Goto MAIN_PROGRAM_LOOP              ' Jump over the PING subroutine
' The PING routine generates a 40khz burst of 8 cycles.

PING_LOOP = 8                       ' Number of cycles in ping

  Set TX1                           ' 1st half of cycle
  Delayus 10                        ' Create a delay of 10uS         
  Clear TX1                         ' 2nd half of cycle
  Delayus 9                         ' Create a delay of 9uS
Djnz PING_LOOP,PING1                ' Special mnemonic to form a fast loop
' The main program loop starts here

While 1 = 1                         ' Create an infinite loop
  TMR1ON = 1                        ' Enable TIMER1
  Delayms 100                       ' Delay 100 ms between samples
  TMR1IF = 0                        ' Clear TIMER1 overflow
  Gosub PING                        ' Transmit a 40KHz pulse
  TIMER1 = 0                        ' Reset TIMER1 before entering the loop

  Repeat                             ' Loop until TIMER1 overflows
    If ECHO = 0 Then                ' Capture TIMER1 if a LOW on ECHO pin detected
      TMR1ON = 0                    ' Disable TIMER1 at this point
      PULSE_LENGTH = TIMER1         ' Store the value of TIMER1

      Break                         ' Exit the loop              
    PULSE_LENGTH = 0                ' If we reached here then Out of Range
  Until TMR1IF = 1                  ' Timeout if TIMER1 overflows
  If PULSE_LENGTH = 0 Then          ' Did we reach the end of the loop ?
    Print at 1,1,"OUT OF RANGE    " ' Yes. So Display text if out of range
  Else                              ' Otherwise...

    ' Display distance in inches
    Print at 1,1,"DIST = ",DEC PULSE_LENGTH / 146,34,"  "

Software explanation.

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 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: -


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.


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.