Эксперименты с цифровым КИХ фильтром в плате FPGA MCY316

от автора

Схема эксперимента

Схема эксперимента

В своей предыдущей статье я написал, как произвести оцифровку звукового сигнала платой FPGA MCY316. В том проекте данные полученные из АЦП просто передавались в компьютер через последовательный порт. Уже на компьютере данные принимались из последовательного порта программой на питоне и отображались графиками в окне.

А сейчас я хочу модифицировать этот проект и добавить в FPGA еще цифровой КИХ фильтр, чтобы разобраться, как он работает.

Рисунок выше показывает схему моего эксперимента.

КИХ фильтр это фильтр с конечной импульсной характеристикой. Если на такой фильтр подается короткий импульс, то на его выходе через некоторое время от сигнала не останется и следа. А всё потому, что описывается фильтр вот каким математическим выражением:

F_{t}=\sum_{k=0}^{N-1} Y_{t-k}\cdot S_{t}

Здесь 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_ramfir_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. Тут я смогу рассмотреть выход фильтра:

Результат симуляции фильтра, частоты от 300Гц до 900Гц

Результат симуляции фильтра, частоты от 300Гц до 900Гц

И вот еще:

Результат симуляции фильтра, частоты от 1700Гц до 2500Гц

Результат симуляции фильтра, частоты от 1700Гц до 2500Гц

Судя по симулятору всё работает как нужно. Нужные частоты проходят через фильтр, а не нужные частоты подавляются фильтром. В тестбенче сигнал sin_val это синусоида которая подается на фильтр, а out_val_ это выход фильтра.

Теперь вопрос, как это будет работать в реальном железе.

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

Плата MCY316 с установленным программатором выглядит вот так:

Полное описание платы можно почитать вот здесь.

Я собираюсь подключить мой смартфон аудио кабелем к этой плате. На смартфоне Андроид я установил программу генератора сигналов и буду подавать синусоидальный сигнал разной частоты в плату. Собственно я это уже делал в своем предыдущем проекте, почитайте пожалуйста вот здесь. Но теперь проект усовершенствован, в него добавлен цифровой полосовой фильтр описанный выше. Проект, работающий в FPGA, будет получать сигнал от АЦП, установленной на плате, и передавать на фильтр. Далее в компьютер будет передаваться левый канал как чистый сигнал, а правый канал как отфильтрованный сигнал. А в программе на питоне отрисуем графики обоих сигналов. Вот что получается:

В смартфоне на лету меняю частоту генератора и вижу на экране компьютера в окне плота, как выглядит прямой и отфильтрованный сигнал. Все работает! Частоты до 600Гц и выше 2200Гц не проходят фильтр, а остальные проходят!

Все исходники проектов можно взять на гитхаб.

Вот такие интересные эксперименты можно проводить с FPGA и обычным смартфоном. Добавлю, что уверен, что этот проект должен / будет работать на более дешевой плате MCY112.

И ещё, если в FPGA проект вставить три таких фильтра, но только на нижние частоты, средние и высокие, то есть подобрать соответствующие коэффициенты для этих фильтров, то можно сделать цветомузыку. Особенно эфектно она может смотреться при управлении адресной светодиодной лентой. Впрочем, я такое уже когда-то делал для другой платы.


ссылка на оригинал статьи https://habr.com/ru/articles/766342/