Eksperymenty z FPGA (10). Podłączamy mikrofon

Eksperymenty z FPGA (10). Podłączamy mikrofon
Pobierz PDF Download icon

W tym odcinku wracamy do przetwornika ADC. Jednak zamiast potencjometru podłączymy do niego mikrofon. Dzięki temu wykonamy eksperymenty z cyfrowego przetwarzania sygnałów. Przed przystąpieniem do pracy jak zwykle przypominam o aktualizacji repozytorium z przykładami (poprzez wywołanie polecenia git pull).

Spis treści

Nagrywamy po raz drugi

Mamy już przygotowane i przetestowane nowe bloki, więc możemy je teraz połączyć razem. Schemat naszego drugiego projektu został zaprezentowany na rysunku 17. Jest on bardzo podobny do pierwszej wersji z rysunku 4. Po prostu pojawiły się dwa dodatkowe moduły, przez które przepływają nasze dane.

Rysunek 17. Nagrywanie dźwięku z usunięciem składowej stałej

Musimy jeszcze ustalić, ile bitów danych z wejścia ADC potrzebujemy. Jak pamiętamy z wykresu przedstawionego na rysunku 8, gdy przekazywaliśmy osiem najstarszych bitów pomiaru, użyteczny sygnał mieścił się, mniej więcej (z pewnym marginesem bezpieczeństwa), w zakresie 80…120. Dla przypomnienia zostało to pokazane na pierwszym słupku z rysunku 18.

Rysunek 18. Ile bitów danych zbieramy

Ponieważ przetwornik zwraca nam 12 bitów danych, oznacza to, że nasz sygnał znajduje się pomiędzy wartościami 1280…1920. Pokazuje to środkowy słupek. Potrafimy już usunąć składową stałą, która wynosi około 1600. Po odjęciu jej okaże się, że użyteczne dane mieszczą się w zakresie około –320…320. Oznacza to, że można bez problemu zmieścić go w 10-bitowej liczbie ze znakiem. Ponieważ, tak jak poprzednio, chcemy wysyłać po 8 bitów danych na jeden pomiar, możemy od razu na blok dc_r podać jedynie 10 najstarszych bitów, co będzie odpowiadało podzieleniu przez cztery.

Listing 9. Połączenie przygotowanych modułów (11_dc_r\rec.sv)
18 parameter N_ADC = 10;
19 parameter N = 8;

26 StreamBus #(.N(N_ADC)) bus_adc(clk, rst);
27 StreamBus #(.N(N_ADC)) bus_dc(clk, rst);
28 StreamBus #(.N(N)) bus_uart(clk, rst);

55 assign bus_adc.data = adc_data_out[11-:N_ADC];

65 dc_r #(.N(N_ADC)) dc_remove (
66 .in(bus_adc),
67 .out(bus_dc));
68
69 saturation #(.N_IN(N_ADC), .N_OUT(N)) sat (
70 .in(bus_dc),
71 .out(bus_uart));
72
73 uart_tx #(
74 .F(F),
75 .BAUD(115200)
76 ) uart (
77 .bus(bus_uart),
78 .tx(tx));

Przejdźmy teraz do kodu, którego fragmenty znajdziemy na listingu 9. Najpierw definiujemy pomocniczo dwie stałe N_ADC i N, które będą określać szerokości interfejsów. Zostały one zdefiniowane w liniach 26…28. Do pierwszej magistrali bus_adc podłączamy wyjście z przetwornika ADC. Niżej znajdują się instancje modułów składowych.

Tym razem zmodyfikujemy sposób symulacji przetwornika ADC. Skorzystamy z możliwości dodania własnego pliku z listą kolejnych wartości napięcia. Najpierw jednak przygotujemy nowy sygnał testowy, który przyda nam się także w kolejnych projektach. Zamiast mowy wygenerujemy sygnał chirp. Po polsku czasami nazywa się go świergot albo ćwierkanie. Jest to sygnał sinusoidalny, ale z liniową zmianą częstości pomiędzy początkową wartością ω0, a końcową ω1. Oznacza to, że w momencie t chwilowa częstość jest równa:

Wyjściowy sygnał otrzymujemy po obliczeniu funkcji sinus dla aktualnej częstości:

Na rysunku 19 pokazany jest przykładowy przebieg częstości, zmieniającej się od 1 do 2 radianów na sekundę.

Rysunek 19. Sygnał chirp (świergot, ćwierkanie)

Do wygenerowania pliku dźwiękowego został wykorzystany program Audacity [5]. Jego główne okno pokazuje rysunek 20. Na pasku zadań wybieramy opcję Generuj. Z rozwiniętego menu wybieramy opcję Świergot.

Rysunek 20. Okno programu Audacity

Pojawi się nowe okno (rysunek 21), gdzie możemy skonfigurować parametry naszego sygnału. Jako częstotliwość początkową wybieramy 0, a końcową 5000 Hz. Amplitudę (zarówno początkową, jak i końcową) zmieniamy na 1. Klikamy OK. Teraz naciskając zielony przycisk Play, możemy odegrać sygnał. Uwaga, jest to dość głośny i nieprzyjemny odgłos.

Rysunek 21. Okno generowania sygnału chirp

W repozytorium znajduje się także plik mp3 11_dc_r\parse\chirp_0_5.mp3, zawierający stworzony przebieg.

Do zarejestrowania danych ponownie wykorzystujemy projekt 10_record/10_rec.qpf. Tym razem zamiast zapisać wynik z powrotem do pliku dźwiękowego, stworzymy plik tekstowy, w którym w każdej linii znajduje się numer próbki, a następnie liczba zmiennoprzecinkowa, odpowiadająca napięciu. Przykładową zawartość pokazuje listing 10.

Listing 10. Fragment danych do symulacji (11_dc_r\parse\adc_sample.txt)
01 39877 1.2761718034744263
02 39878 1.2761718034744263
03 39879 1.2761718034744263
04 39880 1.2890625
05 39881 1.2890625
06 39882 1.2890625
07 39883 1.2890625
08 39884 1.2890625
09 39885 1.3019530773162842
10 39886 1.3019530773162842

Do wygenerowania pliku tekstowego użyjemy kolejnego skryptu. Jego fragment znajdziemy na listingu 11. Najpierw odczytane z portu szeregowego wartości zmieniamy na wolty, mnożąc przez wartość napięcia odniesienia 3,3 V, a następnie dzieląc przez odpowiadającą jej wartość 256.

Listing 11. Skrypt w języku Python przygotowujący dane do symulacji (11_dc_r\parse\adc_sample.ipynb)
01 Vmax = 3.3
02 ADC_max = 256
03 v = np.float32(xn)*Vmax/ADC_max
04 plt.plot(v)
05
06 file="adc_sample.txt"
07 vp = v[20000:30000]
08 with open(file, ‘w’) as f:
09 for i in range(len(vp)):
10 for j in range(5):
11 f.write("{} {}\n".format(5*i+j, vp[i]))

Dalej w pętli tworzymy nasz plik do symulacji. Ponieważ przesyłamy tylko co piątą próbkę, musimy każdy pomiar zapisać w pliku pięciokrotnie. Aby symulacja nie trwała zbyt długo, wybieramy tylko fragment zebranych danych. Uzyskane wartości napięcia możemy także wyrysować w formie wykresu, który został pokazany na rysunku 22.

Rysunek 22. Zmiana napięcia w funkcji czasu
Rysunek 23. Konfiguracja danych symulacyjnych dla bloku ADC

Teraz, generując blok ADC dla nowego projektu, możemy w zakładce Logic Simulation (symulacja logiki) wybrać opcję Enable (włączone), a następnie, przy konfiguracji kanału 1 (CH1), dodać ścieżkę do wygenerowanego przez nas pliku. Kiedy przyjrzymy się wygenerowanym modułom, zobaczymy, że pojawiła się tam odpowiednia konfiguracja, co zostało pokazane na listingu 12.

