Analizator widma z FFT – przykład aplikacji na STM32F4DISCOVERY

W artykule przedstawiamy przykład implementacji analizatora widma FFT sygnału pochodzącego z cyfrowego mikrofonu MEMS zamontowanego na płytce zestawu STM32F4DISCOVERY. Prezentowany przykład ilustruje w jaki sposób odczytywać sygnał z mikrofonu, jak go przetwarzać oraz jak wyświetlić wynik na kolorowym wyświetlaczu LCD. W projekcie przyjęto zakres analizowanych częstotliwości do 16 kHz, jednak całość stanowi podstawę, którą można rozwijać i modyfikować w zależności od wymagań. Projekt został utworzony w środowisku CooCox CoIDE z wykorzystaniem kompilatora GNU Tools ARM Embedded.

 

Fot. 1. Testowanie układu analizatora widma sygnałem audio z głośnika na który podawany jest prostokątny sygnał o częstotliwości 6,76 kHz

Fot. 1. Testowanie układu analizatora widma sygnałem audio z głośnika na który podawany jest prostokątny sygnał o częstotliwości 6,76 kHz

 

 

Urządzenie, które na wejściu przyjmuje sygnał opisany w czasie, a na wyjściu przedstawia ten sam sygnał, ale już opisany w częstotliwości, nazywamy analizatorem widma. Aby wykonać taki układ należy na początku określić etapy jego realizacji:

  1. konwersja sygnału akustycznego na elektryczny,
  2. odbiór sygnału elektrycznego przez mikrokontroler,
  3. wyciągnięcie danych przenoszonych przez sygnał elektryczny i zapis ich do bufora,
  4. wyznaczenie widma zebranych próbek sygnału, czyli transformacja x(t) ? X(f),
  5. przedstawienie widma w formie graficznej.

 

Fot. 2. Umiejscowienie czujnika dźwięku MP45DT02 na płytce STM32F4DISCOVERY

Fot. 2. Umiejscowienie czujnika dźwięku MP45DT02 na płytce STM32F4DISCOVERY

 

Na płytce STM32F4DISCOVERY znajduje się mikrofon MEMS typu MP45DT02 (fotografia 2) i to właśnie on będzie realizował pierwszy etap, czyli zamieniał sygnał akustyczny na elektryczny.

 

Rys. 3. Schemat podłączenia mikrofonu MP45DT02 do mikrokontrolera STM32F407VG w zestawie STM32F4DISCOVERY

 

Podłączenie mikrofonu do mikrokontrolera przedstawiono na rysunku 3. Ma on 6 wyprowadzeń z czego 3 są od zasilania, pozostałe 3 są sygnałowe. Te ostatnie to kolejno: wybór kanału lewy/prawy, wejściowy sygnał taktujący CLK oraz wyjściowy sygnał danych.

Odbierane przez układ I2S dane to tylko zbiór pewnych sekwencji bitów i przed oddaniem ich do etapu 4 należy je jeszcze przekonwertować z postaci PDM do postaci PCM, czyli chodzi głównie o to by uzyskać zbiór 16-bitowych wartości opisujących kolejne próbki sygnału w różnych chwilach czasu (kodowanie PCM). W tym celu można wykorzystać gotową bibliotekę PDM Audio Software Decoding Library, która jest dostępna w pliku archiwalnym pod nazwą STSW-STM32068 w katalogu Utilities/STM32F4-Discovery/) – są to 2 pliki: libPDMFilter_GCC.a oraz pdm_filter.h.

Kiedy mamy już zebrane próbki sygnału to możemy przejść do etapu czwartego i poddać je transformacji Fouriera. We wspomnianym już pliku STSW-STM32068 w katalogu Libraries/CMSIS/DSP_Lib/ można znaleźć kody źródłowe różnych funkcji często stosowanych przy przetwarzaniu sygnałów w tym również gotową funkcje szybkiej transformaty Fouriera (Fast Fourier Transform). Szczegółowy ich opis można znaleźć w pliku Libraries/CMSIS/index.htm w kategorii CMSIS DSP Software Library.

