
В своей предыдущей статье я написал, как произвести оцифровку звукового сигнала платой FPGA MCY316. В том проекте данные полученные из АЦП просто передавались в компьютер через последовательный порт. Уже на компьютере данные принимались из последовательного порта программой на питоне и отображались графиками в окне.
А сейчас я хочу модифицировать этот проект и добавить в FPGA еще цифровой КИХ фильтр, чтобы разобраться, как он работает.
Рисунок выше показывает схему моего эксперимента.
КИХ фильтр это фильтр с конечной импульсной характеристикой. Если на такой фильтр подается короткий импульс, то на его выходе через некоторое время от сигнала не останется и следа. А всё потому, что описывается фильтр вот каким математическим выражением:
Здесь St это отсчёты сигнала, а Yt-k это коэффициенты фильтра. Всего отсчётов N.
Получается вот такая схема фильтра:

Квадратики S это цепочка регистров. С каждым новым приходящим отсчетом сигнала каждый регистр передает хранящийся в нем отсчет следующему регистру. При частоте оцифровки F соседние регистры хранят значения сигнала с интервалом времени 1/F.
Кружочки это умножители на соответствующий коэффициент Y.
Искусство создания цифрового КИХ фильтра состоит в выборе длины цепочки регистров и расчёте коэффициентов Y. Как можно рассчитать фильтр? Если сказать просто, то фильтр должен выделять нужный нам сигнал. Коэффициенты Y хранящиеся в цифровом фильтре это отсчеты эталонного сигнала, который мы хотим обнаружить. Если на такой фильтр подать одиночный импульс, то на выходе как раз получится эталонный сигнал.
Существует множество методов расчёта фильтров и для них написаны готовые программы и библиотеки для Mathlab, GNU Octave, Python.
Предположим, что я хочу создать для звукового сигнала полосовой фильтр (bandpass) с пропусканием от 600Гц до 2200Гц. Программа на питоне для расчёта такого фильтра может выглядеть вот так:
import numpy as np from scipy import signal import matplotlib.pyplot as plt num_taps = 501 # it helps to use an odd number of taps cut_off = [600.0, 2200.0] # Hz sample_rate = 46875 # Hz #T = signal.firwin(num_taps, cut_off, window = "hamming", fs=sample_rate ) T = signal.firwin(num_taps, cutoff = cut_off, window = "hamming", fs=sample_rate, pass_zero = False) Ta = np.array(T) Ta_abs_max = np.amax(np.abs(Ta)) Ta_scaled = Ta*(32766/Ta_abs_max) Ta_scaled_int = Ta_scaled.astype(int) print( Ta ) print( Ta_scaled_int ) np.savetxt('fir-coeffs.txt', Ta_scaled_int, fmt='%d' ) print("========= Altera Memory Initialization File ===================") print("WIDTH = 16;") print("DEPTH = 512;") print("ADDRESS_RADIX = HEX;") print("DATA_RADIX = HEX;") print("CONTENT BEGIN") i=0 while i < len(Ta_scaled_int) : print( f"{i:04x}",":", f"{Ta_scaled_int[i]&0xffff:04x}",";") i=i+1 print("END") print("================================================================") w, h = signal.freqz(T,fs=sample_rate,include_nyquist=False) fig, ax1 = plt.subplots() ax1.set_title('Digital filter frequency response') ax1.plot(w, 20 * np.log10(abs(h)), 'b') ax1.set_ylabel('Amplitude [dB]', color='b') ax1.set_xlabel('Frequency [rad/sample]') ax2 = ax1.twinx() angles = np.unwrap(np.angle(h)) ax2.plot(w, angles, 'g') ax2.set_ylabel('Angle (radians)', color='g') ax2.grid(True) ax2.axis('tight') plt.show()
Такая питоновская программа отработает и нарисует вот такую итоговую частотную характеристику:

