[BASCOM] Uśrednianie wyników ADC

Proste przykłady do częstych pytań.
ODPOWIEDZ
Awatar użytkownika
niveasoft
Posty: 1207
Rejestracja: 17 sie 2015, 12:13
Kontakt:

[BASCOM] Uśrednianie wyników ADC

Post autor: niveasoft » 20 lut 2018, 9:18

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 ;)
  1. $regfile = "m8def.dat"
  2. $crystal = 8000000
  3. $hwstack = 40
  4. $swstack = 16
  5. $framesize = 32
  6.  
  7. $sim
  8. 'OZNACZA PRZYGOTOWANIE KODU DO SYMULACJI - USUNĄĆ DLA PRAWDZIWEGO MIKROKONTROLERA
  9.  
  10. Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2
  11. Config Lcd = 16x2
  12. Deflcdchar 0 , 32 , 32 , 14 , 17 , 31 , 16 , 14 , 1
  13. Deflcdchar 2 , 32 , 32 , 14 , 1 , 15 , 17 , 15 , 1
  14. Cursor Off
  15. Cls
  16.  
  17. Config Adc = Single , Prescaler = Auto , Reference = Avcc
  18.  
  19. Const Ilosc_probek = 50
  20.  
  21. Dim Adc_read As Word , Idx As Byte
  22.  
  23.  
  24. Do
  25.  
  26.  Adc_read = 0
  27.  
  28.   For Idx = 1 To 64
  29.    Adc_read = Adc_read + Getadc(0)
  30.   Next
  31.    Shift Adc_read , Right , 6
  32.  
  33.    Locate 1 , 1 : Lcd Adc_read ; "   "
  34.  
  35. 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 (tutaj 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.
  1. $regfile = "m8def.dat"
  2. $crystal = 8000000
  3. $hwstack = 40
  4. $swstack = 16
  5. $framesize = 32
  6.  
  7. $sim
  8. 'OZNACZA PRZYGOTOWANIE KODU DO SYMULACJI - USUNĄĆ DLA PRAWDZIWEGO MIKROKONTROLERA
  9.  
  10. Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2
  11. Config Lcd = 16x2
  12. Deflcdchar 0 , 32 , 32 , 14 , 17 , 31 , 16 , 14 , 1
  13. Deflcdchar 2 , 32 , 32 , 14 , 1 , 15 , 17 , 15 , 1
  14. Cursor Off
  15. Cls
  16.  
  17. Config Adc = Single , Prescaler = Auto , Reference = Avcc
  18.  
  19. Const Ilosc_probek = 50
  20.  
  21. Dim Tablica(ilosc_probek) As Word
  22. Dim Idx As Byte , Iteration As Byte
  23. Dim Sum As Dword , Wynik As Word
  24.  
  25.  
  26.  
  27. Do
  28.  
  29.  Incr Idx : If Idx > Ilosc_probek Then Idx = 1
  30.  
  31.  Tablica(idx) = Getadc(0)
  32.  
  33.  Sum = 0
  34.  For Iteration = 1 To Ilosc_probek
  35.   Sum = Sum + Tablica(iteration)
  36.  Next
  37.  
  38.  Sum = Sum / Ilosc_probek
  39.  Wynik = Sum
  40.  
  41.   Locate 1 , 1 : Lcd Wynik ; "   "
  42.  
  43. 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).
  1. $regfile = "m8def.dat"
  2. $crystal = 8000000
  3. $hwstack = 40
  4. $swstack = 16
  5. $framesize = 32
  6.  
  7. $sim
  8. 'OZNACZA PRZYGOTOWANIE KODU DO SYMULACJI - USUNĄĆ DLA PRAWDZIWEGO MIKROKONTROLERA
  9.  
  10. Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 , Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2
  11. Config Lcd = 16x2
  12. Deflcdchar 0 , 32 , 32 , 14 , 17 , 31 , 16 , 14 , 1
  13. Deflcdchar 2 , 32 , 32 , 14 , 1 , 15 , 17 , 15 , 1
  14. Cursor Off
  15. Cls
  16.  
  17. Config Adc = Single , Prescaler = Auto , Reference = Avcc
  18.  
  19.  
  20. Dim Adc_read As Word
  21. Dim Suma As Dword
  22. Dim Wynik As Word
  23. Dim Help As Dword
  24.  
  25. Do
  26.  
  27.       Adc_read = Getadc(0)
  28.        Help = Suma
  29.        Shift Help , Right , 3
  30.        Suma = Suma - Help
  31.        Suma = Suma + Adc_read
  32.        Help = Suma
  33.        Shift Help , Right , 3
  34.        Wynik = Help
  35.  
  36.        Locate 1 , 1 : Lcd Wynik ; "    "
  37.  
  38. Loop
  39. 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.

