ZASTOSOWANIA PROCESORÓW SYGNAŁOWYCH

Podobne dokumenty
KONFIGURACJA NOWEGO PROJEKTU W CODE COMPOSER STUDIO

Wprowadzenie do programowania na procesorze sygnałowym

Generowanie sygnałów na DSP

Zastosowania Procesorów Sygnałowych. dr inż. Grzegorz Szwoch p Katedra Systemów Multimedialnych.

Filtry FIR i biblioteka DSPLIB

Przekształcenie Fouriera i splot

1 Podstawy c++ w pigułce.

Podstawy programowania Laboratorium. Ćwiczenie 2 Programowanie strukturalne podstawowe rodzaje instrukcji

Informatyka I : Tworzenie projektu

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

Warsztaty AVR. Instalacja i konfiguracja środowiska Eclipse dla mikrokontrolerów AVR. Dariusz Wika

Adam Korzeniewski p Katedra Systemów Multimedialnych

etrader Pekao Podręcznik użytkownika Strumieniowanie Excel

Adam Korzeniewski p Katedra Systemów Multimedialnych

1 Podstawy c++ w pigułce.

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 8. Karol Tarnowski A-1 p.

Wydział Elektryczny Katedra Telekomunikacji i Aparatury Elektronicznej

I - Microsoft Visual Studio C++

Instrukcja do ćwiczeń nr 4 typy i rodzaje zmiennych w języku C dla AVR, oraz ich deklarowanie, oraz podstawowe operatory

Programowanie w języku Python. Grażyna Koba

LABORATORIUM 3 ALGORYTMY OBLICZENIOWE W ELEKTRONICE I TELEKOMUNIKACJI. Wprowadzenie do środowiska Matlab

1.Wstęp. 2.Generowanie systemu w EDK

ZASTOSOWANIA PROCESORÓW SYGNAŁOWYCH - PROJEKT

Jak napisać program obliczający pola powierzchni różnych figur płaskich?

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Wprowadzenie do środowiska Qt Creator

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Gromadzenie danych. Przybliżony czas ćwiczenia. Wstęp. Przegląd ćwiczenia. Poniższe ćwiczenie ukończysz w czasie 15 minut.

Menu Plik w Edytorze symboli i Edytorze widoku aparatów

LABORATORIUM PROCESORY SYGNAŁOWE W AUTOMATYCE PRZEMYSŁOWEJ. Zasady arytmetyki stałoprzecinkowej oraz operacji arytmetycznych w formatach Q

1. Skopiować naswój komputer: (tymczasowy adres)

1 Moduł Modbus ASCII/RTU 3

TwinCAT 3 konfiguracja i uruchomienie programu w języku ST lokalnie

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Materiały oryginalne: ZAWWW-2st1.2-l11.tresc-1.0kolor.pdf. Materiały poprawione

Rozdział ten zawiera informacje na temat zarządzania Modułem Modbus TCP oraz jego konfiguracji.

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

1 Moduł Modbus ASCII/RTU

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

SPRZĘTOWA REALIZACJA FILTRÓW CYFROWYCH TYPU SOI

Podstawy programowania w języku C i C++

Programowanie w języku C++ Grażyna Koba

Podstawy programowania skrót z wykładów:

PRZEWODNIK PO ETRADER ROZDZIAŁ XII. ALERTY SPIS TREŚCI

Programowanie strukturalne i obiektowe

Lab 9 Podstawy Programowania

Wydział Elektryczny. Katedra Telekomunikacji i Aparatury Elektronicznej. Konstrukcje i Technologie w Aparaturze Elektronicznej.

Podstawy programowania. Wykład: 5. Instrukcje sterujące c.d. Stałe, Typy zmiennych c.d. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Kadry Optivum, Płace Optivum

Wprowadzenie do programowania w języku Visual Basic. Podstawowe instrukcje języka

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

CZĘŚĆ A PIERWSZE KROKI Z KOMPUTEREM

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Konfigurowanie sterownika CX1000 firmy Beckhoff wprowadzenie. 1. Konfiguracja pakietu TwinCAT do współpracy z sterownikiem CX1000

Rejestracja faktury VAT. Instrukcja stanowiskowa

Tworzenie nowego projektu w asemblerze dla mikroprocesora z rodziny 8051

Cyfrowe Przetwarzanie Obrazów i Sygnałów

Instrukcja instalacji i obsługi modemu ED77 pod systemem operacyjnym Windows 98 SE (wydanie drugie)

Spis treści. 1 Moduł Modbus TCP 4

Język C zajęcia nr 11. Funkcje

Ćwiczenia z S Komunikacja S z miernikiem parametrów sieci PAC 3200 za pośrednictwem protokołu Modbus/TCP.

Instalacja programu:

Aplikacja do podpisu cyfrowego npodpis

Pobieranie edytora CodeLite

Pętle i tablice. Spotkanie 3. Pętle: for, while, do while. Tablice. Przykłady

Programowanie niskopoziomowe

I. Program II. Opis głównych funkcji programu... 19

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Wstęp do Programowania, laboratorium 02

INSTRUKCJE WIKAMP Dotyczy wersji systemu z dnia

WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM. NetBeans. Wykonał: Jacek Ventzke informatyka sem.

WOJEWÓDZTWO PODKARPACKIE

Ćwiczenia nr 4. Arkusz kalkulacyjny i programy do obliczeń statystycznych

