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.