• Email
  • Forum

Uśrednianie wyników ADC

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.

Zwykła średnia

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

Średnia ciągniona

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

Inne rozwiązanie ;)

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.


FILM MOŻNA ZOBACZYĆ NA FORUM


Mediana

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)

Funkcja odczytu z ADC tylko ośmiu bitów czyli bit ADLAR

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
Email

Jeśli mogę w czymś pomóc, napisz.