Ну и конечно, эта программа еще выдаст список более 500 коэффициентов фильтра:
[-1.98161865e-04 -1.88693513e-04 -1.70507493e-04 -1.44797941e-04 -1.13348819e-04 -7.83990087e-05 -4.24696335e-05 -8.16611985e-06 2.20303956e-05 4.59664915e-05 6.19988370e-05 6.91407007e-05 6.71599779e-05 5.66193512e-05 3.88532870e-05 1.58812526e-05 -9.73848540e-06 -3.51052202e-05 -5.72086133e-05 -7.31806289e-05 -8.05468297e-05 -7.74562747e-05 -6.28698992e-05 -3.66895801e-05 1.85968102e-07 4.58869791e-05 9.76822284e-05 1.52167589e-04 2.05521707e-04 2.53810532e-04 2.93317172e-04 3.20869991e-04 3.34140421e-04 3.31882943e-04 3.14093089e-04 2.82065134e-04 2.38338818e-04 1.86533662e-04 1.31079230e-04 7.68594287e-05 2.87976107e-05 -8.58395309e-06 -3.15924150e-05 -3.77416223e-05 -2.60417625e-05 2.82941136e-06 4.64957021e-05 1.01002501e-04 1.61085330e-04 2.20570999e-04 2.72882654e-04 3.11609038e-04 3.31090312e-04 3.26968676e-04 2.96652381e-04 2.39646763e-04 1.57715444e-04 5.48482047e-05 -6.29717152e-05 -1.88190415e-04 -3.12268004e-04 -4.26380806e-04 -5.22183857e-04 -5.92568032e-04 -6.32343297e-04 -6.38782772e-04 -6.11971626e-04 -5.54919647e-04 -4.73415723e-04 -3.75624708e-04 -2.71450537e-04 -1.71711753e-04 -8.71948709e-05 -2.76652008e-05 -9.22474985e-07 -1.19887444e-05 -6.25082253e-05 -1.50423395e-04 -2.69969696e-04 -4.12004401e-04 -5.64655741e-04 -7.14248804e-04 -8.46437746e-04 -9.47452001e-04 -1.00534965e-03 -1.01116567e-03 -9.59847193e-04 -8.50882173e-04 -6.88551514e-04 -4.81765292e-04 -2.43479579e-04 1.02721050e-05 2.61662531e-04 4.92568115e-04 6.86154767e-04 8.28413800e-04 9.09508078e-04 9.24799747e-04 8.75454053e-04 7.68547449e-04 6.16649687e-04 4.36895576e-04 2.49608530e-04 7.65808001e-05 -6.08496040e-05 -1.43761365e-04 -1.57477525e-04 -9.30892382e-05 5.14731461e-05 2.70935874e-04 5.52812515e-04 8.78183614e-04 1.22308179e-03 1.56038302e-03 1.86205560e-03 2.10158077e-03 2.25633609e-03 2.30972824e-03 2.25287556e-03 2.08567371e-03 1.81712579e-03 1.46487975e-03 1.05398419e-03 6.14943586e-04 1.81219535e-04 -2.13620600e-04 -5.38867392e-04 -7.69486161e-04 -8.88531054e-04 -8.88926887e-04 -7.74436926e-04 -5.59700440e-04 -2.69302192e-04 6.40793507e-05 4.02314761e-04 7.05052077e-04 9.33266078e-04 1.05282547e-03 1.03776235e-03 8.72943226e-04 5.55882890e-04 9.75089362e-05 -4.78229308e-04 -1.13591537e-03 -1.83128900e-03 -2.51474575e-03 -3.13543575e-03 -3.64562187e-03 -4.00492239e-03 -4.18405998e-03 -4.16776900e-03 -3.95657476e-03 -3.56724787e-03 -3.03184641e-03 -2.39538016e-03 -1.71225352e-03 -1.04175727e-03 -4.42972267e-04 3.04868860e-05 3.35430765e-04 4.43309317e-04 3.43283242e-04 4.38408466e-05 -4.27234784e-04 -1.02460552e-03 -1.68873435e-03 -2.35062225e-03 -2.93756072e-03 -3.37943246e-03 -3.61502785e-03 -3.59782419e-03 -3.30070166e-03 -2.71914354e-03 -1.87258441e-03 -8.03719544e-04 4.24240422e-04 1.73220653e-03 3.03140643e-03 4.23065541e-03 5.24411002e-03 5.99884697e-03 6.44160060e-03 6.54404046e-03 6.30607423e-03 5.75681306e-03 4.95302562e-03 3.97511819e-03 2.92089363e-03 1.89754428e-03 1.01250393e-03 3.63906941e-04 3.14657810e-05 6.85739925e-05 4.96368127e-04 1.30034283e-03 2.42991777e-03 3.80111778e-03 5.30226763e-03 6.80234030e-03 8.16135667e-03 9.24203385e-03 9.92173992e-03 1.01037470e-02 9.72679111e-03 8.77204880e-03 7.26682036e-03 5.28445650e-03 2.94036135e-03 3.84227125e-04 -2.21102237e-03 -4.66280516e-03 -6.79395348e-03 -8.44809614e-03 -9.50408227e-03 -9.88819881e-03 -9.58305782e-03 -8.63225034e-03 -7.14016640e-03 -5.26674513e-03 -3.21731695e-03 -1.22810184e-03 4.51699508e-04 1.57997057e-03 1.94242115e-03 1.37240771e-03 -2.31767523e-04 -2.89355342e-03 -6.55007415e-03 -1.10501644e-02 -1.61589715e-02 -2.15691018e-02 -2.69177966e-02 -3.18091506e-02 -3.58399760e-02 -3.86275921e-02 -3.98376219e-02 -3.92098062e-02 -3.65799301e-02 -3.18961728e-02 -2.52285447e-02 -1.67705300e-02 -6.83258217e-03 4.17231352e-03 1.57502346e-02 2.73537830e-02 3.84136884e-02 4.83725176e-02 5.67182725e-02 6.30156468e-02 6.69328436e-02 6.82621456e-02 6.69328436e-02 6.30156468e-02 5.67182725e-02 4.83725176e-02 3.84136884e-02 2.73537830e-02 1.57502346e-02 4.17231352e-03 -6.83258217e-03 -1.67705300e-02 -2.52285447e-02 -3.18961728e-02 -3.65799301e-02 -3.92098062e-02 -3.98376219e-02 -3.86275921e-02 -3.58399760e-02 -3.18091506e-02 -2.69177966e-02 -2.15691018e-02 -1.61589715e-02 -1.10501644e-02 -6.55007415e-03 -2.89355342e-03 -2.31767523e-04 1.37240771e-03 1.94242115e-03 1.57997057e-03 4.51699508e-04 -1.22810184e-03 -3.21731695e-03 -5.26674513e-03 -7.14016640e-03 -8.63225034e-03 -9.58305782e-03 -9.88819881e-03 -9.50408227e-03 -8.44809614e-03 -6.79395348e-03 -4.66280516e-03 -2.21102237e-03 3.84227125e-04 2.94036135e-03 5.28445650e-03 7.26682036e-03 8.77204880e-03 9.72679111e-03 1.01037470e-02 9.92173992e-03 9.24203385e-03 8.16135667e-03 6.80234030e-03 5.30226763e-03 3.80111778e-03 2.42991777e-03 1.30034283e-03 4.96368127e-04 6.85739925e-05 3.14657810e-05 3.63906941e-04 1.01250393e-03 1.89754428e-03 2.92089363e-03 3.97511819e-03 4.95302562e-03 5.75681306e-03 6.30607423e-03 6.54404046e-03 6.44160060e-03 5.99884697e-03 5.24411002e-03 4.23065541e-03 3.03140643e-03 1.73220653e-03 4.24240422e-04 -8.03719544e-04 -1.87258441e-03 -2.71914354e-03 -3.30070166e-03 -3.59782419e-03 -3.61502785e-03 -3.37943246e-03 -2.93756072e-03 -2.35062225e-03 -1.68873435e-03 -1.02460552e-03 -4.27234784e-04 4.38408466e-05 3.43283242e-04 4.43309317e-04 3.35430765e-04 3.04868860e-05 -4.42972267e-04 -1.04175727e-03 -1.71225352e-03 -2.39538016e-03 -3.03184641e-03 -3.56724787e-03 -3.95657476e-03 -4.16776900e-03 -4.18405998e-03 -4.00492239e-03 -3.64562187e-03 -3.13543575e-03 -2.51474575e-03 -1.83128900e-03 -1.13591537e-03 -4.78229308e-04 9.75089362e-05 5.55882890e-04 8.72943226e-04 1.03776235e-03 1.05282547e-03 9.33266078e-04 7.05052077e-04 4.02314761e-04 6.40793507e-05 -2.69302192e-04 -5.59700440e-04 -7.74436926e-04 -8.88926887e-04 -8.88531054e-04 -7.69486161e-04 -5.38867392e-04 -2.13620600e-04 1.81219535e-04 6.14943586e-04 1.05398419e-03 1.46487975e-03 1.81712579e-03 2.08567371e-03 2.25287556e-03 2.30972824e-03 2.25633609e-03 2.10158077e-03 1.86205560e-03 1.56038302e-03 1.22308179e-03 8.78183614e-04 5.52812515e-04 2.70935874e-04 5.14731461e-05 -9.30892382e-05 -1.57477525e-04 -1.43761365e-04 -6.08496040e-05 7.65808001e-05 2.49608530e-04 4.36895576e-04 6.16649687e-04 7.68547449e-04 8.75454053e-04 9.24799747e-04 9.09508078e-04 8.28413800e-04 6.86154767e-04 4.92568115e-04 2.61662531e-04 1.02721050e-05 -2.43479579e-04 -4.81765292e-04 -6.88551514e-04 -8.50882173e-04 -9.59847193e-04 -1.01116567e-03 -1.00534965e-03 -9.47452001e-04 -8.46437746e-04 -7.14248804e-04 -5.64655741e-04 -4.12004401e-04 -2.69969696e-04 -1.50423395e-04 -6.25082253e-05 -1.19887444e-05 -9.22474985e-07 -2.76652008e-05 -8.71948709e-05 -1.71711753e-04 -2.71450537e-04 -3.75624708e-04 -4.73415723e-04 -5.54919647e-04 -6.11971626e-04 -6.38782772e-04 -6.32343297e-04 -5.92568032e-04 -5.22183857e-04 -4.26380806e-04 -3.12268004e-04 -1.88190415e-04 -6.29717152e-05 5.48482047e-05 1.57715444e-04 2.39646763e-04 2.96652381e-04 3.26968676e-04 3.31090312e-04 3.11609038e-04 2.72882654e-04 2.20570999e-04 1.61085330e-04 1.01002501e-04 4.64957021e-05 2.82941136e-06 -2.60417625e-05 -3.77416223e-05 -3.15924150e-05 -8.58395309e-06 2.87976107e-05 7.68594287e-05 1.31079230e-04 1.86533662e-04 2.38338818e-04 2.82065134e-04 3.14093089e-04 3.31882943e-04 3.34140421e-04 3.20869991e-04 2.93317172e-04 2.53810532e-04 2.05521707e-04 1.52167589e-04 9.76822284e-05 4.58869791e-05 1.85968102e-07 -3.66895801e-05 -6.28698992e-05 -7.74562747e-05 -8.05468297e-05 -7.31806289e-05 -5.72086133e-05 -3.51052202e-05 -9.73848540e-06 1.58812526e-05 3.88532870e-05 5.66193512e-05 6.71599779e-05 6.91407007e-05 6.19988370e-05 4.59664915e-05 2.20303956e-05 -8.16611985e-06 -4.24696335e-05 -7.83990087e-05 -1.13348819e-04 -1.44797941e-04 -1.70507493e-04 -1.88693513e-04 -1.98161865e-04]
Поскольку я собираюсь реализовать этот фильтр в ПЛИС, то использование плавающей точки хоть и возможно, но скорее всего неоправданно. Можно отмасштабировать эти коэффициенты хотя бы к знаковому 16ти битному значению. Тут нужно понимать, что всякая реализация требует ресурсов: памяти, регистров, умножителей. Нужно найти компромисс между точностью, занятыми ресурсами, временем, затраченным на реализацию проекта.
Вот у меня в этом фильтре сейчас 501 коэффициент фильтра. Значит и для хранения входных отсчетов нужно столько же регистров. В FPGA можно построить такую цепочку:

Умножаю 501 на 16 бит сигнала получается 8016 триггеров/логических элементов будет занято в ПЛИС. Довольно много так-то. Какие еще есть варианты? Можно использовать встроенную в ПЛИС статическую память.

Выделяю в FPGA два блока памяти длиной 512 элементов: один для входящих сигналов, а второй для хранения коэффициентов фильтра. Блоки памяти в виде циклических буферов.
Программа, показанная выше, так же напечатает содержимое MIF файла, для блока памяти Альтеры, используемой в FPGA.
Каждый приходящий новый отсчет сигнала записывается по указателю записи, который потом инкрементируется. Но! Пока не пришёл следующий отсчет сигнала нужно быстренько пробежаться по всему буферу и перечитать его полностью. Так же пробежаться по массиву коэффициентов и тоже перечитать его попутно производя перемножения и суммирование результатов. Если рабочая частота у меня будет 24МГц, то частота оцифровки получится 24000000/512=46875Гц. Вполне подходит для оцифровки звука и моих простых экспериментов. При такой частоте оцифровки по теоремме Котельникова я могу рассчитывать на обработку звуковых сигналов до 23,4КГц.
Реализую такой фильтр в ПЛИС на языке Verilog HDL (кстати, когда уже Хабр внедрит в свой редактор поддержку Verilog HDL?).
`timescale 1ns/1ps module fir_filter( input wire nreset, input wire clk, //idata sampling frequency input wire [15:0]idata, //samples unsigned output reg signed [47:0]out_val, output reg out_ready ); reg [8:0]rd_addr; reg [8:0]wr_addr; //read samples from cyclic buffer always @(posedge clk or negedge nreset) if( ~nreset ) rd_addr <= 0; else rd_addr <= rd_addr + 1; //"wr" signal -> writes new sample to cyclic buffer reg wr; always @(posedge clk or negedge nreset) if( ~nreset ) wr <= 1'b0; else wr <= (rd_addr==9'h1ff); always @(posedge clk or negedge nreset) if( ~nreset ) wr_addr <= 0; else if( rd_addr==9'h1ff ) wr_addr <= wr_addr + 1; wire signed [15:0]odata; //cyclic buffer for samples `ifdef ICARUS dp_mem_1clk_p #( .DATA_WIDTH(16), .ADDR_WIDTH(9), .RAM_DEPTH(1 << 9) )mem_samples ( .Clk( clk ), .Reset_N( nreset ), .we( wr ), .rd( nreset ), .wr_addr( wr_addr ), .rd_addr( rd_addr ), .data_in( idata ), .data_out( odata ) ); `else fir_ram fir_ram_inst ( .clock ( clk ), .data ( idata ), .rdaddress ( rd_addr ), .wraddress ( wr_addr ), .wren ( wr ), .q ( odata ) ); `endif //fir coefficients contiguously extracted from filter embeddef memory wire [8:0]rd_addr_coeff; assign rd_addr_coeff = 512 - rd_addr + wr_addr; wire signed [15:0]fir_coeff; `ifdef ICARUS dp_mem_1clk_p #( .DATA_WIDTH(16), .ADDR_WIDTH(9), .RAM_DEPTH(1 << 9) )mem_coeff ( .Clk( clk ), .Reset_N( nreset ), .we( 1'b0 ), .rd( nreset ), .wr_addr( 9'd0 ), .rd_addr( rd_addr_coeff ), .data_in( 16'd0 ), .data_out( fir_coeff ) ); `else fir_rom fir_rom_inst ( .address ( rd_addr_coeff ), .clock ( clk ), .q ( fir_coeff ) ); `endif reg signed [47:0]filter_val_acc; always @(posedge clk or negedge nreset) if( ~nreset ) begin out_val <= 0; filter_val_acc <=0; end else if( wr ) begin out_val <= filter_val_acc; filter_val_acc <= fir_coeff * odata; end else begin filter_val_acc <= filter_val_acc + fir_coeff * odata; end always @( posedge clk or negedge nreset ) if( ~nreset ) out_ready <= 0; else out_ready <= wr; endmodule
А для проверки работоспособности этого безобразия напишу тестбенч Verilog:
`timescale 1ns / 1ps module tb(); reg reset; //assume basic clock is 10Mhz reg clk; initial clk=0; always #50 clk = ~clk; //fir clk is sampling freq * 512 //sampling freq is 46875 reg fir_clk; initial fir_clk=0; always #20.833 fir_clk = ~fir_clk; //function calculating sinus function real sin; input x; real x; real x1,y,y2,y3,y5,y7,sum,sign; begin sign = 1.0; x1 = x; if (x1<0) begin x1 = -x1; sign = -1.0; end while (x1 > 3.14159265/2.0) begin x1 = x1 - 3.14159265; sign = -1.0*sign; end y = x1*2/3.14159265; y2 = y*y; y3 = y*y2; y5 = y3*y2; y7 = y5*y2; sum = 1.570794*y - 0.645962*y3 + 0.079692*y5 - 0.004681712*y7; sin = sign*sum; end endfunction //generate requested "freq" digital integer freq; reg [31:0]cnt; reg cnt_edge; always @(posedge clk or posedge reset) begin if(reset) begin cnt <=0; cnt_edge <= 1'b0; end else if( cnt>=(10000000/(freq*64)-1) ) begin cnt<=0; cnt_edge <= 1'b1; end else begin cnt<=cnt+1; cnt_edge <= 1'b0; end end real my_time; real sin_real; reg signed [15:0]sin_val; //generate requested "freq" sinus always @(posedge cnt_edge) begin sin_real <= sin(my_time); sin_val <= sin_real*32767; //fit 16bit signed short word my_time <= my_time+3.14159265*2/64; end wire signed [47:0]out; wire out_rdy; fir_filter fir_( .nreset( ~reset ), .clk( fir_clk ), .idata( sin_val ), .out_val( out ), .out_ready( out_rdy ) ); wire signed [15:0]out_val_; assign out_val_ = out[35:20]; initial begin $dumpfile("out.vcd"); $dumpvars(1, tb.freq, tb.sin_val, tb.out_val_ ); reset = 1; #1000; reset = 0; read_bp_coeff(); my_time=0; for ( freq=300; freq<4000; freq=freq+100 ) begin #20000000; if( freq>1000 && freq<2000 ) freq=freq+200; end $finish; end integer file_filter; integer i; integer scan_result; reg signed [15:0]coeff; task read_bp_coeff; begin file_filter = $fopen("python/fir-coeffs.txt", "r"); if (file_filter == 0) begin $display("file bp filter handle was NULL"); $finish; end for( i=0; i<512; i=i+1 ) begin scan_result = $fscanf(file_filter, "%d\n", coeff); if ( scan_result!=1 ) coeff = 0; //$display("coeff %d = %d",i,coeff); fir_.mem_coeff.mem[i] = coeff; end $fclose(file_filter); end endtask endmodule
Тут должен заметить, что симулировать проект я буду в Icarus Verilog, мне так проще, а он естественно не знает про существование Алтеровских блоков памяти. Поэтому я в проект вставляю их "эквивалент" типа dp_mem_1clk_p.v. Тестбенч считает текстовый файл python/fir-coeffs.txt" с коэффициентами фильтра, которые ранее были найдены с помощью питоновской программы и запишет эти значения в память fir_.mem_coeff.mem. Ну и потом только тестбенч начнет работать. Он вычисляет синусоидальный сигнал меняя его от 300Гц до 4000Гц и подает на фильтр. Вообще у меня в проекте используется условная компиляция по макросу ICARUS. Если он определен, то используются простые модули памяти dp_mem. А если макрос не определен, то будут использоваться блоки памяти сгенерированные в альтеровском MegaWizard. В этом визарде для блоков памяти типа ALT_DP_ROM можно задавать Memory Initialization FIle (MIF), который у меня уже сгенерирован программой на Python.
Запустить icarus verilog можно вот такой последовательностью команд (компиляция, симуляция, отображение):
>iverilog -DICARUS=1 -o tb.bin tb.v fir_filter.v dp_mem_1clk_p.v >vvp tb.bin >gtkwave out.vcd
Симулятор vvp во время симуляции сгенерирует файл изменений сигналов проекта во времени out.vcd. Его передаем в GtkWave. Тут я смогу рассмотреть выход фильтра:

И вот еще:

Судя по симулятору всё работает как нужно. Нужные частоты проходят через фильтр, а не нужные частоты подавляются фильтром. В тестбенче сигнал sin_val это синусоида которая подается на фильтр, а out_val_ это выход фильтра.
Теперь вопрос, как это будет работать в реальном железе.
Я использую плату FPGA MCY316 с USB-JTAG программатором MBFTDI. На плате стоит ПЛИС Altera Cyclone III с почти 16ю тысячами логических элементов. Ну и на плате стоит звуковое АЦП PCM1801 и аудио разъемы для подключения сигнала.
Плата MCY316 с установленным программатором выглядит вот так:

Полное описание платы можно почитать вот здесь.
Я собираюсь подключить мой смартфон аудио кабелем к этой плате. На смартфоне Андроид я установил программу генератора сигналов и буду подавать синусоидальный сигнал разной частоты в плату. Собственно я это уже делал в своем предыдущем проекте, почитайте пожалуйста вот здесь. Но теперь проект усовершенствован, в него добавлен цифровой полосовой фильтр описанный выше. Проект, работающий в FPGA, будет получать сигнал от АЦП, установленной на плате, и передавать на фильтр. Далее в компьютер будет передаваться левый канал как чистый сигнал, а правый канал как отфильтрованный сигнал. А в программе на питоне отрисуем графики обоих сигналов. Вот что получается:
В смартфоне на лету меняю частоту генератора и вижу на экране компьютера в окне плота, как выглядит прямой и отфильтрованный сигнал. Все работает! Частоты до 600Гц и выше 2200Гц не проходят фильтр, а остальные проходят!
Все исходники проектов можно взять на гитхаб.
Вот такие интересные эксперименты можно проводить с FPGA и обычным смартфоном. Добавлю, что уверен, что этот проект должен / будет работать на более дешевой плате MCY112.
И ещё, если в FPGA проект вставить три таких фильтра, но только на нижние частоты, средние и высокие, то есть подобрать соответствующие коэффициенты для этих фильтров, то можно сделать цветомузыку. Особенно эфектно она может смотреться при управлении адресной светодиодной лентой. Впрочем, я такое уже когда-то делал для другой платы.
