PROTON Plus PICBASIC COMPILER


 

Ultrasonic Range Finding.

Clarifying the results

Our simple receiving circuit does not have sophisticated filtering or gain controls, so the results can be rather erratic at times. However, this is true of most simple ultrasonic range systems, as sound can be reflected and bounced around by many objects in the vicinity of the initial ping. One way of alleviating this is to use a process named MEDIAN filtering, Median meaning Middle.

A median filter simply takes the middle value of a sorted sample range. Because the range of samples is sorted in either ascending or descending order, any erroneous results should be on the extremities of the samples, and the average value should be somewhere in the middle. For example, suppose we have 7 samples: -

149, 150, 1, 151, 150, 0, 150

A simple average (Addition of all values divided by the amount of values) of these samples would produce a value of 107, which is totally wrong as the average value is clearly between 149 and 151, and is actually 150. It’s the values 0 and 1 that have clouded the result.


To median filter the same 7 samples involves using a straightforward bubble sort algorithm to arrange the values in ascending order.

0, 1, 149, 150, 150, 150, 151

Now if we take the fourth (middle) value in the list, we get the average of 150.

Of course, this method isn’t perfect, and it does rely on the amount of samples taken. For example, if only 3 samples were taken, there’s a good chance that the average value would not be the middle value when sorted, but if 15 samples, or more, were taken, there’s a very good chance that all the erroneous values will be pushed to the outskirts of the samples and the true result will lie somewhere in the middle.

As mentioned above, median filtering relies heavily on a bubble sort algorithm, and a simple bubble sort demonstration program written in PROTON+ BASIC is listed below.

' Bubble sort Demonstration  
Include "PROTON_4.INC"
                   
Symbol SAMPLES_TO_TAKE = 7                ' The amount of samples to take
Dim SAMPLE[SAMPLES_TO_TAKE] as Byte ' Create an array to hold the samples
Dim SWAPTMP as Byte                       ' Temporary variable for swapping
Dim INDEX as Byte                         ' Holds the position in the sort
Dim SWAP_OCCURED as Bit                   ' Indicates if the sort is complete
Delayms 200                               ' Wait for the PICmicro to stabilise
Clear                                     ' Clear all RAM before we start
Cls                                       ' Clear the LCD
STR SAMPLE = 149, 150, 1, 151, 150, 0, 150 ' Load the array with values
Gosub BUBBLE_SORT                         ' Do the bubble sort
' Display the sorted list

Print at 1,1,Dec SAMPLE[0],":",Dec SAMPLE[1],":",Dec SAMPLE[2],":",Dec SAMPLE[3]
Print at 2,1,Dec SAMPLE[4],":",Dec SAMPLE[5],":",Dec SAMPLE[6]
Stop       
'--------------------------------------------------------------------       
' This subroutine implements  a technique called "bubble sort."
 
BUBBLE_SORT:
Repeat
  SWAP_OCCURED = 0                  ' Clear flag that indicates a swap.
  INDEX = 0
  Repeat                            ' For each cell of the array...
   If SAMPLE[INDEX] > SAMPLE[INDEX + 1] Then  ' move larger values up.
     SWAPTMP = SAMPLE[INDEX]              ' ..by swapping them.
     SAMPLE[INDEX] = SAMPLE[INDEX + 1]
     SAMPLE[INDEX + 1] = SWAPTMP
     SWAP_OCCURED = 1               ' Set bit if swap occurred. 
   Endif
   Inc INDEX
  Until INDEX = SAMPLES_TO_TAKE     ' Check next cell of the array.
Until SWAP_OCCURED = 0              ' Keep sorting until no more swaps.
Return


The idea behind the bubble sort subroutine is quite straightforward.. compare adjacent bytes in the array. i.e. SAMPLE[0] and SAMPLE[1].  If the value stored in SAMPLE[0] is less than or equals that in SAMPLE[1] then do nothing.  Otherwise, swap the values so that SAMPLE[0] gets the contents of SAMPLE[1],  and vice-versa. Keep doing this with each pair of values in the array, and the larger values will migrate toward the higher index values. Repeated passes through the array will completely sort it. The routine is finished when it makes a loop through the array without swapping any pairs.

We can now incorporate the BUBBLE_SORT subroutine in our original range finding program, but we need to make some changes in order to take nine range samples instead of the original single sample. The listing for the new 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

' and a MEDIAN filter to give a more reliable reading
'
' 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
Symbol SAMPLES_TO_TAKE =
9    ' The amount of range samples to take