[media]https://drive.google.com/file/d/1-R9P4I ... sp=sharing[/media]

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
  1. Const Ilosc_probek_adc = 5
  2. Const Mediana_adc =(ilosc_probek_adc + 1) / 2
  3. Dim Adcy(ilosc_probek_adc) As Word
  4.  
  5. N = Ilosc_probek_adc : Adc_sum = 0
  6.       Do
  7.         Adcy(n) = Getadc(adca , 3 )
  8.  
  9.        Decr N
  10.       Loop Until N = 0
  11.       Sort Adcy(1)
  12.       Adc_result = Adcy(mediana_adc)
Awatar użytkownika
niveasoft
Posty: 1207
Rejestracja: 17 sie 2015, 12:13
Kontakt:

Re: [BASCOM] Uśrednianie wyników ADC

Post autor: niveasoft » 12 maja 2019, 9:29

Dominata
Teraz pozwolę sobie na żarcik.
Na innym forum spytano mnie czy trudno jest wyznaczyć wartość najczęściej występującą w tablicy odczytów ADC. Nie jest to chyba prawdziwa dominata w znaczeniu tego słowa z Wikipedii, ale postanowiłem napisać funkcję która to obliczy. Żeby jednak nie dawać wszystkiego na tacy zakodowałem ją moim nowym gadgetem w Bascom mhihi :D
Taki zakodowany program mogą skompilować nawet Ci użytkownicy Bascom którzy nie mają włączonej funkcji kryptograficznej więc po skopiowaniu ta funkcją jest całkowicie funkcjonalna a jedynie nie pokazuje sposobu działania.
  1. $regfile = "m328pdef.dat"
  2. $crystal = 16000000
  3. $hwstack = 64
  4. $swstack = 32
  5. $framesize = 32
  6. $baud = 115200
  7. $sim
  8.  
  9. Config Submode = New
  10.  
  11. Const Samples = 20
  12.  
  13. Dim Adc_readings(samples) As Word , Result As Word
  14.  
  15. 'wartości przykładowe
  16. Adc_readings(1) = 20
  17. Adc_readings(2) = 512                                       'x2
  18. Adc_readings(3) = 600                                       'x3
  19. Adc_readings(4) = 330
  20. Adc_readings(5) = 470
  21. Adc_readings(6) = 1023
  22. Adc_readings(7) = 800
  23. Adc_readings(8) = 200
  24. Adc_readings(9) = 512                                       'x2
  25. Adc_readings(10) = 220
  26. Adc_readings(11) = 300
  27. Adc_readings(12) = 1000
  28. Adc_readings(13) = 55
  29. Adc_readings(14) = 115
  30. Adc_readings(15) = 900
  31. Adc_readings(16) = 600                                      'x3
  32. Adc_readings(17) = 45
  33. Adc_readings(18) = 12
  34. Adc_readings(19) = 1001
  35. Adc_readings(20) = 600                                      'x3
  36.  
  37. Function Get_dominate(byref Values_array() As Word , Byval Tab_len As Byte) As Word
  38.  
  39. $crypt 8574e1bdc1875dbee0ab21daab53ace2900ac2ae786f56722e5156c8fb629faa69ae69baa1a9fc914aefb63f23f16a0bc13dad0a6891e0a4e046acf2992f3790c543b526151e840abfb8ed857ea79d40517f1e552cab6e7bb35ac1cfdbb252bc
  40. $crypt D9395592cd6f010b1b64525f7a4a6a39900ac2ae786f56722e5156c8fb629faa69ae69baa1a9fc914aefb63f23f16a0b14c860eac23aac44434093cacc386b1738d600e836b4926da2b342d77f1e989789c339a98f8482b5fd5195d0205b6e4567cd4cef5a60efb578b072be7577be7b
  41. $crypt 77a9f52e7833aca666579371a4f2a379ecbbbf6c02404c56ddeb7358b22523b4
  42. $crypt 2cc282243133703231c4cbaa51ebcc50ecbbbf6c02404c56ddeb7358b22523b4
  43. $crypt 2631d11658cd9fa39eb14c49211e8d2b0eed7886e739db5ec2ab1d214b10d03a
  44.  
  45. $crypt A054c7fcfc8163aff4352e352b6572ff246a33602a2b9d9a48472096d20f40aa69ae69baa1a9fc914aefb63f23f16a0b1db0abe50156ce2630e44388a011529c6c876d1d24b43a429492e072c01df26ec5c3e87bbb60df08275a7334c2c32b0b6fcdbb0f3c51c9f6a00ec54b3bacffdac655a1004dd872e90f31f81d8e23213c
  46.  
  47. $crypt 080fd66b2b6714381eabea49d31eed9f742054403f98c75f69dee57156dac11669ae69baa1a9fc914aefb63f23f16a0bf18526741fa8ee9f0ae0a51ef4b2601f0333713e427b1853cd2c14753bd161e81ea12a9dd2189a97f072e6753a3007fa2498ce979fb3b7979d9658f9d946d0ef
  48. $crypt 73caf5f47612bbd515eca73be7eb76d269ae69baa1a9fc914aefb63f23f16a0b69ae69baa1a9fc914aefb63f23f16a0be9976b3fff1adc4c23f13351ed3be26b85ec4c3731238d46b1434bc52c4188fe316b9a68632a15cfcd44a4fbe2709087
  49. $crypt A765f47724fe2a609969f62d9b842449
  50.  
  51. $crypt B71b90a189a64e8a5231dbf73964badc0bb36a5a4565e635289fe9a57db12a7d
  52. $crypt C3fdc5bb8bd28208c4e0c0147d2092120df89d1899fa1346109597103f509f3d9c5607af0dbbde40546b7777a8b507b5
  53. $crypt 24a2e32a2c876c8fdca909299ac46e54
  54. $crypt E95c33f64dde93f584b17118dc698e674f697c8a08806eebbfc03016b55a6188
  55. $crypt 1ee8eedaade66ed5f57acb747f2f4bb3182f358164718cc113b0f8881a6a4f1e
  56. $crypt 97a16d1a13fe8b87659b1e4a71baaa87b1b1c017267acb2423c18c5992e4ee38
  57. $crypt 43bf69d07f974701614e4f9389fd6a0a
  58. $crypt D2e00d35f106da4b884fa3ae02de9eaf
  59. $crypt C1dcde6e5b73c341db2559581b19fa98
  60. $crypt Db854a6a4f46718154523bb227f0cc79996c9ccae63dad18654cd62cbf5f2230
  61. $crypt C946085ca0fbafab97e5b9dc376f75a2
  62.  
  63. $crypt A429f00ebadf4eaa43890c874f90d692
  64. $crypt 483a93796a653a949535c06b20b4a87e35f7005a3456065074de46ffd600a1f9
  65.  
  66. End Function
  67.  
  68. Result = Get_dominate(adc_readings(1) , Samples)
  69. Print "Wartośc dominująca to " ; Result
  70.  
  71. End
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Awatar użytkownika
pimowo
Posty: 392
Rejestracja: 28 maja 2016, 10:07

