PROCESORY SYGNAŁOWE - LABORATORIUM
Transkrypt
PROCESORY SYGNAŁOWE - LABORATORIUM
PROCESORY SYGNAŁOWE - LABORATORIUM
Ćwiczenie nr 03
Obsługa portu szeregowego, układu kodeka audio i pierwsze przetwarzanie
sygnałów (cyfrowa regulacja głośności)
1. Konfiguracja układu szeregowego portu MCBSP
Porty szeregowe są zwykle jednymi z najważniejszych urządzeń w mikroprocesorowych
układach przetwarzania sygnałów. Dzięki prostej architekturze transmisji szeregowej
umożliwiają przesyłanie danych między układem mikroprocesorowym a przetwornikami
analogowo-cyfrowymi (A/C) i cyfrowo-analogowymi (C/A). Bardziej wydajne interfejsy
równoległe są stosowane tylko w przypadkach, w których najważniejszym kryterium jest
szybkość transmisji. Makieta laboratoryjna umożliwia akwizycję sygnałów z tzw. kodeka
audio TLV320AIC23 firmy Texas Instruments. Układ ten nie tylko zawiera przetworniki A/C
i C/A, ale również zapewnia szereg funkcji związanych z akwizycją sygnałów audio
(programowane wzmocnienie, nadpróbkowanie, filtrację i obsługę różnych interfejsów
szeregowych). Dokumentacja układu jest dostępna na stronie laboratorium. Układ kodeka
konfigurowany jest za pomocą szeregowego interfejsu SPI, który jest obsługiwany przez port
MCBSP0 procesora sygnałowego. Port MCBSP1 używany jest do przesyłania próbek
sygnałów konwertowanych przez przetworniki A/C i C/A w układzie kodeka.
Obsługa portu MCBSP odbywa się za pomocą funkcji biblioteki CSL (plik nagłówkowy
csl_mcbsp.h), zatem praca z portem rozpoczyna się od jego otwarcia za pomocą funkcji
MCBSP_open. Następnie port jest konfigurowany przy użyciu funkcji MCBSP_config i
uruchamiany funkcją MCBSP_start. Do odbierania danych z rejestrów odbiornika i nadajnika
wykorzystuje się funkcje MCBSP_read i MCBSP_write. Natomiast funkcji MCBSP_xrdy i
MCBSP_rrdy używa się do synchronizacji programu z nadajnikiem i odbiornikiem przez
monitorowanie bitów XRDY i RRDY w rejestrze SPCR portu MCBSP. Zanim zapiszemy daną
do rejestru nadajnika należy upewnić się, że nadajnik jest do tego gotowy (XRDY=1).
Podobnie z odbiornikiem – należy poczekać z odbieraniem danych do momentu, w którym
pojawi się nowe słowo w rejestrze odbiornika (RRDY=1).
W przypadku, w którym port obsługiwany jest za pomocą przerwań, wykorzystuje się
funkcje MCBSP_getXmtEventId i MCBSP_getRcvEventId do pobrania identyfikatora do
konfiguracji układu obsługi przerwań. Ponieważ zarówno nadajnik jak i odbiornik mogą
zgłaszać przerwanie z różnych powodów, konieczna jest odpowiednia konfiguracja bitów
XINTM i RINTM w rejestrze SPCR portu MCBSP. W ramach przygotowania do ćwiczenia
należy opracować w formie notatki odręcznej lub wydruku opis funkcji obsługi portu
szeregowego wywoływanych w module mcsbp.c na podstawie zamieszczonej na stronie
dokumentacji biblioteki CSL. W oparciu o zamieszczoną na stronie dokumentację portu
MCBSP należy również opisać konfigurację poszczególnych rejestrów portu MCBSP0 i
MCBSP1. Opracowane materiały należy okazać na początku zajęć laboratoryjnych.
2. TLV320AIC23 – układ kodeka audio
Zadaniem układu kodeka jest akwizycja sygnałów wejściowych i generowanie sygnałów
wyjściowych. Dodatkowo układ zapewnia wiele funkcji związanych z kondycjonowaniem
sygnałów audio. Może być programowany za pomocą danych przesyłanych szeregowym
portem SPI. Na wykorzystywanej na zajęciach laboratoryjnych makiecie port ten jest
podłączony do portu MCBSP0 układu procesora TMS320C6713. Za pomocą tego portu układ
mikroprocesorowy może przesyłać do układu kodeka 16 bitowe słowo sterujące (rys. 1) .
Słowo to jest podzielone na dwie części. Starsze 7 bitów stanowi numer programowanego
rejestru, natomiast młodsze 9 bitów stanowi zawartość, która ma być do niego zapisana.
Rys. 1. Słowo konfigurujące układ kodeka audio TLV320AIC23. Bity rn definiują
numer rejestru, do którego wpisane zostaną bity z danymi dn.
Opis i znaczenie rejestrów układu kodeka znajdują się w dokumentacji zamieszczonej na
stronie internetowej laboratorium. Przygotowując się do ćwiczenia należy opisać tę
konfigurację układu kodeka, która została ustawiona w projekcie CircularBuffer
zamieszczonym na stronie laboratorium przy ćwiczeniu nr 3. Opracowany opis należy
przedstawić w formie notatki odręcznej lub wydruku na początku zajęć.
3. Bufor kołowy – narzędzie do synchronizacji interfejsów komunikacyjnych z
programem sterującym pracą układu mikroprocesorowego.
Podstawowym problem przy obsłudze interfejsów komunikacyjnych jest synchronizacja
odczytu lub zapisu danych z programem. W przypadku, gdy interfejs obsługiwany jest za
pomocą przerwań, pojawia się problem przekazania danej z urządzenia do programu i
odwrotnie.
Dobrym
rozwiązaniem
jest
buforowanie
danych,
które
polega
na
zaimplementowaniu struktury danych umożliwiającej niezależny zapis i odczyt przez program
główny i funkcje obsługi przerwania. Poniżej podano przykład efektywnej implementacji
struktury tzw. bufora kołowego, który umożliwia przechowywanie N-1 ostatnio zapisanych
słów. W tym celu w strukturze CIRCBUFFER zdefiniowano 3 pola: wskaźnik buf, który
umożliwi dostęp do tablicy danych, dwa indeksy pozycji zapisu w i pozycji odczytu r oraz
rozmiar L tablicy, której adres zostanie przypisany do pola buf. Zmienne te będą
modyfikowane tylko za pomocą funkcji circ_put i circ_get. Funkcja circ_put sprawdza, czy
bufor jest pełny. Jeżeli jest, to zwraca wartość 1, a jeżeli nie, to wpisuje daną pod bieżącą
pozycję i zwiększa indeks pozycji zapisu w. Natomiast funkcja circ_get, jeżeli bufor jest
pusty, to zwraca 1, a jeżeli nie, to zwraca przez wskaźnik data wartość spod bieżącej pozycji
indeksu odczytu r i zwiększa go o 1.
typedef struct _CIRCBUFFER
{
volatile Int16 *buf;
volatile int w;
volatile int r;
volatile int L;
}CIRCBUFFER;
void init_circular_buffer(CIRCBUFFER *b, Int16 *buf, int N)
{
b->buf=buf;
b->L=N; b->w=0; b->r=0;
}
int circ_put(CIRCBUFFER* b, Int16 data)
{
if(b->w==(b->r-1)&(b->L-1))
return 1;
b->buf[b->w]=data;
b->w=(b->w+1)&(b->L-1);
return 0;
}
int circ_get(CIRCBUFFER *b, Int16* data)
{
if(b->w==b->r)
return 1;
*data=b->buf[b->r];
b->r=(b->r+1)&(b->L-1);
return 0;
}
int circ_len(CIRCBUFFER *b)
{
return (b->w-b->r)&(b->L-1);
}
Istotne w tej implementacji jest wyrażenie „&(L-1)”. Jest to forma efektywnej
implementacji operacji reszty z dzielenia przez L w przypadku, gdy N jest potęgą liczby 2.
Jeżeli L jest potęgą liczby dwa, to jej kod binarny wygląda jak niżej.
Liczba w kodzie
dziesiętnym
16
32
Liczba w kodzie
binarnym
0001 0000
0010 0000
64
0100 0000
128
1000 0000
Dzielenie przez takie liczby jest tożsame z przesunięciem bitowym w prawo o numer
pozycji jedynki. Pozycje numeruje się od 0, zatem dzielenie przez 16 to przesunięcie w prawo
o 4 bity, przez 32 o 5 bitów, przez 128 o 7 bitów. Jeżeli dzielenie jest prostym przesunięciem,
to resztę z dzielenia stanowią te bity, które są na prawo od jedynki. Na przykład reszta z
dzielenia 56 przez 16 wynosi 8 i jest zaznaczona poniżej w kodzie liczby 56 kolorem szarym.
0011 1000
Poniżej podano więcej przykładów.
78 mod 32 =14 0100 1110
113 mod 64 =49 0111 0001
102 mod 16 =6
0110 0110
121 mod 32 =25 0111 1001
Żeby uzyskać wartość liczbową tych bitów, należy wykonać operację iloczynu logicznego
z liczbą o 1 mniejszą niż potęga dwójki, przez którą dzielimy. Taką liczbę zawsze stanowią
jedynki na pozycjach po prawej stronie. Fakt ten zilustrowano na poniższych przykładach.
Liczba w kodzie
Liczba w kodzie
dziesiętnym
binarnym
16-1=15
0000 1111
32-1=31
0001 1111
64-1=63
0011 1111
128-1=127
0111 1111
Niżej natomiast porównano operację iloczynu logicznego z operacją reszty z dzielenia.
Liczba w kodzie
Iloczyn logiczny w kodzie binarnym
dziesiętnym
0100 1110(78) & 0001 1111(31) = 0000 1110(14)
78 mod 32 =14
0111 0001(113) & 0011 1111(63) = 0011 0001(49)
113 mod 64 =49
0110 0110(102) & 0000 1111(15) = 0000 0110(6)
102 mod 16 =6
0111 1001(121) & 0001 1111(31) = 0001 1001(25)
121 mod 32 =25
Jak widać, w sytuacji gdy dzielnik jest potęgą dwójki, operacja iloczynu logicznego z
liczbą o 1 mniejszą jest tożsama z operacją reszty z dzielenia.
Operacja reszty z dzielenia ma szczególne znaczenie dla obsługi bufora kołowego.
Wykonana po operacji arytmetycznej na pozycji bufora umożliwia poprawne indeksowanie
bufora. Na przykład: jeżeli bufor ma długość 128 słów i aktualny indeks zapisu w=127
(znajduje się na końcu tablicy), to operacja w=(w+1)&127 sprawia, że przesuwa się on na
pozycję w=0 (początek tablicy). Właśnie tę właściwość wykorzystuje się w funkcjach
circ_put i circ_get.
Bardzo ważna jest możliwość monitorowania stanu zapełnienia bufora. Jeżeli indeks
zapisu w jest na tej samej pozycji co indeks odczytu r, to oznacza, że bufor jest pusty. Jeżeli
indeks zapisu w znajduje się o jedną pozycję przed indeksem odczytu r, to znaczy, że bufor
jest pełny. Ponieważ indeks zapisu może pokrywać się z indeksem odczytu tylko w sytuacji,
gdy bufor jest pusty, to maksymalna ilość znaków w buforze wynosi L-1. Ilość danych w
buforze jest obliczana przez funkcję circ_len i jest równa różnicy między pozycją zapisu a
pozycją odczytu. Prosta różnica może być jednak myląca, gdy pozycja zapisu ma mniejszy
indeks niż pozycja odczytu. W tej sytuacji różnica jest ujemna. Jeżeli jednak obliczy się
iloczyn logiczny tej różnicy z liczbą L-1, uzyskujemy wartość równą ilości danych w buforze.
Przykład:
w=20 r=25 L=32
20-25=-5
-5 w kodzie U2 wynosi 1111 1011
31 w kodzie U2 wynosi 0001 1111
1111 1011 & 0001 1111= 0001 1011
0001 1011 w kodzie U2 oznacza 27.
Właśnie tyle znaków znajduje się w buforze.
Warto zauważyć, że funkcja circ_put modyfikuje tylko indeks zapisu z, natomiast funkcja
circ_get tylko indeks odczytu r. Funkcja circ_len nie modyfikuje żadnego indeksu. Oznacza
to, że każda z tych funkcji może być użyta w funkcji obsługi przerwania i nie doprowadzi to
do niesynchronizowanej modyfikacji indeksu. Jeżeli dany bufor jest buforem nadajnika, to
program główny używa funkcji circ_put, natomiast funkcja obsługi przerwania od nadajnika
używa circ_get. W przypadku odbiornika będzie odwrotnie. Oczywiście bufory odbiornika i
nadajnika muszą być zdefiniowane jako osobne tablice.
Przypisanie tablicy do danego bufora może być zrealizowane przez funkcję
init_circular_buffer. Inicjuje ona pola zmiennej strukturalnej typu CIRCBUFFER i tworzy w
ten sposób bufor kołowy, który może być obsługiwany za pomocą funkcji circ_put, circ_get i
circ_len.
4. Opis zadań do wykonania
Zadanie 1
Zmodyfikuj moduł mcbsp.c tak, aby zarówno odbieranie danych, jak i nadawanie
odbywało się w funkcji obsługi przerwania. Konieczne jest dodanie zmiennej output i tablicy
out_array do obsługi bufora kołowego nadajnika. W funkcji setupMCBSP należy dodać
instrukcje, które skonfigurują układ kontroli przerwań w taki sposób, żeby przerwanie od
nadajnika portu MCBSP1 wywoływało funkcję obsługi przerwania o nazwie transmitter.
Natomiast w funkcji obsługi przerwania należy pobrać próbkę z bufora wyjściowego output i
wysłać ją do nadajnika portu MCBSP1. W funkcji main należy zastąpić wywołanie funkcji
writeData zapisem danej do bufora output.
Zadanie 2
Zmodyfikuj program z zadania 1 w taki sposób, aby tłumił amplitudę sygnału
wyjściowego. Tłumienie realizowane jest przez operację mnożenia przez liczbę mniejszą
od 1. Skonfiguruj układ licznika nr 0 tak, aby zgłaszał przerwanie co 100 ms. W funkcji
obsługi przerwania od licznika należy zmieniać tłumienie sygnału wyjściowego od 0 do 1 i z
powrotem. Okres zmian powinien wynosić 5s.
Zadanie 3
Skonfiguruj układ licznika nr 1 w taki sposób, żeby co 100 ms zgłaszał przerwanie.
Zaprojektuj maszynę stanów sygnalizatora diodowego, która będzie wyświetlała stopień
zajętości buforów wejściowego i wyjściowego. Dioda nr 3 ma sygnalizować rodzaj bufora (0wejściowy, 1-wyjściowy). Diody od 0 do 2 będą sygnalizować w kodzie binarnym stopień
zajętości buforów (od 0-pusty, 1- zajęty w 1/8, 2-zejęty w 1/4, itd. aż do 7-pełny).
W funkcji obsługi przerwania od licznika 1, należy wywołać maszynę stanów
sygnalizatora, która będzie wyświetlała naprzemiennie zajętość buforów wejściowego i
wyjściowego. Przerwanie to ma również obsługiwać jeden z liczników softwarowych. Za
pomocą tego licznika należy co 5s uruchamiać pętlę opóźniającą w funkcji main. Ilość iteracji
pętli opóźniającej należy dobrać tak, aby zajętość bufora wejściowego wzrastała o około 1/8
(około 16 próbek), a zajętość bufora wyjściowego zmniejszała się o 1/8. Tuż po inicjacji
bufora wyjściowego należy wpisać do niego 127 zer zapełniając go całkowicie. W ten sposób
uruchomiona pętla opóźniająca będzie zmniejszać stopień zapełnienia tego bufora.