Listing 12. Konfiguracja pliku z danymi dla ADC (11_dc_r\adc\simulation\submodules\adc_modular_adc_0.v)
26 altera_modular_adc_control #(
39 .simfilename_ch0 (""),
40 .simfilename_ch1 ("C:/riscv/fpga-experiments/11_dc_r/parse/adc_sample.txt"),
41 .simfilename_ch2 (""),

Pozostaje jeszcze przygotowanie testbenchu. Jest on bardzo prosty, ponieważ tym razem generujemy jedynie sygnał zegarowy i reset. Dane będą odczytywane z pliku i dostarczane przez blok ADC. Tym razem, pomiędzy kolejnymi próbkami, będziemy mieli długą przerwę ze stanem nieustalonym. Dlatego, aby wyrysować czytelne wykresy, stworzymy dodatkowe sygnały, gdzie będziemy zatrzaskiwać jedynie poprawne dane.

Odpowiadający za to kod przedstawiony jest na listingu 13.

Listing 13. Zapisywanie danych w testbenchu (11_dc_r\rec_tb.sv)
33 always_ff @(posedge clk) begin
34 if (dut.adc_valid_out)
35 adc_data_out <= dut.adc_data_out;
36 if (dut.bus_dc.valid)
37 data_dc <= dut.bus_dc.data;
38 if (dut.bus_uart.valid)
39 data_out <= dut.bus_uart.data;
40 end

Symulację uruchamiamy komendą:

do ./rec_sim.do

Ponieważ symulujemy czas kilku sekund, jej wykonanie może zająć nawet kilkanaście minut. Fragment uzyskanego wyniku pokazuje rysunek 24.

Rysunek 24. Symulacja całego modułu nagrywania

Na końcu otwórzmy projekt 11_dc_r/11_rec.qpf w środowisku Quartus i rozpocznijmy jego budowę. Kiedy się zakończy, możemy wgrać bitstream i zarejestrować kolejny fragment nagrania. Do jego konwersji na format dźwiękowy możemy wykorzystać skrypt 11_dc_r/parse/Parse.ipynb.

Od pokazanego na listingu 2 różni się traktowaniem odebranych danych jako liczby ze znakiem:

xn = np.int8(x)

Rysunek 25. Mowa zarejestrowana przez układ z usuwaniem składowej stałej

Wykres z zarejestrowanymi danymi przedstawia rysunek 25. Widzimy, że gdy wartości przechodzą poza dopuszczalne wartości 127, albo –128, następuje nasycenie.

Podsumowanie

W tym odcinku rozpoczęliśmy eksperymenty z przetwarzaniem dźwięku w układzie FPGA. W następnej części zaimplementujemy transformatę Fouriera i będziemy prezentować aktualne spektrum odbieranego sygnału na diodach LED.

Rafał Kozik
rafkozik@gmail.com

[1] Intel MAX 10 Analog to Digital Converter User Guide, 2020-08-15, https://intel.ly/3iIQ6Q2
[2] anaconda.com, 2020-08-15, https://bit.ly/3iN89oi
[3] Film pokazujący, jak skonwertować nagranie https://bit.ly/3iMh5u4
[4] Lyons r.G., Wprowadzenie do cyfrowego przetwarzania sygnałów, Wydawnictwo Komunikacji i Łączności, Warszawa 2010
[5] Audacity, 2020-08-15, https://bit.ly/3h5vjWz

Artykuł ukazał się w
Elektronika Praktyczna
wrzesień 2020
DO POBRANIA
Pobierz PDF Download icon

Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik październik 2021

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio wrzesień - październik 2021

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka Podzespoły Aplikacje październik 2021

Automatyka Podzespoły Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna październik 2021

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich październik 2021

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów