Projekt UCYF – FILTR CYFROWY

Transkrypt

Projekt UCYF – FILTR CYFROWY
Projekt UCYF – FILTR CYFROWY
Prowadzący
dr inż. Paweł Tomaszewicz
Autorzy
Piotr Kowalski - 4T2
Jakub Jachimowicz - 4T3
Data
14 stycznia 2005r.
1. Wprowadzenie i założenia wstępne:
Przedmiotem naszych działań było zaprojektowanie w języku AHDL filtru cyfrowego czystego
formatu dźwięku (RAW) oraz zademonstrowanie jego działania poprzez uruchomienie gotowego
projektu w laboratorium na płytce firmy ALTERA.
Wstępnie zdecydowaliśmy się na realizację układu, który będzie tłumił częstotliwości powyżej
2[kHz]. Jest to o tyle istotne, iż wycięcie takiego pasma spowoduje zauważalne zmiany w odsłuchu
przefiltrowanego pliku. Jakkolwiek dyskusyjna jest już tutaj kwestia, czy takie pasmo przepustowe ma
jakiekolwiek zastosowanie praktyczne. W konfrontacji z pasmem telefonicznym ( 300[Hz] – 3,4[kHz]),
możemy liczyć na otrzymanie gorszej jakości dzwięku z uwagi na szerokość pasma. Fakt, iż ucho
ludzkie najlepiej reaguje na częstotliwości w otoczeniu 2[kHz], decyduje raczej o tym, że lepszego
kompromisu pomiędzy szerokością pasma i zadowalającą jakością dzwięku osiągnąć się nie da.
Na rysunku obok pokazano wstępny schemat układu, który zamierzaliśmy
zrealizować.
Początkowo zajęliśmy się specyfikacją formatu RAW. Otórz jest to, jak
było już wspominane, tzw. surowy format zapisu. Po wyedytowaniu
przykładowego pliku *.raw, zobaczyliśmy jego strukturę. Są to zwyczajne
wartości HEX kolejnych słów (sampli):
– dla 16-bit STEREO: [L] [L] [R] [R]
– dla 8 bit MONO: [P] [P] [P], gdzie symbole L, R, P stanowią zwykłe liczby
szesnastkowe postaci np: FF, 2B, 4C... Specyfikacja format WAV znajduje
się na stronie:
http://home.elka.pw.edu.pl/~pkowals3/wave.html
Zatem ciąg słów szesnastkowych daje nam informację o wysokości
napięcia poszczególnych próbek, przy czym warto tu zaznaczyć, iż umownie w
plikach komputerowych przyjmuje się wartości dodatnie. Jednak w
rzeczywistości odtwarzacze dzwięku interpretują liczby ze znakiem, gdyż w
przeciwnym razie przekształcenie napięcia z dodatnich próbek spowodowałoby
spalenie głośnika po podaniu ciągu próbek tego samego znaku. Membrana
głośnika wychyla się bowiem właśnie odpowiednio proporcjonalnie co do wartości prostego sample'a –
tak powstają drgania membrany.
Opis ewolucji układu jak i końcowy rezultat naszych zmagań przedstawiamy w dalszej części
niniejszej specyfikacji.
Dlaczego wybraliśmy właśnie filtr SOI?
Początkowe, przeszukiwania Internetu w celu zapoznania się z tematem projektu dawały różne
rezultaty. Stopniowo jednak spośród możliwych realizacji filtr FIR (Finite Impulse Response) okazał
się być właściwy do naszych celów, choć rozwiązaniem idealnym nie jest. Trudniejszy w
implementacji filtr NOI – IIR (Infinitive Impulse Response), jest jednak mniej złożony, gdyż realizacja
niższego stopnia daje lepsze rezultaty niż dużo bardziej złożony (wyższego rzędu) filtr FIR. W wyniku
zastosowania pętli zwrotnej, filtr ten jakby sam się poprawia w nieskończonej liczbie kroków, zatem
nie potrzebuje wielu rzędów, aby uzyskać żądaną transmitancję.
Jednym z zamysłów była również implementacja Szybkiej Transfromaty Fouriera (FFT), ale
złożoność problemu nas przerosła. Wybraliśmy zatem filtr SOI rzędu szóstego, gdyż uzyskana
charakterystyka (patrz dalej) jest zadowalająco dokładna i przy niewielkiej powierzchni układu uda
nam się z pewnością uzyskać dobre wyniki.
Na wejście filtru podawać będziemy plik MONO 8-bits z pewną częstotliwością próbkowania.
Problemy i trudności realizacyjne:
Akumulacja:
Kwestia zasugerowana przez prowadzącego. Dotyczy przypadku, kiedy po wymnożeniu i
zsumowaniu poszczególnych składowych, otrzymujemy wartość próbki o ilczbie bitów większej niż
rozmiar wyjściowy. Może to mieć miejsce np. podczas przetwarzania częstotliwości, dla których
wartość transmitancji zbliża się do jedynki bądź ją nieznacznie przekracza. W naszym przypadku
sytucja taka grozi nam w okolicach zera częstotliwości.
Siłą rzeczy nie da się zapisać liczby dziesięcio-bitowej na ośmiu bitach, zatem należało
rozwiązać ten problem poprzez stworzenie układu, który nie pozwalał na zwrócenie próbek większych
niż ośmiobitowe. Wszystkie liczby większe od 255 były automatycznie sprowadzane do tej wartości –
maksymalnej, jaką da się zapisać na 8 bitach. (patrz: plik akumulacja.tdf).
Jakkolwiek, sposób wymnażania próbek z uwagi na dokładność (patrz: opis układu) i
zaokrąglania przez ALTERĘ wartości próbek, sprzyja przekraczaniu zakresu choć nie spotkaliśmy się z
taką sytuacja podczas symulacji. Z pewnością dlatego, że próbki nie były zbyt przesterowane, nie miały
wartości bliskich maksymalnej.
Znak ujemnych współczynników:
W celu uproszczenia układu, współcyznniki filtru dobraliśmy jedynie dodatnie. Metodą 'prób i
błędów' symulowaliśmy różne transmitancje tak długo, aż natrafiliśmy na dodatnie mnożniki
poszczególnych gałęzi filtru. Takie podejście upraszcza układ, a przede wszystkim bardziej przemawia
do wyobraźni.
Błędy kompilacji:
Najbardziej problematyczny okazał się układ mnożący, z którym mieliśmy podczas kompilacji
spore kłopoty. Powodem początkowych niepowodzeń był fakt ignorowania przez kompilator rzekomo
nieużywanych wejść układu. W istocie, teoretycznie dane te nie były modyfikowane w ciele modułu,
jednak były nam potrzebne ze względu na ośmiobitowe wejście filtru. Zignorowanie tych wejść
zaburzyłoby komunikację z portem USB, przez który przychodzą paczki 8-botowych danych.
Efektem ubocznym rozwiązania w/w problemu jest dodatkowe wyjście 7-bitowe układu, pod
którego kolejne piny warunkowo podpieliśmy bitowe “1”. Nieco sztuczne i niezbyt eleganckie
rozwiązanie, ale działa. Być może owe wyjście posłuży w przyszłości do rozbudowy układu.
Problem wykorzystania bitów nie wydaje nam się trywialny, gdyż kompilator języka AHDL nie
wykonuje danych sekwencyjnie, instrukcja po instrukcji. Na podstawie kodu buduje on mianowicie
fizyczny układ opisany właśnie w języku AHDL. Podejście typowo programistyczne w tym wypadku
utrudnia wyraźnie uzmysłowienie sobie, że to nie takie proste jak w #C. W języku VHDL byłoby to już
rozwiązane inaczej, gdyż tam instrukcje bloku wykonywane są sekwencyjnie – instrukcja po instrukcji.
2. Zaprojektowanie filtru w programie Matlab:
Przy pomocy funkcji remez modelujemy filtr szóstego rzędu. Wektory wejściowe funkcji remez
to w naszym przypadku:
N = 6 % rząd filtru %
[F] = [0
0.15
[A] = [0.95
0.9
[B] = remez (N,F,A);
0.4
0.2
0.6
0.15
0.9
0.1
1]
0.05]
Wektor [F] opisuje względną częstotliwość, natomiast wektor [A] to ta część amplitudy
przenoszona dla danej składowej. Transmitancja filtru o podanych wyżej zależnościach będzie
następująca:
Transmitancja filtru SOI 6-rzędu
Pasmo 3-dB kształtuje się tu zatem na poziomie 1 [kHz]. Odpowiedź filtru na sygnał o
składowych 1[kHz] oraz 3[kHz] daje odpowiedź postaci: (poniżej widmo amplitudowe)
Zielonym kolorem zaznaczono widmo po przejściu przez filtr.
Wyższa składowa wyraźnie wycięta. Podobny efekt będziemy chcieli uzyskać w naszym
układzie. Funkcja remez daje nam następujące współczynniki:
0.0232
0.1064
0.2010
0.3475
0.2010
0.1064
0.0232
3. Budowa poszczególnych modułów układu:
A) Układ wejściowy:
Składa się z siedmiu rejestrów, z których każdy zawiera w sobie po osiem przerzutników.
Poszczególne rejestry są połączone ze sobą szeregowo i od każdego odchodzi gałąź do układu
mnożącego, skąd wszystkie gałęzie spotkają się w sumatorze, który da końcową próbkę.
Schemat układu wejściowego filtru SOI 6-rzędu.
Powyżej widzimy łańcuszek rejestrów, które przechowują próbki, jakie przyszły z zewnątrz do
układu filtru. Sygnał Enable jest podany do przerzutników jako sygnał zegarowy i wraz z narastającym
zboczem następuje wpis do przerzutnika/rejestru. Po następnym takcie następuje już jednoczesne
przejście sygnału na wyjście oraz wpis do kolejnego w kolejce rejestru. Sygnał przechodzi więc
cyklicznie przez wszystkie rejestry połączone szeregowo, a w tajemnicy opóźnień tkwi serket filtracji.
B) Układ mnożący:
Jest to chyba najciekawszy fragment układu filtru, który zaprojektowaliśmy. W języku AHDL
ciężko jest zrealizować operację mnożenia przez liczbę ułamkową. Nie korzystając z funkcji
bibliotecznej posłużyliśmy się operacją przesuwu w prawo uzyskując dzielenie.
Przy przesunięciu w prawo o 1 bit liczba dzieli się przez 2, gdy przesuniemy o 2 bity w prawo,
to mamy 4 razy mniejszą liczbę itd. Operację takiego dzielenia wykorzystaliśmy do mnożenia przez
liczby nie większe niż jeden.
Dla liczby 8-bitowej maksymalny przesuw w prawo (lub w lewo) to 7 bitów. Daje nam to liczbę
będącą wynikiem mnożenia przez 0.0078125, ponieważ ten ułamek to 1/128 - wynik przesunięcia w
prawo o 7 bitów.
Poprzez sumowanie takich cząstkowych mnożeń będących efektem elementarnych przesunięć,
dostajemy prawie idealnie dopasowane przybliżenie współczynnika filtru.
Przykład:
Chcąc wymnożyć liczbę 240 przez współczynnik 0.3475 postępujemy tak:
– szukamy kombinacji liniowej ułamków ½, ¼,... takiej, aby ich suma była jak najbliższa
współczynnikowi oraz liczba ułamków była najmniejsza (chcemy układ minimalny)
0.3475 da sie rozłożyć na sumę: ¼ + 1/16 +1/32 = 11/32 = 0.3437
Liczba 240 binarnie: 11110000, a mnożenie dokładne daje wynik 83.4
AHDL da po sumie trzech przesunięć wynik: 00111100 + 00001111 + 00000111 = 60 + 15 + 7 = 82.
Błąd jest zatem jednostkowy! Im więcej składników owej kombinacji liniowej, tym wyniki będą
gorsze, ale dla 8 bitów nie będzie już gorzej. Poniżej zamieszczamy fragment kodu:
Strzałkami zaznaczono nadmiarowy sygnał wyjścia, który trzyma fikcyjne sygnały tylko po to,
by program kompilatora nie ignorował wejść.
Układ sumatora oparty jest na sześciu symbolach bibliotecznych add_sub_lpm. Warto jeszcze
wspomnieć, że układ mnożący oddaje o 2 bity więcej niż przyjął i podaje je też na sumator. Powód
nadmiaru opisany był na początku.
Funkcją biblioteczną mógłby być także rejestr, jednak w ramach oszczędności miejsca
wygenerowaliśmy go w następujący sposób:
Kod układu rejestru (z tablicą ośmiu przerzutników)
Na wyjściu sumatora pracuje układ akumulatora, który redukuje 10-bitowe dane do ośmiu bitów.
Kod układu do kontroli nasycenia próbek (8-bitowych danych)
Układ zajął 182 logiczne komórki układu FLEX 10K i nie wniósł bardzo dużych opóźnień.
Na ostatniej stronie układ filtru w całości oraz symulacja filtrowania.
Układ filtru SOI 6-rzędu zrealizwanego w języku AHDL.
Symulacja układu filtru cyfrowego.
4. Programowanie płytki laboratoryjnej:
Po podłączeniu płytki do portu LPT, należy zainstalować ją w systemie jako port gier używając
sterowników z katalogu maxplus2/drivers. Po wybraniu powyższej opcji należy wybrać przycisk
configure, a następnie ustawić jako aktywny programator ByteBlaster i wybrać port LPT. Często
występują problemy z uaktywnieniem portu LPT – jest on niedostępny. Należy wtedy kombinować
włączając w różnej kolejności porty układu płytki laboratoryjnej. Po zaprogramowaniu układu
(wcześniej oczywiście należy wybrać układ identyczny z układem FLEX na płytce),
Assign -> Device
wszystkie 3 zielone diody będą się świecić, a na wyświetlaczach siedmiosegmentowych będą zera.
Program do wymiany danych z płytką wygląda następująco. Napisaliśmy go w C++ używają
środowiska Borland 6 oraz biblioteki FTD2XX.DLL
5. Obsługa portu USB
Pomimo, że główną częścią projektu jest filtr cyfrowy, dolnoprzepustowy zaprojektowany w
systemie Max Puls+2 amerykańskiej firmy Altera, to jednak zawartość jego (filtru) jest dołączona do
pliku uklad.tdf -> patrz załączniki.
Dołączony jest także plik wyświetlacza siedmiosegmentowego, który steruje LEDami na płytce.
Automat zawarty w/w pliku jest dziewięcio-stanowy – bez niego komunikacja przez port USB byłaby
niemożliwa. Najwięcej problemów podczas transmisji sprawił nam właśnie ten układ.
Automat wpadał w nieznany nam stan (inny niż zdefiniowane) i zawieszał swoją pracę – od tej
pory nie dawał znaku życia. Rozwiązanie przyszło w następującej postaci:
6. Literatura, źródła wiedzy oraz pomocne materiały:
1. Synteza układów cyfrowych, Tadeusz Łuba
2. Wprowadzenie do cyfrowego przetwarzania sygnalów, Richard G. Lyons
3. wwwzpt.tele.pw.edu.pl
4. www.scholar.google.com – polecamy świężą wyszukiwarkę dokumentów naukowych
Załączniki do dokumentacji:
raw.zip - program w C# i pliki *.RAW
altera.zip – pliki projektu
soft.zip - program do obslugi wymiany danych przez USB