Re: [BASCOM] Uśrednianie wyników ADC

Post autor: pimowo » 16 maja 2019, 19:39

Tak sobie poszukuję sposobu na szybkie "czytanie" ADC, które odrzuci jakieś zakłócenia czy błędne odczyty i mam... jak zawsze u Bartka można wszytko znaleźć :)
Mam pytanie do kodu Mediana, czy to jest kompletny, działający kod?
  1. Const Ilosc_probek_adc = 5
  2. Const Mediana_adc =(ilosc_probek_adc + 1) / 2
  3. Dim Adcy(ilosc_probek_adc) As Word
  4.  
  5. N = Ilosc_probek_adc : Adc_sum = 0
  6.       Do
  7.         Adcy(n) = Getadc(adca , 3 )
  8.  
  9.        Decr N
  10.       Loop Until N = 0
  11.       Sort Adcy(1)
  12.       Adc_result = Adcy(mediana_adc)
Nie mam jak tego sprawdzić bo jestem daleko od domu :(
Awatar użytkownika
niveasoft
Posty: 1207
Rejestracja: 17 sie 2015, 12:13
Kontakt:

Re: [BASCOM] Uśrednianie wyników ADC

Post autor: niveasoft » 16 maja 2019, 20:00

Tak, to chyba wszystko. To taka najprostsza mediana. W folderze Sampli Bascom`a jest taka jakaś bardziej skomplikowana z kilku kanałów ADC chyba, wyniki poparowane w gupy itd.. a tu masz np. 5 próbek i ta druga Const to jest po to by obliczyć środek z ilości próbek. Jak masz 5 próbek to środek ustawi na 3. Czyli posortuje wyniki od najmniejszego do największego i weźmie środkowy.

Tyle, że jak zerkam to to jest chyba kod na Xmege. Do zwykłego AVR zamieniasz Te Getadc(adca,3) na zwykłe Getadc(3) czy co tam mierzysz ;)
Awatar użytkownika
pimowo
Posty: 392
Rejestracja: 28 maja 2016, 10:07

Re: [BASCOM] Uśrednianie wyników ADC

Post autor: pimowo » 16 maja 2019, 20:17

Dzięki. Coś mi właśnie nie pasowało to "adca" ;)
Chcę robić pomiar pH wody w akwarium. Wcześniej próbowałem z uśrednianiem to mi wszytko "tańczyło" i zacząłem poszukiwać innych sposobów na pomiar.
W sobotę dam znać czy to zadziałało ;)
Awatar użytkownika
Oldman
Posty: 61
Rejestracja: 19 cze 2019, 11:15

Re: [BASCOM] Uśrednianie wyników ADC

Post autor: Oldman » 23 lip 2020, 19:42

Dzięki Panie Bartku za ten wpis. Rozwiązał mój problem ze sterowaniem pompy ogrodowej a nazwa lekarstwa to "Mediana" :)
Może komuś się przyda ta funkcja więc zamieszczam krótki opis. Zrobiłem sobie zbiornik wody deszczowej ze starych, nieużywanych komór szamba. Do podlewania służy pompa ogrodowa podwieszona pod pokrywą zbiornika. Poziom wody mierzę wodoszczelną czujką ultradźwiękową (też wisi pod pokrywą) a wynik wyświetlam na Oledzie. Całością zarządza Arduino Nano. Żeby nie przelało zbiornika to zrobiłem automatyczne załączanie pompy. Załącza się przy 175cm a wyłącza przy 173cm. I tu zaczęły się schody bo każde załączenie lub wyłączenie pompy zakłócało wynik a więc i pracę pompy.Nie pomogły cewki, kondensatory, ferryty. Dopiero mediana zadziałała.
Zamieszczam tu fragmenty programu z pomiarem poziomu wody i zastosowaniem funkcji Sort.
  1. Dim Calkowite As Byte
  2. Dim Odleglosc As Single                                     ' Zmienna do obliczenia poziomu wody
  3. Dim Mediana(5) As Word                                   ' tablica do zapisania pięciu pomiarów
  4. Dim Wynik As Byte
  5. Dim N As Byte
  6.  
  7.  
  8. Const Glebokosc = 200                                       'odległość czujnika od dna w centymetrach żeby wyliczyć poziom wody
  9. Const Gornystan = 175                                       'poziom przy którym WŁĄCZA się automatyczne  pompowanie
  10. Const Dolnystan = 173                                       'poziom przy którym WYŁĄCZA się automatyczne  pompowanie
  11.  
  12. N = 5                                                              
  13.  
  14.  Config Watchdog = 512
  15.  Start Watchdog
  16.  
  17. Do
  18.  Reset Watchdog
  19. Loop
  20. End
  21.  
  22. Echopomiar:                                                 'Przerwanie rozpoczyna się, gdy nastąpi zmiana sygnału na  pinie echo z czujki
  23.   If Pind.2 = 1 Then                                        'Gdy pin zmienia się na wysoki, Timer1 zaczyna mierzyć czas
  24.       Timer1 = 0                                            
  25.          Start Timer1                                      
  26.    Else                                                     'jeśli poziom zmieni się na niski, pomiar jest zakończony
  27.        Odleglosc = Timer1                                   'Zapisz wartość w zmiennej Odleglosc
  28.          'wzór: s = 344m/s * ti / 2 czyli 344*0.0000005/2= 0,000086
  29.        Odleglosc = Odleglosc * 86
  30.        Odleglosc = Odleglosc / 10000                        ' wynik w cm
  31.        Odleglosc = Glebokosc - Odleglosc                    'tu obliczony jest poziom wody od dna
  32.  
  33.        Calkowite = Round(odleglosc)                         'zaokrąglenie do  liczb całkowitych
  34.        Mediana(n) = Calkowite
  35.        Decr N
  36.        If N = 0 Then
  37.             Sort Mediana(1)                                       ' poukładanie pomiarów wg wartości
  38.             Wynik = Mediana(3)                                ' wybranie środkowego pomiaru
  39.             N = 5
  40.                  If Wynik => Gornystan Then                        'Jeżeli poziom wody osiągnie zadany stan górny
  41.                              Portb.5 = 0                                  ' to załączy się przekaźnik sterujący pompą
  42.                  Elseif Wynik <= Dolnystan Then                  'jak pompa usunie nadmiar wody
  43.                              Portb.5 = 1                                  'to zostanie wyłączona
  44.                 End If
  45.        End If
ODPOWIEDZ