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.