Dim SAMPLES[SAMPLES_TO_TAKE] as Byte
' Create an array to hold the range samples
Dim SAMPLE_COUNT as Byte
Dim PING_LOOP as Byte              
' PING Loop counter
Dim PULSE_LENGTH as Word           
' TOF (Time Of Flight) value
Dim SWAPTMP as Word
Dim INDEX as Byte
Dim SWAP_OCCURED as Bit
Dim MEDIAN as Word
       
'---------------------------------------------------------------------------------
' 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 subroutine


        
'---------------------------------------------------------------------------------
' The PING routine generates a 40khz burst of 8 cycles.

PING:
PING_LOOP =
8                             ' Number of cycles in burst
PING1:

  Set TX1                                 ' 1st half of cycle
  Delayus
10                              ' Create a delay for 10uS         
  Clear TX1                              
' 2nd half of cycle
  Delayus
9                               ' Create a delay for 9uS
Djnz PING_LOOP,PING1                      ' Special mnemonic to form a fast loop
Return
 
'---------------------------------------------------------------------------------
' Create a MEDIAN filter to make an educated guess as to what is the true reading
' from the sample readings taken.
' The routine below is a BUBBLE SORT, that arranges all the samples in ascending

' order within the array SAMPLES.
' The middle sample is then extracted.
' This should eliminate spurious readings from the edges.

MEDIAN_FILTER:
Repeat
  SWAP_OCCURED =
0                        ' Clear flag that indicates swap.
  INDEX =
0
  Repeat                                 
' For each cell of the array...
    If SAMPLES[INDEX] > SAMPLES[INDEX +
1] Then  ' Move larger values up.
      SWAPTMP = SAMPLES[INDEX]           
' ..by swapping them.
      SAMPLES[INDEX] = SAMPLES[INDEX +
1]
      SAMPLES[INDEX +
1] = SWAPTMP
      SWAP_OCCURED =
1                    ' Set bit if swap occurred. 
    Endif
    Inc INDEX
  Until INDEX = SAMPLES_TO_TAKE          
' Check out next cell of the array.
Until SWAP_OCCURED =
0                    ' Keep sorting until no more swaps.
PULSE_LENGTH = SAMPLES[
4]                 ' Extract the middle sample's value
Return

'---------------------------------------------------------------------------------
' Take a range reading

GET_RANGE:
Delayms
20                          ' Delay 20 ms between samples
TMR1ON = 1                          ' Enable TIMER1
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                  ' Exit if a LOW on the ECHO pin is detected
   
TMR1ON = 0                      ' Disable TIMER1 at this point
    PULSE_LENGTH =
TIMER1           ' Store the value of TIMER1
    Break                          
' Exit the loop              
  Endif
  PULSE_LENGTH =
0                  ' If we reached here then Out of Range
Until TMR1IF = 1                    ' Timeout if TIMER1 overflowed

Return


           
'---------------------------------------------------------------------------------
' The main program loop starts here


MAIN_PROGRAM_LOOP:
While
1 = 1                         ' Create an infinite loop
SAMPLE_COUNT =
0
Repeat                             
' Create a loop for all the sample readings
  Gosub GET_RANGE                  
' Go get a range
  SAMPLES[SAMPLE_COUNT] = PULSE_LENGTH /
58 ' Convert into cm and store it
  Inc SAMPLE_COUNT
Until SAMPLE_COUNT = SAMPLES_TO_TAKE
' Loop until all samples taken    
Gosub MEDIAN_FILTER                
' Perform a median filter on the samples
If PULSE_LENGTH =
0 Then            ' Did we reach the end of the loop ?
  Print at
1,1,"NOTHING IN RANGE"   ' Yes. So Display text if out of range
Else                               
' Otherwise...
  ' Display distance in centimetres 

  Print at 1,1,"DIST = ",DEC PULSE_LENGTH," cm       "     
Endif
Wend

The range finding part of the program hasn’t really changed a great deal, it’s simply now called as a subroutine, aptly named GET_RANGE.

The main part of the program forms a nine cycle loop in order to fill the array with samples at 20ms intervals. Once all the samples are taken, the MEDIAN_FILTER subroutine is called to sort the array and extract the middle value. This is then displayed on the LCD.

There is another method we could use that still involves the median filter, that of averaging. This entails adding the middle three of the nine samples taken, then dividing them by the amount of additions, in our case three.

This can be implemented in our program with a single line change. Within the MEDIAN_FILTER subroutine, change the line: -

PULSE_LENGTH = SAMPLES[4]

to

PULSE_LENGTH = (SAMPLES[3] + SAMPLES[4] + SAMPLES[5]) / 3

This method is better performed with more samples, but more samples means a larger delay between range findings. This is only ascertainable with the type of project that it’s intended for, and I’ll leave it up to you whether or not you implement it.