Ostatni etap to wyświetlenie otrzymanego widma sygnału na wyświetlaczu LCD i można to zrobić wykorzystując kilka plików biblioteki STM32 embedded GUI library do pobrania na stronie (wymagają one pewnych modyfikacji).

Widma częstotliwościowe prostych sygnałów

Tworzenie aplikacji analizatora widma najlepiej rozpocząć od wykonania czegoś prostego. Ponieważ interesuje nas widmo częstotliwościowe to warto sprawdzić jak wygląda ono dla kilku prostych sygnałów. W tym celu możemy wygenerować cztery sygnały, które zawierają od jednej do czterech składowych harmonicznych:

Generowanych jest 512 próbek sygnału i liczba ta wynika z wymagań narzuconych przez funkcję wyznaczającą widmo i chęci przedstawienia jak najdłuższego przebiegu na wyświetlaczu (wykorzystane będą tylko pierwsze 256). Wartość zmiennej freq w toku działania aplikacji demonstracyjnej jest zmieniana od 1 do 1024, a zmienna dt jest wirtualnym okresem próbkowania, który wynosi 0,001. Dalej można wyświetlić wygenerowane sygnały:

Mając przygotowany sygnał wykorzystujemy trzy funkcje z biblioteki DSP i wyznaczamy najpierw transformatę Fouriera danego sygnału, a następnie obliczamy wartości modułów dla każdej pary liczb rzeczywistej i urojonej. Na koniec możemy wyszukać składową sygnału (częstotliwość), która ma największą amplitudę i przeskalować tablicę modułów do zakresu nadającego się do wyświetlenia:

Pierwsza funkcja przyjmuje trzy argumenty: wskaźnik do struktury typu arm_rfft_instance_f32 opisującej sposób działania transformaty FFT, wskaźnik do buforu wejściowego z wartościami próbek sygnału oraz wskaźnik do buforu wyjściowego w którym będą zapisane zespolone wartości transformaty sygnału. Ważne jest aby w buforze wejściowym były zapisane rzeczywiste wartości (uwaga: zostaną one zmodyfikowane po wykonaniu się funkcji), czyli:

Natomiast w buforze wyjściowym, który powinien być dwa razy większy od wejściowego, oprócz wartości rzeczywistych będą znajdować się również wartości urojone:

W przypadku struktury przekazywanej w pierwszym argumencie nie ma potrzeby ręcznego uzupełniania jej, gdyż można do tego celu wykorzystać funkcję arm_rfft_init_f32():

Druga funkcja przyjmuje również trzy argumenty: wskaźnik do buforu wejściowego z wartościami par liczb rzeczywistych i urojonych, wskaźnik do buforu wyjściowego do którego zostaną zapisane wartości rzeczywiste oraz ostatni parametr – ilość liczb zespolonych. Funkcja oblicza moduł dla każdej pary wartości liczby rzeczywistej i urojonej zgodnie ze wzorem:

Trzecia funkcja znajduje maksymalną wartość w tablicy buffer_output_mag o długości 512 elementów i zapisuje ją do zmiennej maxvalue, a indeks komórki w jakiej się ona znajduje jest zapisywany w zmiennej maxvalueindex.

 

Fot. 4. Sygnały i ich widma:  od jednej składowej do czterech składowych

Fot. 4. Sygnały i ich widma:  od jednej składowej do czterech składowych

 

Na koniec, po przeskalowaniu wartości modułów, można wyświetlić otrzymane wyniki (fotografia 4). Wystarczy przedstawić pierwsze 256 elementów tablicy buffer_output_mag, gdyż druga połowa stanowi lustrzane odbicie pierwszej:

Odbiór sygnału audio z mikrofonu

Kiedy mamy pewność, że wyznaczanie widma częstotliwościowego sygnału działa poprawnie to możemy iść dalej i zamiast przygotowanego wcześniej sygnału wykorzystać sygnał odbierany z mikrofonu. Przedtem jednak konfigurujemy wykorzystywane peryferia:

