O uśrednianiu słyszał każdy, ale nie każdy do końca rozumie sposoby i ich konsekwencje. Postaram się przedstawić trzy przykłady.
Najprostszym sposobem jest zebranie kilku próbek i podzielenie ich. Na przykład, wynik z ADC ma maksymalnie 1023 a Word pomieści 65536. Można więc spokojnie w jednym takim Word dodać do siebie 64 pomiary i potem szybko podzielić przez 64. Wadą tego rozwiązania jest to, że program się tu zatrzymuje na czas pomiarów. Czyli musi 64 razy zmierzyć... Można zbierać mniej próbek... W zależności od programu nie musi to wcale przeszkadzać. Kod takiego rozwiązania, gotowy do przetestowania w Bascomowym symulatorze, poniżej. Zwróć uwagę że Bascom pod suwakiem ADC pokazuje co ustawiłeś a na wyświetlaczu masz wynik ;)
$regfile = "m8def.dat" $crystal = 8000000 $hwstack = 40 $swstack = 16 $framesize = 32 $sim 'OZNACZA PRZYGOTOWANIE KODU DO SYMULACJI - USUNĄĆ DLA PRAWDZIWEGO MIKROKONTROLERA Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2 Config Lcd = 16x2 Deflcdchar 0 , 32 , 32 , 14 , 17 , 31 , 16 , 14 , 1 Deflcdchar 2 , 32 , 32 , 14 , 1 , 15 , 17 , 15 , 1 Cursor Off Cls Config Adc = Single , Prescaler = Auto , Reference = Avcc Const Ilosc_probek = 50 Dim Adc_read As Word , Idx As Byte Do Adc_read = 0 For Idx = 1 To 64 Adc_read = Adc_read + Getadc(0) Next Shift Adc_read , Right , 6 Locate 1 , 1 : Lcd Adc_read ; " " Loop
Innym sposobem jest przechowywanie większej ilości poprzednich wyników a nowe odczyty pomału wpływają na na całość średniej.
W tym celu musimy mieć tablicę Wordów(n) i to jest po części wadą tego rozwiązania jeśli mamy uC z małą ilością SRAM.
Sposób działania jest prosty. Mamy zmienną która pokazuje do której komórki tablicy zapiszemy sobie nowy wynik (tuaj Idx).
Zawsze odczytujemy nowy wynik i zawsze sumujemy wszystkie próbki a potem dzielimy przez ilość próbek. To jakby drugi minus tego rozwiązania.
$regfile = "m8def.dat" $crystal = 8000000 $hwstack = 40 $swstack = 16 $framesize = 32 $sim 'OZNACZA PRZYGOTOWANIE KODU DO SYMULACJI - USUNĄĆ DLA PRAWDZIWEGO MIKROKONTROLERA Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2 Config Lcd = 16x2 Deflcdchar 0 , 32 , 32 , 14 , 17 , 31 , 16 , 14 , 1 Deflcdchar 2 , 32 , 32 , 14 , 1 , 15 , 17 , 15 , 1 Cursor Off Cls Config Adc = Single , Prescaler = Auto , Reference = Avcc Const Ilosc_probek = 50 Dim Tablica(ilosc_probek) As Word Dim Idx As Byte , Iteration As Byte Dim Sum As Dword , Wynik As Word Do Incr Idx : If Idx > Ilosc_probek Then Idx = 1 Tablica(idx) = Getadc(0) Sum = 0 For Iteration = 1 To Ilosc_probek Sum = Sum + Tablica(iteration) Next Sum = Sum / Ilosc_probek Wynik = Sum Locate 1 , 1 : Lcd Wynik ; " " Loop
Kolejne rozwiązanie nie potrzebuje ani czekać na dużo pomiarów, ale też nie potrzebuje tablicy. Poprzednia suma jest przechowywana w zmiennej typu DWORD.
Wystarczy popatrzeć żeby zobaczyć jak to działa ;) Jeśli masz więcej kanałów ADC do uśrednienia to dla każdego potrzebujesz osobną Sumę DWORD (cztery bajty).
$regfile = "m8def.dat" $crystal = 8000000 $hwstack = 40 $swstack = 16 $framesize = 32 $sim 'OZNACZA PRZYGOTOWANIE KODU DO SYMULACJI - USUNĄĆ DLA PRAWDZIWEGO MIKROKONTROLERA Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2 Config Lcd = 16x2 Deflcdchar 0 , 32 , 32 , 14 , 17 , 31 , 16 , 14 , 1 Deflcdchar 2 , 32 , 32 , 14 , 1 , 15 , 17 , 15 , 1 Cursor Off Cls Config Adc = Single , Prescaler = Auto , Reference = Avcc Dim Adc_read As Word Dim Suma As Dword Dim Wynik As Word Dim Help As Dword Do Adc_read = Getadc(0) Help = Suma Shift Help , Right , 3 Suma = Suma - Help Suma = Suma + Adc_read Help = Suma Shift Help , Right , 3 Wynik = Help Locate 1 , 1 : Lcd Wynik ; " " Loop End
Dla dwóch ostatnich kodów nagrałem film z symulacji Bascomowej. Sam oceń co działa/reaguje szybciej. Oczywiście metodą średniej ciągnionej przyspieszymy kod zmniejszając ilość próbek (wielkość tablicy). Można też użyć ilośc próbek która będzie potęgą dwójki czyli 16,32,64 i skorzystać z szybkiego dzielenia przesunięciem bitowym Shift.
Mediana - odsyłam do Wikipedii jeśli nie uda mi się tego przedstawić jaśniej - to "zebranie kilku próbek, posortowanie ich w kolejności rosnącej/malejącej i wybranie środkowej"
Najprościej pisząc. Jeśli mamy zbiór pięciu odczytów z ADC (do tego musimy je przechowywać w tablicy) to na końcu sortujemy je.. i tak powiedzmy, że odczyty były 512, 511, 120, 513,511 to widać że odczyty głównie mają 512, ale jeden odczyt 120 zaburza cała sprawę i dla średniej arytmetycznej miałby duży wpływ..
Natomiast mediana to posortowanie tych wyników od najmniejszego do największego i wybranie środkowego. To odrzuca anomalie.
Sortujmy 120,511,511,512,513 i wynikiem tej mediany będzie 511 kiedy średnia arytmetyczna wyniosłaby...433! (dodaj wszystkie do siebie i podziel/5)
Najprostszy kod mediany poniżej. Sort to funkcja Bascoma
Const Ilosc_probek_adc = 5 Const Mediana_adc =(ilosc_probek_adc + 1) / 2 Dim Adcy(ilosc_probek_adc) As Word N = Ilosc_probek_adc : Adc_sum = 0 Do Adcy(n) = Getadc(adca , 3 ) Decr N Loop Until N = 0 Sort Adcy(1) 'posortuj od najmniejszej do największej Adc_result = Adcy(mediana_adc)
Jeśli dokładność dziesięciu bitów czyli zakres do 1023 nie jest nam potrzebny to możemy ADC czytać "szybciej" w zakresie do 255. Wystarczy w tym celu ustawić jeden bit konfigurujący ADC.
Poniżej gotowa funkcja. Podaje się numer ADC a zwróci wynik do zmiennej typu Byte
$regfile = "m8adef.dat" $crystal = 8000000 $hwstack = 32 $swstack = 8 $framesize = 24 Config Submode = New Config Lcd = 16x2 Config Lcdpin = Pin , Db4 = Portd.5 , Db5 = Portd.6 , Db6 = Portd.7 , Db7 = Portd.4 , E = Portd.3 , Rs = Portd.2 Cursor Off , Noblink Cls Config Adc = Single , Prescaler = Auto , Reference = Avcc Dim Adc_read As Byte '-[ODCZYT ADC TYLKO OSIEM BIT]- Function Get_adc(byval Chnl As Byte)as Byte Local Mux_byte As Byte Select Case Chnl Case 0 : Mux_byte = &B01100000 'REF=AVCC,ADLAR=ON Case 1 : Mux_byte = &B01100001 Case 2 : Mux_byte = &B01100010 Case 3 : Mux_byte = &B01100011 Case 4 : Mux_byte = &B01100100 Case 5 : Mux_byte = &B01100101 Case Else Get_adc = 0 Exit Function End Select 'po zmianie kanału lepiej pominąc pierwszy odczyt Admux = Mux_byte 'ustaw kanał Adcsr.adsc = 1 'wystartuj konwersję Bitwait Adcsra.adsc , Reset 'poczekaj na zakonczenie Adcsr.adsc = 1 'wystartuj konwersję Bitwait Adcsra.adsc , Reset 'poczekaj na zakończenie Get_adc = Adch 'odczytaj wynik End Function Do Adc_read = Get_adc(0) Locate 1 , 3 : Lcd Adc_read ; " " Waitms 100 Loop