14. TWORZENIE MAKROPOLECEŃ

Podstawy programowania. Ćwiczenie. Pojęcia bazowe. Języki programowania. Środowisko programowania Visual Studio

Tworzenie prezentacji w MS PowerPoint

METODY KOMPUTEROWE W OBLICZENIACH INŻYNIERSKICH

Konfiguracja pakietu CrossStudio for MSP

Niektóre piny mogą pełnić różne role, zależnie od aktualnej wartości sygnałów sterujących.

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Obliczenia iteracyjne

Dodatki. Dodatek A Octave. Język maszyn

Nr: 15. Tytuł: Kancelaris w systemie Windows 8 i Windows 8.1. Data modyfikacji:

Podstawy Programowania

Site Installer v2.4.xx

Ćwiczenia laboratoryjne. Oprogramowanie i badanie prostych metod sortowania w tablicach

Samodzielnie wykonaj następujące operacje: 13 / 2 = 30 / 5 = 73 / 15 = 15 / 23 = 13 % 2 = 30 % 5 = 73 % 15 = 15 % 23 =

Moduł Handlowo-Magazynowy Przeprowadzanie inwentaryzacji z użyciem kolektorów danych

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

1. Aplikacja LOGO! App do LOGO! 8 i LOGO! 7

Baltie 3. Podręcznik do nauki programowania dla klas I III gimnazjum. Tadeusz Sołtys, Bohumír Soukup

Sposoby tworzenia projektu zawierającego aplet w środowisku NetBeans. Metody zabezpieczenia komputera użytkownika przed działaniem apletu.

METODY KOMPUTEROWE W OBLICZENIACH INŻYNIERSKICH

Konfigurowanie sterownika CX9000 firmy Beckhoff wprowadzenie

Widok programatora PonyProgUSB wersja 1.0 oraz jego elementy przedstawiono na poniższym rysunku.

Transkrypt:

ZASTOSOWANIA PROCESORÓW SYGNAŁOWYCH Materiały pomocnicze do realizacji projektu Opracowanie: dr inż. Grzegorz Szwoch wersja 1.13, 07.05.2016 Gdańsk 2016

SPIS TREŚCI 1 KONFIGURACJA NOWEGO PROJEKTU W CODE COMPOSER STUDIO...2 Instalacja pomocniczych bibliotek...2 Tworzenie nowego projektu...2 Konfigurowanie projektu do kompilacji na DSP 5535...3 Kompilacja projektu...5 Uruchomienie projektu w trybie Debug...5 Debugowanie programu...5 Wyświetlanie zawartości bufora...5 IMPLEMENTACJA ALGORYTMÓW NA DSP...7 Typy liczbowe na procesorze DSP C5535...7 Reprezentacja liczb zmiennoprzecinkowych (Q15)...7 Mnożenie liczb Q15...8 Bufor kołowy...8 Krótko o tablicach i wskaźnikach języka C...9 Korzystanie z biblioteki DSPLIB...9 Przetwarzanie blokowe...11 Instrukcje wewnętrzne DSP...11 Generowanie sygnału sinus...11 Generowanie szumu białego...12 Implementacja filtrów dwukwadratowych IIR...13 Konfiguracja projektu do obliczania FFT...14 Obliczanie częstotliwości przedziału FFT...15

2 KONFIGURACJA NOWEGO PROJEKTU W CODE COMPOSER STUDIO Instalacja pomocniczych bibliotek Pobrać plik ze strony Katedry: http://www.multimed.org/student/zps/zps-workspace.zip Z pobranego archiwum wypakować katalogi (wyłącznie): - common5535 - lib do katalogu Workspace (domyślnie: C:\Users\[username]\workspace_v6_1). Powinien on znajdować się w katalogu nadrzędnym w stosunku do tworzonego projektu. Tworzenie nowego projektu Podłączyć płytkę DSP (duży wtyk USB na płytce) do portu USB w komputerze. Uruchomić Code Composer Studio. Z menu File wybrać New > CCS Project. W polu Target rozwinąć listę z prawej strony i wybrać: TMS320C5535. W polu Connection rozwinąć listę i wybrać: Texas Instruments XDS 100v2 USB Debug Probe. Warto wcisnąć przycisk Verify i upewnić się że płytka odpowiada. W polu Project name wpisać nazwę projektu. W razie potrzeby, podać katalog docelowy (Location). Można pozostawić domyślny typ projektu Empty project (with main.cpp). Kliknąć na Advanced settings i rozwinąć sekcję z opcjami. W linii Linker command file kliknąć Browse i wczytać plik C5535.cmd z katalogu common5535. Kliknąć Finish.

