Jak wspomnieli inni, powinieneś rozważyć użycie filtra IIR (nieskończona odpowiedź impulsowa) zamiast filtra FIR (skończona odpowiedź impulsowa), którego używasz teraz. Jest w tym coś więcej, ale na pierwszy rzut oka filtry FIR są implementowane jako jawne zwoje i filtry IIR z równaniami.
Szczególnym filtrem IIR, którego często używam w mikrokontrolerach, jest jednobiegunowy filtr dolnoprzepustowy. Jest to cyfrowy odpowiednik prostego filtra analogowego RC. W przypadku większości aplikacji będą one miały lepsze właściwości niż używany filtr pudełkowy. Większość zastosowań filtru skrzynkowego, z którym się spotkałem, wynika z tego, że ktoś nie zwraca uwagi w klasie cyfrowego przetwarzania sygnałów, a nie z powodu potrzeby ich szczególnych cech. Jeśli chcesz tylko tłumić wysokie częstotliwości, o których wiesz, że są hałasem, lepiej jest zastosować jednobiegunowy filtr dolnoprzepustowy. Najlepszym sposobem zaimplementowania jednego cyfrowo w mikrokontrolerze jest zwykle:
FILT <- FILT + FF (NOWOŚĆ - FILT)
FILT to trwały stan. Jest to jedyna trwała zmienna potrzebna do obliczenia tego filtra. NOWOŚĆ to nowa wartość aktualizowana przez filtr w tej iteracji. FF to frakcja filtra , która reguluje „ciężkość” filtra. Spójrz na ten algorytm i zobacz, że dla FF = 0 filtr jest nieskończenie ciężki, ponieważ wynik nigdy się nie zmienia. Dla FF = 1 tak naprawdę nie ma żadnego filtru, ponieważ dane wyjściowe są zgodne z danymi wejściowymi. Przydatne wartości są pomiędzy. W małych systemach wybierasz FF na 1/2 Ntak, że mnożenie przez FF można osiągnąć jako przesunięcie w prawo o N bitów. Na przykład, FF może wynosić 1/16 i pomnożyć przez FF, a zatem przesunięcie w prawo o 4 bity. W przeciwnym razie ten filtr wymaga tylko jednego odjęcia i jednego dodania, chociaż liczby zwykle muszą być szersze niż wartość wejściowa (więcej na temat dokładności numerycznej w osobnej sekcji poniżej).
Zwykle wykonuję odczyty A / D znacznie szybciej niż są potrzebne i stosuję dwa z tych filtrów kaskadowo. Jest to cyfrowy odpowiednik dwóch filtrów RC połączonych szeregowo i tłumi się o 12 dB / oktawę powyżej częstotliwości dobiegu. Jednak w przypadku odczytów A / D zwykle lepiej jest spojrzeć na filtr w dziedzinie czasu, biorąc pod uwagę jego odpowiedź krokową. To mówi ci, jak szybko twój system zobaczy zmianę, gdy zmierzy się rzecz, którą mierzysz.
Aby ułatwić projektowanie tych filtrów (co oznacza tylko wybranie FF i podjęcie decyzji, ile z nich kaskadować), używam mojego programu FILTBITS. Podajesz liczbę bitów shift dla każdego FF w kaskadowej serii filtrów, a to oblicza odpowiedź krokową i inne wartości. Właściwie zwykle uruchamiam to za pomocą mojego skryptu opakowującego PLOTFILT. Spowoduje to uruchomienie FILTBITS, który tworzy plik CSV, a następnie drukuje plik CSV. Na przykład oto wynik „PLOTFILT 4 4”:
Dwa parametry PLOTFILT oznaczają, że będą dwa kaskady filtrów typu opisanego powyżej. Wartości 4 wskazują liczbę bitów shift do zrealizowania pomnożenia przez FF. Dwie wartości FF wynoszą zatem w tym przypadku 1/16.
Czerwony ślad jest odpowiedzią krokową jednostki i jest najważniejszą rzeczą do obejrzenia. Na przykład oznacza to, że jeśli dane wejściowe ulegną natychmiastowej zmianie, wydajność połączonego filtra wyrówna się do 90% nowej wartości w 60 iteracjach. Jeśli zależy Ci na 95% czasie rozliczenia, musisz poczekać około 73 iteracji, a na 50% czas rozliczenia tylko 26 iteracji.
Zielony ślad pokazuje moc wyjściową z jednego piku pełnej amplitudy. To daje ci pojęcie o losowym tłumieniu hałasu. Wygląda na to, że żadna pojedyncza próbka nie spowoduje więcej niż 2,5% zmiany wyniku.
Niebieski ślad ma dać subiektywne wrażenie tego, co ten filtr robi z białym szumem. To nie jest rygorystyczny test, ponieważ nie ma gwarancji, jaka dokładnie była zawartość liczb losowych wybranych jako sygnał szumu białego dla tej serii PLOTFILT. To tylko po to, aby dać ci szorstkie odczucie, jak bardzo będzie zgnieciony i jak gładki.
PLOTFILT, może FILTBITS i wiele innych przydatnych rzeczy, szczególnie do tworzenia oprogramowania układowego PIC, jest dostępnych w wersji oprogramowania PIC Development Tools na mojej stronie pobierania oprogramowania .
Dodano informacje o precyzji numerycznej
Widzę z komentarzy i teraz nowej odpowiedzi, że istnieje zainteresowanie omówieniem liczby bitów potrzebnych do wdrożenia tego filtra. Zauważ, że pomnożenie przez FF spowoduje utworzenie nowych bitów Log 2 (FF) poniżej punktu binarnego. W małych systemach FF jest zwykle wybierane jako 1/2 N, tak że mnożenie jest faktycznie realizowane przez prawe przesunięcie N bitów.
FILT jest zatem zwykle stałą liczbą całkowitą. Zauważ, że nie zmienia to żadnej matematyki z punktu widzenia procesora. Na przykład, jeśli filtrujesz 10-bitowe odczyty A / D i N = 4 (FF = 1/16), to potrzebujesz 4 bitów ułamkowych poniżej 10-bitowych liczb całkowitych A / D. Jednym z najbardziej procesorów byłoby 16-bitowe operacje na liczbach całkowitych z powodu 10-bitowych odczytów A / D. W takim przypadku nadal możesz wykonywać dokładnie te same 16-bitowe operacje na liczbach całkowitych, ale zacznij od odczytów A / D przesuniętych o 4 bity. Procesor nie zna różnicy i nie musi. Wykonywanie obliczeń matematycznych dla całych 16-bitowych liczb całkowitych działa niezależnie od tego, czy uważasz, że są to 12,4 stałe liczby całkowite, czy prawdziwe 16-bitowe liczby całkowite (stały punkt 16,0).
Ogólnie rzecz biorąc, musisz dodać N bitów do każdego bieguna filtra, jeśli nie chcesz dodawać szumu ze względu na reprezentację numeryczną. W powyższym przykładzie drugi filtr dwóch musiałby mieć 10 + 4 + 4 = 18 bitów, aby nie stracić informacji. W praktyce na maszynie 8-bitowej oznacza to, że używasz 24-bitowych wartości. Technicznie tylko drugi z dwóch biegunów potrzebowałby szerszej wartości, ale dla uproszczenia oprogramowania zwykle używam tej samej reprezentacji, a tym samym tego samego kodu, dla wszystkich biegunów filtra.
Zwykle piszę podprogram lub makro, aby wykonać jedną operację na filtrze, a następnie stosuję go do każdego z biegunów. To, czy podprogram lub makro zależy od tego, czy cykle czy pamięć programu są ważniejsze w tym konkretnym projekcie. Tak czy inaczej, używam jakiegoś stanu scratch, aby przekazać NEW do podprogramu / makra, które aktualizuje FILT, ale także ładuje to do tego samego stanu scratch NEW, w którym był. Ułatwia to stosowanie wielu biegunów, ponieważ zaktualizowane FILT jednego bieguna jest NOWOŚĆ następnego. Kiedy podprogram jest pożyteczny, wskazany jest wskaźnik FILT po drodze, który jest aktualizowany do zaraz po FILT po wyjściu. W ten sposób podprogram automatycznie działa na kolejnych filtrach w pamięci, jeśli wywoływany jest wiele razy. Z makrem nie potrzebujesz wskaźnika, ponieważ podajesz adres, aby operować na każdej iteracji.
Przykłady kodu
Oto przykład makra opisanego powyżej dla PIC 18:
////////////////////////////////////////////////// //////////////////////////////
//
// Filtr FILTRA Makro
//
// Zaktualizuj jeden biegun filtra o nową wartość w NEWVAL. NEWVAL został zaktualizowany do
// zawiera nową przefiltrowaną wartość.
//
// FILT to nazwa zmiennej stanu filtru. Przyjmuje się, że ma 24 bity
// szeroki i w lokalnym banku.
//
// Wzór na aktualizację filtra to:
//
// FILT <- FILT + FF (NEWVAL - FILT)
//
// Mnożenie przez FF odbywa się poprzez prawe przesunięcie bitów FILTBITS.
//
/ filtr makr
/pisać
dbankif lbankadr
movf [arg 1] +0, w; NEWVAL <- NEWVAL - FILT
subwf newval + 0
movf [arg 1] +1, w
subwfb newval + 1
movf [arg 1] +2, w
subwfb newval + 2
/pisać
/ loop n filtbits; raz na każdy bit, aby przesunąć NEWVAL w prawo
rlcf newval + 2, w; przesuń NEWVAL w prawo o jeden bit
rrcf newval + 2
rrcf newval + 1
rrcf newval + 0
/ endloop
/pisać
movf newval + 0, w; dodaj przesuniętą wartość do filtra i zapisz w NEWVAL
addwf [arg 1] +0, w
movwf [arg 1] +0
movwf newval + 0
movf newval + 1, w
addwfc [arg 1] +1, w
movwf [arg 1] +1
movwf newval + 1
movf newval + 2, w
addwfc [arg 1] +2, w
movwf [arg 1] +2
movwf newval + 2
/ endmac
A oto podobne makro dla PIC 24 lub dsPIC 30 lub 33:
////////////////////////////////////////////////// //////////////////////////////
//
// Makro FILTR ffbits
//
// Zaktualizuj stan jednego filtra dolnoprzepustowego. Nowa wartość wejściowa jest w W1: W0
// a stan filtra, który ma zostać zaktualizowany, wskazuje W2.
//
// Zaktualizowana wartość filtru zostanie również zwrócona w W1: W0 i W2 wskaże
// do pierwszej pamięci po stanie filtru. To makro może zatem być
// wywoływane kolejno w celu aktualizacji serii kaskadowych filtrów dolnoprzepustowych.
//
// Formuła filtra to:
//
// FILT <- FILT + FF (NOWOŚĆ - FILT)
//
// gdzie mnożenie przez FF jest wykonywane przez arytmetyczne przesunięcie w prawo o
// FFBITS.
//
// OSTRZEŻENIE: W3 jest zniszczony.
//
/ filtr makr
/ var new ffbits integer = [arg 1]; pobierz liczbę bitów do przesunięcia
/pisać
/ write "; Wykonaj jednobiegunowe filtrowanie dolnoprzepustowe, shift bits =" ffbits
/pisać " ;"
sub w0, [w2 ++], w0; NOWOŚĆ - FILT -> W1: W0
podpunkt w1, [w2--], w1
lsr w0, # [v ffbits], w0; przesuń wynik w W1: W0 w prawo
sl w1, # [- 16 ffbits], w3
lub w0, w3, w0
asr w1, # [v ffbits], w1
dodaj w0, [w2 ++], w0; dodaj FILT, aby uzyskać końcowy wynik w W1: W0
addc w1, [w2--], w1
mov w0, [w2 ++]; zapisz wynik do stanu filtra, wskaźnik wyprzedzenia
mov w1, [w2 ++]
/pisać
/ endmac
Oba te przykłady są zaimplementowane jako makra przy użyciu mojego preprocesora asemblera PIC , który jest bardziej wydajny niż którykolwiek z wbudowanych narzędzi makr.