Po skonfigurowaniu peryferii można wykonać inicjalizację biblioteki PDM:

W pliku main.h zdefiniowane są stałe wykorzystywane w programie:

Warto zwrócić uwagę na konfigurację układu I2S oraz biblioteki PDM pod względem wartości częstotliwości. Ponieważ biblioteka PDM oferuje cztery funkcje konwertujące sygnał z postaci PDM na PCM (współczynnik decymacji 64 lub 80, dane w kolejności MSB lub LSB):

to wymaganym jest aby częstotliwość na linii CLK układu MP45DT02 była równa:

FCLK = DecimatorFactor · FS

Tak więc, jeśli chcemy uzyskać częstotliwość próbkowania 32 kHz oraz wykorzystać współczynnik decymacji o wartości 64 to częstotliwość na linii CLK powinna wynosić 2048 kHz. Aby ustawić taką wartość w układzie I2S należy wiedzieć, że układ w trybie normalnej pracy odbiera po 16 bitów danych (najmniejsza możliwa wartość pola I2S_DataFormat) na przemian z kanału pierwszego oraz drugiego. A ponieważ w danej sytuacji będzie on cały czas odbierał dane z jednego źródła to na odbiór „próbki z dwóch kanałów” przeznaczone są 32 bity, czyli 2048 kHz / 32 = 64 kHz – taka też wartość jest ustawiana w polu I2S_AudioFreq.

Po włączeniu odbioru danych przez układ I2S komendą I2S_Cmd(SPI2, ENABLE) dane będą najpierw kopiowane do rejestru odbiorczego, a następnie w przerwaniu zostaną przeniesione do bufora PDM_Input_Buffer. Gdy bufor ten zostanie całkowicie zapełniony to wykona się funkcja PDM_Filter_64_LSB() konwertująca sygnał z postaci PDM do postaci PCM (plik stm32f4xx_it.c):

Widmo częstotliwościowe sygnału audio z mikrofonu

Ostatni etap projektu to wyświetlenie widma sygnału jaki otrzymujemy z mikrofonu. Do tego celu przygotowano dwie funkcje DrawSpectrum_Prepare(), która rysuje tylko raz stałe elementy wykresu oraz DrawSpectrum_Update(), która rysuje samo widmo lub go usuwa w zależności od przekazanego jako argument koloru.

Poniżej przedstawiono kod który wykorzystuje wszystkie podane dotąd informacje. Komentarza mogą wymagać dwa miejsca. Ponieważ w funkcje obsługi przerwań od odbioru danych od I2S maksymalny rozmiar paczki zapisanej w buforze PCM_Output_Buffer wynosi w danej sytuacji 32 elementy to trzeba zebrać ich 16 aby otrzymać tablicę 512 wartości rzeczywistych przekazywanych do funkcji wyznaczającej transformatę. Po drugie zamiast skalować widmo wykorzystując maksymalną jej wartość jest ono skalowane z wartością stałą wyznaczoną doświadczalnie tak aby wyświetlanie odbywało się poprawnie.

/wp-content/uploads/images/Analizator_widma_z_FFT_przyklad_aplikacji_na_STM32F4DISCOVERY/rys5.jpg

Rys. 5. Struktura prezentowanego projektu

 

Ostatecznie struktura projektu wygląda tak jak na rysunku 5, a na rysunkach 6 i 7 przedstawiono konfigurację w zakładce Compile oraz Link środowiska CooCox CoIDE.

 

Rys. 6. Konfiguracja w zakładce Compile

Rys. 6. Konfiguracja w zakładce Compile

 

Rys. 7. Konfiguracja w zakładce Link

Rys. 7. Konfiguracja w zakładce Link

 

W pliku system_stm32f4xx.c można znaleźć wartości użyte do konfiguracji sygnałów taktujących zarówno samego systemu jak również przekazywanego do peryferii I2S. Sprzętowa jednostka FPU nie została włączona gdyż biblioteka PDM nie jest przystosowana do jej obsługi (kompilator wyrzuca błędy).

Jan Szemiet