3 Konfigurowanie projektu do kompilacji na DSP 5535 Kliknąć prawym przyciskiem myszy na nazwie projektu, wybrać Properties. Wybrać Build > C5500 Compiler > Processor Options, zmienić memory model z large na huge. Z Advanced options wybrać Runtime Model Options, zmienić Specify type size to hold results of pointer math (--ptrdiff_size) na 32. Wybrać Build > C5500 Compiler > Include Options. W górnym polu (Add dir to #include search path): wcisnąć przycisk z zielonym plusem (Add). W okienku wybrać Browse, znaleźć katalog common5535 i zatwierdzić. Wybrać Build > C5500 Linker > File Search Path. W górnym polu (Include library file or command file as input) dodać (wciskając ikonę z zielonym plusem i wpisując z klawiatury): - common5535.lib - CSLc55x5h.lib - 55xdsph.lib W polu niżej (Add <dir> to library search path) dodać (metodą jak wcześniej) katalogi (rozpakowane z pobranego archiwum): - lib - common5535/debug

W tym momencie projekt jest skonfigurowany do pracy w trybie Debug. Jeżeli jest potrzeba wykorzystania konfiguracji Release, należy wybrać ją z listy Configuration i powtórzyć wszystkie kroki (zamienić nazwę katalogu na common5535/release w poprzednim kroku). Zatwierdzić przyciskiem OK. 4

Kompilacja projektu Upewnić się że projekt jest wybrany (Active) w panelu Project Explorer. Kliknąć przycisk z młotkiem na pasku narzędziowym. Upewnić się że nie ma błędów kompilacji powinien być widoczny komunikat typu 'Finished building target: HelloDSP.out'. Każda zmiana w kodzie wymaga jego ponownego skompilowania. Uruchomienie projektu w trybie Debug Kliknąć ikonę z pluskwą na pasku narzędziowym, albo wcisnąć klawisz F11. Jeżeli projekt uruchomi się pomyślnie, program przełączy się do trybu debugowania. Wykonywanie programu zatrzyma się na punkcie startowym (zwykle nagłówek funkcji main). Jest to widoczne w postaci niebieskiej strzałki na marginesie edytora kodu. Uruchomienie programu nastąpi po wciśnięciu przycisku Resume (żółto-zielona ikona play/pause), lub po naciśnięciu klawisza F8. W tym momencie program powinien działać. Ewentualne błędy zostaną wyświetlone w programie. Wyjście z trybu debugowania przycisk Terminate (ikona z czerwonym kwadratem) lub Control+F2. Uwaga: program na DSP nadal się wykonuje! Debugowanie programu W edytorze można ustawić punkty przerwania (breakpoint) za pomocą dwukrotnego kliknięcia na marginesie z lewej strony edytora. Breakpoint jest zaznaczony niebieską kropką. Breakpointy można też ustawiać w trybie debugowania, nawet w trakcie działania programu! Skrót klawiszowy: Control+Shift+B. Program zatrzyma się po osiągnięciu breakpointa. Przesuwając kursor nad dowolną zmienną w edytorze można odczytać jej wartość. Zakładka Variables w górnej części okna programu pokazuje zdefiniowane zmienne i ich wartości, które można modyfikować. Zaznaczenie wyrażenia w edytorze i wybranie z menu kontekstowego opcji Add watch expression powoduje dodanie wyrażenia do zakładki Expressions. Przycisk Step Into (F5) powoduje przejście do kolejnej instrukcji, a jeżeli bieżącą instrukcją jest wywołanie funkcji, debugger wchodzi do wnętrza tej funkcji. Przycisk Step Over (F6) jw., ale nie wchodzi do wnętrza funkcji. Przycisk Step Return (F7) wychodzi z funkcji, do której weszliśmy przez Step Into. Przycisk Resume (F8) wznawia działanie programu. Wyświetlanie zawartości bufora Załóżmy, że mamy bufor próbek o rozmiarze 2048: int bufor[2048]; 5

Chcemy wykreślić jego zawartość. Zatrzymujemy wykonywanie programu za breakpoincie. W zakładce Variables klikamy prawym przyciskiem myszy na zmiennej bufor i wybieramy Graph. Okno bufora powinno pokazać się na dole okna. Następnie należy otworzyć okno Show The Graph Properties klikając na ikonę paska narzędziowego wykresu i ustawić parametry jak poniżej. Acquisition Buffer Size rozmiar bufora (tu 2400) Dsp Data Type wybrać typ, np. 16 bit signed integer Sampling Rate Hz wpisać częstotliwość próbkowania, np. 48000 Start Address tu powinna być już automatycznie wpisany wskaźnik do bufora Display Data Size należy wpisać rozmiar bufora (2400) lub mniej Time Display Unit ustawić jednostkę osi czasu wg uznania. Bufor zostanie uaktualniony za każdym razem gdy program zatrzyma się na breakpoincie. 6 Można również wyświetlić FFT zawartości bufora. Należy z menu Tools wybrać Graph > FFT Magnitude. W oknie należy ustawić opcje jak powyżej (należy ręcznie wpisać bufor jako Start Address) oraz dodatkowo podać rozmiar FFT (zmieniając FFT Order) i typ okna.

7 IMPLEMENTACJA ALGORYTMÓW NA DSP Typy liczbowe na procesorze DSP C5535 Procesor C5535 jest stałoprzecinkowy, co oznacza że nie posiada typów zmiennoprzecinkowych (float, double) i potrafi jedynie wykonywać operacje na liczbach całkowitych. Poniżej zestawiono obsługiwane typy stałoprzecinkowe i ich rozmiar bitowy: int, short: 16 bitów (2 bajty) long: 32 bity (4 bajty) long long: 40 bitów Każdy typ występuje w dwóch wersjach: signed: ze znakiem (domyślnie) unsigned: bez znaku Zatem typ 16-bitowy bez znaku zapisujemy jako unsigned int. Typy posiadają również synonimy. Np. typ int może być czasami zapisany jako Int16. Biblioteka DSPLIB definiuje synonimy DATA dla signed int, LDATA dla signed long oraz ushort dla unsigned int. Należy dobrać odpowiedni typ do zastosowań, tak aby zapisywana liczba zmieściła się na wybranej liczbie bitów. Nie należy używać dłuższych typów (long) bez potrzeby, ponieważ wykonywanie operacji na DSP może być wolniejsze niż w przypadku liczb typu int. Rzutowanie typów polega na podaniu docelowego typu w nawiasie przed wyrażeniem, np.: long y = (long)x. Jeżeli nowy typ jest większy, konwersja jest bezstratna, ale jeżeli nowy typ jest mniejszy (np. long na int), nadmiarowe bity zostaną obcięte. Programista musi zadbać o to, aby operacja ta nie powodowała błędów. Reprezentacja liczb zmiennoprzecinkowych (Q15) Jak wspomniano wyżej, procesor C5535 nie ma typów do zapisu liczb zmiennoprzecinkowych. Istnieje jednak metoda, która umożliwia zapisywanie liczb zmiennoprzecinkowych z zakresu od 0 do 1 za pomocą liczb typu int. Jest to jednak tylko konwencja, procesor nadal będzie wykonywał operacje tak jakby były to liczby stałoprzecinkowe. Typ int pozwala zapisywać liczby z zakresu od -2 15 do (2 15-1), czyli od -32768 do 32767. Dowolną liczbę z zakresu od -1 (włącznie) do +1 (wyłącznie) można więc przedstawić jako ułamek tego zakresu: q(x) = x * 2 15 = x * 32768 Zapis ten jest nazywany Q15, ponieważ 15 bitów reprezentuje część ułamkową: kolejne bity mają wartości od 2-1 do 2-15, najstarszy bit określa znak. Rozdzielczość takiego zapisu wynosi 2-15 = 0,000030517578125. Należy zwrócić uwagę na to, że nie ma możliwości zapisania liczby 1 (największa możliwa do zapisania liczba wynosi 0,999969482421875. Operacje na liczbach Q15 odbywają się w trybie stałoprzecinkowym, np. dodawanie 0,13 + 0,27: 0,13 0,13 * 32768 = 4260 (konwersja na Q15) 0,27 0,27 * 32768 = 8847 4260 + 8847 = 13107 (dodawanie na DSP zwracany wynik)

13107 / 32768 = 0,40 (w zaokrągleniu konwersja na liczbę zmiennoprzecinkową) 0,13 + 0,27 = 0,40 (a więc dostaliśmy prawidłowy wynik). Uwaga: przy wykonywaniu operacji może wystąpić przepełnienie zakresu (overflow), np. przy dodawaniu 0,80 + 0,21. Programista musi zadbać o to aby przepełnienie nie wystąpiło, np. zapisując cząstkowe wyniki w zmiennej typu long. Mnożenie liczb Q15 W wyniku mnożenia dwóch liczb zapisanych w Q15 dostajemy wynik w postaci Q30 (Q15+15): x * y (x * 2 15 ) * (y * 2 15 ) = x * y * 2 30 Wynik ten wymaga do zapisania więcej niż 16 bitów, a więc typu long. Należy pamiętać o tym, że przynajmniej jedna z mnożonych liczb musi być typu long aby wynik mnożenia również miał typ long. Przykładowo, jeżeli zmienne x, y są typu int, poniższe wyrażenie da błędny wynik: long z = x * y; // źle! Kompilator języka C najpierw obliczy wyrażenie po prawej stronie znaku równości. Oba operandy są typu int, więc wynik wyrażenia będzie miał również typ int. Przypisanie go do zmiennej typu long niczego nie zmieni, wynik może być nieprawidłowy. Przykładowo: 0,7 * 0,8 22938 * 26214 = 601296732 Jeżeli wykonamy mnożenie na liczbach typu int, wynik zostanie obcięty do 16 bitów, czyli do -22282. Dostaniemy więc nieprawidłowy wynik (-0,68). Aby prawidłowo wykonać mnożenie, należy rzutować przynajmniej jeden z argumentów do typu long przed obliczeniem wyrażenia: long z = (long)x * y; // dobrze Wynik mnożenia liczb typu long i int jest zapisywany w większym z typów, czyli long. Dostajemy więc prawidłowy wynik, zapisany w formacie Q30. Aby uzyskać wynik w formacie Q15, należy obciąć 15 młodszych bitów części ułamkowej. Zostanie wtedy jedynie 15 starszych bitów, co odpowiada zapisowi Q15. Wystarczy wykonać przesunięcie bitowe o 15 miejsc w prawo, za pomocą operatora >> (operacja >>15 odpowiada dzieleniu przez 2 15 ). W naszym przypadku: 601296732 >> 15 = 18349 18349 / 32768 = 0,56 Mamy więc prawidłowy wynik. Jeżeli chcemy zapisać wynik w zmiennej typu int, musimy go rzutować do tego typu (upewnijmy się że nie będzie przepełnienia). Zatem następujący idiom służy do mnożenia liczb typu int: int z = (int)(((long)x * y) >> 15); Kompilator dla procesora C55x definiuje zestaw instrukcji wewnętrznych (intrinsics). Do mnożenia liczb typu int można użyć instrukcji _smpy: Bufor kołowy int z = _smpy(x, y); W wielu przypadkach potrzebujemy zapisywać wartości w buforze kołowym. Musimy zadeklarować dwie rzeczy: bufor oraz wskaźnik pozycji zapisu: #define N 2048 int bufor[n]; 8

unsigned int indeks = 0; Dla wygody zapisujemy rozmiar N bufora jako stałą preprocesora. Warto na początku wyzerować zawartość bufora: int i; for (i = 0, i < N; i++) bufor[i] = 0; Po użyciu bufora, należy przesunąć indeks na następną pozycję. Można zrobić to tak: indeks++; if (indeks == N) indeks = 0; Jeżeli indeks przesunął się poza koniec bufora, cofamy go na początkową pozycję. To samo można zapisać krócej za pomocą instrukcji wewnętrznej _circ_incr: indeks = _circ_incr(indeks, N); Krótko o tablicach i wskaźnikach języka C Zmienne definiowane jako int czy long przechowują pojedyncze wartości. Jeżeli chcemy przechowywać wiele wartości w jednej zmiennej (np. bufor próbek), musimy zadeklarować tablicę, podając po nazwie zmiennej, rozmiar tablicy w nawiasach kwadratowych. Rozmiar musi być stałą, nie wolno stosować zmiennych lub wyrażeń. Na przykład tak tworzymy tablicę liczb typu int o rozmiarze 2048: int bufor[2048]; Dowolną wartość z bufora można odczytać podając indeks w nawiasach kwadratowych indeksy są numerowane od zera. Indeksy mogą być stałymi, zmiennymi lub wyrażeniami. Np. trzeci element uzyskamy tak: int trzeci = bufor[2]; Jeżeli deklarujemy stałą tablicę, np. współczynniki filtru, możemy podać jej wartości w nawiasach klamrowych (należy pamiętać o specyfikatorze const): const int wsp_iir[] = {810, 1621, 810, -20965, 7824}; Niektóre funkcje, np. z biblioteki DSPLIB, wymagają podania wskaźnika do zmiennej jako argumentu. Jest to zaznaczone gwiazdką przy argumencie w sygnaturze funkcji, np.: int funkcja(int* bufor); Wskaźnik jest adresem pamięci, pod którym zapisana jest zmienna. W przypadku tablic języka C, nazwa tablicy jest wskaźnikiem do niej. Wystarczy więc podać nazwę tablicy jako argument funkcji: int bufor[2048]; funkcja(bufor); W przypadku zmiennych nie będących tablicami (skalarnych), wskaźnik można pobrać dodając znak & przed nazwą zmiennej, np.: int zmienna; funkcja(&zmienna); Uwaga: typ wskaźnika musi się zgadzać. Wskaźnik typu const int* nie jest tym samym co wskaźnik int*. W razie potrzeby należy rzutować wskaźnik, np.: const int wsp[] = {0, 1, 2, 3}; funkcja((int*)wsp); 9 Korzystanie z biblioteki DSPLIB Biblioteka DSPLIB zawiera typowe algorytmy DSP (filtracja, splot, FFT i inne) zaimplementowane w języku asembler i zoptymalizowane dla danego procesora. Należy z nich korzystać zamiast pisania

własnych procedur, chyba że dana procedura nie spełnia wymagań. We własnym projekcie należy dołączyć plik nagłówkowy dsplib.h oraz użyć przy linkowaniu biblioteki odpowiedniej dla danego procesora i modelu pamięci (np. 55xdsph.lib) te kroki zostały już wykonane w ramach procedury tworzenia nowego projektu opisanej na początku tego dokumentu. Opis wszystkich procedur zawartych w bibliotece można znaleźć w dokumencie TMS320C55x DSP Library Programmer s Reference SPRU422J jest on też dostępny ze strony Katedry. Rozdział 4 zawiera podstawowe informacje na temat biblioteki. Istotne są dwie rzeczy: biblioteka definiuje typy danych: DATA (== signed int, 16 bit) oraz LDATA (== signed long, 32 bit), używane są formaty zapisu liczb: Q15 (DATA) oraz Q31 (LDATA uwaga, nie Q30!) Należy zapoznać się z oznaczeniami zawartymi w tabeli 4.1, są one używane w opisach funkcji. Tabela 4.2 zawiera wykaz wszystkich procedur dostępnych w bibliotece. Jako przykład, weźmy implementację filtru IIR drugiego rzędu (biquad). Według tabeli 4.2, możemy użyć funkcji iircas5 lub iircas51. Weźmy tę drugą. Znajdujemy opis funkcji (najłatwiej użyć indeksu na końcu dokumentu). Mamy następującą deklarację funkcji: ushort oflag = iircas51 (DATA *x, DATA *h, DATA *r, DATA *dbuffer, ushort nbiq, ushort nx) Poniżej mamy opis wszystkich parametrów. W skrócie przedstawia się on następująco. DATA *x wskaźnik do bufora nx próbek sygnału do przetworzenia DATA *h wskaźnik do współczynników filtru (5 współczynników na sekcję) DATA *r wskaźnik do bufora wyjściowego, nx próbek przefiltrowanego sygnału DATA *dbuffer wskaźnik do bufora roboczego, rozmiar (4*nbiq + 1), u nas: 5 ushort nbiq liczba sekcji, u nas: 1 ushort nx liczba próbek, u nas: 1 Załóżmy, że mamy próbkę wejściową zapisaną w zmiennej wejscie, wynik chcemy zapisać w zmiennej wyjscie, współczynniki filtru są zapisane w tablicy wsp_iir. Musimy jeszcze zaalokować bufor roboczy dla filtru (dobrze jest wypełnić go zerami). int wejscie; int wyjscie; const DATA wsp_iir[] = {810, 1621, 810, -20965, 7824}; DATA bufor[5]; Procedurę filtracji wywołujemy następująco: iircas51(&wejscie, (DATA*)wsp_iir, &wyjscie, bufor, 1, 1); Wskaźniki do zmiennych typu int zostały pobrane za pomocą operatora &, a wskaźnik do współczynników filtru został zrzutowany na typ DATA* - zostało to opisane w punkcie Krótko o tablicach i wskaźnikach języka C. Analogicznie można przetworzyć jedną instrukcją cały blok próbek: define N 1024 int wejscie[n]; int wyjscie[n]; const DATA wsp_iir[] = {810, 1621, 810, -20965, 7824}; DATA bufor[5]; //... iircas51(wejscie, (DATA*)wsp_iir, wyjscie, bufor, 1, N); W tym przypadku zmienne wejscie i wyjscie są tablicami, nie trzeba więc używać operatora &. 10

Przetwarzanie blokowe Przetwarzanie próbka po próbce polega na tym, że pobieramy próbkę z wejścia, przetwarzamy ją i natychmiast wysyłamy na wyjście. W niektórych przypadkach musimy najpierw zgromadzić pewną liczbę próbek, np. aby obliczyć FFT. Musimy w tym celu utworzyć dwa bufory kołowe: wejściowy i wyjściowy. Procedura przetwarzania jest następująca. Zapisać nową próbkę w buforze wejściowym. Wysłać na wyjście odpowiednią próbkę z bufora wyjściowego. Sprawdzić czy bufor został zapełniony. Jeśli tak, przetworzyć blok próbek z bufora wejściowego i zapisać wynik w buforze wyjściowym. Procedurę tę można zilustrować poniższym (niepełnym) pseudokodem. // rozmiar buforów define N 1024 int bufor_wej[n], bufor_wyj[n]; unsigned int indeks = 0; // pętla przetwarzania próbek while (1) { // pobranie nowej próbki bufor_wej[indeks] = pobierz_probke(); // wysłanie próbki na wyjście wyslij_probke(bufor_wyj[indeks]); // sprawdzenie czy bufor został zapełniony indeks++; if (indeks == N) { indeks = 0; przetworz_probki(bufor_wej, bufor_wyj); } } Należy zwrócić uwagę, że próbka wysyłana na wyjście i próbka pobrana z wejścia nie pochodzą z tego samego momentu próbka wyjściowa pochodzi z poprzednio przetworzonego bufora. Powoduje to opóźnienie pomiędzy wejściem a wyjściem, zależne od długości bufora. Np. dla bufora o rozmiarze 2048 próbek i częstotliwości próbkowania 48 khz, opóźnienie wyniesie 2048/48000 = ok. 43 ms. Należy starać się aby opóźnienia nie przekraczały 50 ms. Instrukcje wewnętrzne DSP Procesor DSP posiada pewną liczbę tzw. instrukcji wewnętrznych (intrinscis). Są to instrukcje zoptymalizowane do wykonywania operacji specyficznych dla danego procesora. Można ich używać również pisząc kod w języku C. Nazwy instrukcji zaczynają się od podkreślenia (_). We wcześniejszych punktach dokumentu przywołano instrukcje _smpy i _circ_incr. Używanie instrukcji wewnętrznych skraca i upraszcza kod, ale należy dobrze rozumieć jak działa dana instrukcja. Opis instrukcji wewnętrznych zawarty jest w dokumencie TMS320C55x DSP Library Programmer s Reference (SPRU281) w rozdziale 6.5. Istotny fragment tego dokumentu zamieszczono na stronach Katedry. Generowanie sygnału sinus Aby wygenerować ciągły sygnał sinusoidalny na DSP, można posłużyć się metodą licznika fazowego. Faza sygnału zmienia się w sposób ciągły, w zakresie od -π/2 do π/2, z zawinięciem. Na stałoprzecinkowym procesorze DSP można reprezentować fazę za pomocą liczb Q15: -32768 oznacza -π/2, 32767 oznacza prawie π/2. Faza będzie samodzielnie zawijała się gdy wystąpi przepełnienie zakresu. 11

Dla kolejnych próbek generowanego sygnału należy zwiększać wartość licznika fazowego o kąt odpowiadający częstotliwości sygnału. Załóżmy, że częstotliwość f = 1 Hz, co oznacza, że w ciągu jednej sekundy nastąpi pełne przejście całego zakresu fazy. Jednej sekundzie odpowiada liczba próbek równa częstotliwości próbkowania. Przyjmijmy fs = 48000 Hz. Pełen zakres fazy to 2 16 wartości (zapisujemy je na 16 bitach). Zatem zwiększeniu czasu o jedną próbkę (1/fs sekundy) odpowiada zwiększenie fazy o: d = 2 16 / fs = 65536 / 48000 Stosunek ten można zapisać inaczej: d = 2 * 32768 / 48000 = 2 * 22368 / 32768 = (2 * 22368) >> 15 = 22368 >> 14 Łatwej jest wykonać przesunięcie o 14 bitów w prawo niż dzielenie przez 48000. Dla dowolnej częstotliwości f wyrażonej w Hz, krok fazowy będzie równy: d = (f * 22368) >> 14 Np. dla częstotliwości f = 1234 Hz, krok wyniesie (po zaokrągleniu) 1685. Zatem generowanie sinusa można zrealizować następująco. Zainicjalizować zmienną licznika fazowego (int) na 0. Obliczyć stały krok fazowy (d). Dla kolejnych próbek: obliczyć wartość sinusa dla aktualnej fazy, po czym zwiększyć stan licznika fazowego o d. Do obliczenia wartości sinusa należy użyć funkcji sine z biblioteki DSPLIB. Poniżej przykład. DATA faza = 0; const int d = 1685; DATA sinus; while (1) { sine(&faza, &sinus, 1); faza += d; // sinus zawiera obliczona probke sygnalu } Wywoływanie funkcji sine dla każdej próbki jest czasochłonne. Dobrym pomysłem jest wygenerowanie dłuższego bufora próbek jednym wywołaniem funkcji sine, a następnie pobieranie obliczonych wartości z tego bufora i ponowne napełnienie bufora po jego zużyciu. W takim przypadku należy obliczyć osobne wartości fazy dla każdego elementu i zapisać je w osobnym buforze. int faza = 0; const int d = 1685; #define N 1024 DATA bufor_faza[n]; DATA bufor_sinus[n]; for (int i = 0; i < N; i++) { bufor_faza[i] = faza; faza += d; } sine(bufor_faza, bufor_sinus, N); Generowanie szumu białego Aby wygenerować szum biały, można posłużyć się funkcją rand16 z biblioteki DSPLIB. Przed użyciem generatora liczb pseudolosowych należy zainicjalizować go (tylko raz) za pomocą funkcji rand16init. Poniższa procedura generuje w pętli jedną próbkę szumu. 12

DATA szum; rand16init(); while (1) { rand16(&szum, 1); // wykorzystanie probki szum... } Tak jak poprzednio, lepiej jest wygenerować dłuższy bufor próbek szumu naraz. DATA szum[1024]; rand16init(); rand16(szum, 1024); 13 Implementacja filtrów dwukwadratowych IIR Filtry IIR implementuje się na DSP w formie złożonej z sekcji dwukwadratowych. Transmitancja sekcji dwukwadratowej (SOS ang. second order section) jest opisana wzorem: H b + b z + b z 1 2 0 1 2 ( z) = 1 2 1+ a1z + a2z Filtr jest implementowany za pomocą równania różnicowego: y ( n) = b0 x( n) + b1 x( n 1) + b2 x( n 2) a1 y( n 1) a2 y( n 2) Program fdatool z Matlaba zapisuje obliczone współczynniki w dwóch macierzach, domyślnie nazywanych SOS i Gain. Macierz SOS zawiera współczynniki sekcji. Każdy rząd to współczynniki jednej sekcji, w kolejności: b0, b1, b2, a0, a1, a2. Kolejne wiersze zawierają współczynniki kolejnych sekcji. Macierz Gain zawiera wzmocnienia sekcji. Współczynnik o indeksie i skaluje sygnał przed podaniem go na wejście i-tej sekcji. Ostatni współczynnik skaluje sygnał po wyjściu z filtru. Aby filtr działał prawidłowo, konieczne jest spełnienie następujących warunków: wzmocnienie całego filtru nie może być większe od 1, wzmocnienie żadnej z sekcji nie może być większe od 1. Przekształcenie współczynników do formy, w której te warunki zostaną spełnione, jest skomplikowane. Przykładowy opis, dla filtru w strukturze II (w języku angielskim) można znaleźć pod adresem: http://www.ti.com/lit/an/spra509/spra509.pdf. Tutaj zostanie opisana uproszczona procedura dla formy I, która jednak nie daje pewności że sygnał nie zostanie przesterowany. Wzmocnienie sekcji dwukwadratowej (dla składowej stałej) można łatwo obliczyć ze wzoru: b0 + b1 + b G = 1+ a + a natomiast wzmocnienie całego filtru jest iloczynem wzmocnień poszczególnych sekcji. Najłatwiej jest skorygować wzmocnienia każdej sekcji tak, aby były one jednostkowe. W tym celu, dla każdej sekcji opisanej wierszem macierzy SOS, należy obliczyć jej współczynnik G według powyższego wzoru, a następnie przeskalować sekcję, dzieląc współczynniki b w liczniku przez G. Macierz Gain można w tym przypadku zignorować. Następnie współczynniki filtru przekształca się na format Q15. Jeżeli wszystkie współczynniki są mniejsze od 1, można obliczone współczynniki zastosować w funkcji iircas51 z biblioteki DSPLIB. Funkcja wymaga podania dla każdej sekcji, współczynników w kolejności: b0, b1, b2, a1, a2. W przypadku stosowania 1 2 2

kilku sekcji, współczynniki podaje się kolejno, sekcjami. UWAGA: zawsze a0 = 1, tego współczynnika nie podaje się w DSPLIB. Niestety, zwykle przynajmniej jeden współczynnik, zazwyczaj a1, jest większy od 1, ale mniejszy od 2. Jedyną metodą jest w tym przypadku przekształcenie współczynników do formatu Q1.14, czyli przemnożenie ich przez 2 14. Powoduje to jednak, że wartość a0 = 0,5, a nie 1. Nie jest to problemem przy własnej implementacji filtru., Przy założeniu że sygnał wejściowy jest w formacie Q15, wartość obliczona przez sekcję filtru ma format Q1.29. Jest ona więc 2 razy mniejsza niż spodziewana wartość w formacie Q30. Zatem obliczoną wartość wyjściową należy przemnożyć przez 2. Skorygowaną wartość należy zapisać w buforze filtru oraz wysłać na jego wyjście. Procedura iircas51 zakłada jednak, że współczynniki są zapisane w formacie Q15, czyli że a0 = 32767, a nie 16383. Każda obliczona wartość wyjściowa jest zatem zmniejszona dwukrotnie względem oczekiwanej. Po n-tej sekcji, wartość zostaje zmniejszona 2 n razy. W wyniku tego, wartości wyjściowe zostaną zwykle wyzerowane (będą zbyt małe) i filtr przestanie działać (dostaniemy ciszę na wyjściu). Niestety, nie da się tego skorygować inaczej, niż modyfikując kod funkcji w asemblerze i dodając instrukcję przesuwającą wynik operacji o 1 bit w lewo (instrukcja SFTS). Zmodyfikowany kod funkcji iircas51 zamieszczono w archiwum dostępnym na stronie Katedry (materiały pomocnicze, ZPS, pliki potrzebne do utworzenia projektu CCS). Plik iircas51.asm, znajdujący się w podkatalogu iircas51_q14, należy skopiować do katalogu projektu. Po skompilowaniu projektu, kod ten zastąpi oryginalną wersję zamieszczoną w bibliotece DSPLIB. Modyfikacja kodu polegała na dodaniu instrukcji STFS AC1, #1, która mnoży wynik filtracji przez 2 po jego obliczeniu, przed zapisaniem w rejestrach. UWAGA: filtr IIR w opisanej implementacji jest bardzo podatny na przesterowanie, dlatego lepiej jest nie przetwarzać sygnałów o maksymalnej amplitudzie. Sugerowane rozwiązanie na potrzeby projektu: zmniejszenie amplitudy filtrowanego sygnału o połowę. Można to wykonać przesuwając wartości sygnału wejściowego o jeden bit w prawo, np.: sinus >>= 1; Konfiguracja projektu do obliczania FFT Biblioteka DSPLIB zawiera procedury do obliczania FFT, dla danych rzeczywistych i zespolonych, 16- i 32- bitowych. Opis tych funkcji zawarty jest w dokumentacji DSPLIB. Korzystanie z tych funkcji wymaga jednak odpowiedniego skonfigurowania obszarów pamięci, w których będą zapisywane dane. Konfigurację zapisuje się w pliku.cmd linkera. Poniższy przykład dotyczy konfiguracji zapisanej w pliku C5535.cmd, dostarczanej z szablonem projektu. Do poprawnego działania FFT, należy w bloku definicji sekcji (SECTIONS) dodać następujące wpisy:.fftcode > SARAM0.data:twiddle > SARAM1, align(2048).input > DARAM0, align(4) Szczególnie istotny jest wpis dla.data.twiddle. Jeżeli go nie będzie, program skompiluje się i uruchomi, ale otrzymamy nieprawidłowe wyniki. Definicja sekcji.input dotyczy danych, które będą przetwarzane przez FFT. Tablica próbek, która będzie podana do funkcji obliczającej FFT, musi zostać przydzielona do tej sekcji. W kodzie programu, przed zadeklarowaniem bufora próbek (koniecznie w części przed funkcją main), należy podać dyrektywę DATA_SECTION. Przykładowo dla tablicy o nazwie dane: #pragma DATA_SECTION (dane, ".input") DATA dane[1024]; 14

Obliczanie częstotliwości przedziału FFT Mamy wynik obliczenia N-punktowej transformaty FFT zapisany w buforze. Chcemy wiedzieć jakiej częstotliwości odpowiada k-ty punkt transformaty. N punktów FFT pokrywa zakres od 0 do fs. Jeden przedział transformaty odpowiada zatem zakresowi częstotliwości: d = fs / N Przykładowo, dla N = 2048 i fs = 48000 Hz: d = 23,4375 Hz. Częstotliwość k-tego przedziału wynosi zatem k d. Aby obliczyć ją na DSP, musimy zapisać liczbę d w formacie Q15. Musimy użyć typu long (32-bitowego) aby zapisać zarówno część całkowitą, jak i ułamkową (właściwie, jest to format Q16.15). dq = (fs / N) * 2 15 (48000 / 2048) * 32768 768000 Teraz wystarczy przemnożyć dq przez numer przedziału. Uzyskamy liczbę Q15 zapisaną na 32 bitach. Aby uzyskać wynik w postaci liczby całkowitej, należy obciąć część ułamkową: f = (dq * k) >> 15 Przykładowo, dla k = 1000: f = (768000 * 1000) >> 15 = 23437 Hz Uwaga: maksymalna wartość mnożnika k nie powodująca przepełnienia zakresu 32 bitów wynosi 2796. Zatem maksymalny rozmiar FFT pozwalający zastosować opisane operacje to N = 4096. Procedury FFT z biblioteki DSPLIB i tak nie pozwalają stosować okien dłuższych niż 2048. 15