В своей предыдущей статье я написал, как произвести оцифровку звукового сигнала платой 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 проект вставить три таких фильтра, но только на нижние частоты, средние и высокие, то есть подобрать соответствующие коэффициенты для этих фильтров, то можно сделать цветомузыку. Особенно эфектно она может смотреться при управлении адресной светодиодной лентой. Впрочем, я такое уже когда-то делал для другой платы.
Комментарии (14)
aamonster
10.10.2023 08:12А можно не надо?
В смысле я понимаю, что у взятой ПЛИС есть дофига ресурсов, которые можно потратить на параллельное умножение 500 чисел, и FIR на неё идеально ложится, но не оптимизировать фильтры – это ж путь в пекло (ну в смысле тепловыделение всё больше). Конечно, это просто учебный пример, но дети ж поверят, что так и надо...
nckma Автор
10.10.2023 08:12Ну конкретно в этом примере нет параллельного умножения 500 чисел. Есть последовательное.
aamonster
10.10.2023 08:12Тем более :-) (в смысле, "бонусом" у нас ещё и понижение производительности из-за того, что используем последовательно 500 умножений там, где хватило бы десятка).
Впрочем, простите за невнимательность. Потом почитаю статью как следует, тема-то для меня интересная (давно хочу освоить) и примеры кажутся достаточно простыми.
yamifa_1234
10.10.2023 08:12А окно из 500 отсчётов скользящее? Или шагает по 500 отсчётов? Вы ыглядит как шагающее окно. И ещё вопрос, почему такой тип окна(скользящее или нет...) выбрали?
nckma Автор
10.10.2023 08:12Окно конечно скользящее. Одна выборка сигнала пришла и одна ушла.
А что бывает по другому?
yamifa_1234
10.10.2023 08:12Можно набирать по 500 отсчётов и выдавать по 500 отсчётов. Недостаток у такого режима в том что стыки между окон кривые будут. Ну или можно набирать новые 100 отсчётов а потом выдавать 100, в итоге получится шаг по 100 отсчётов, тогда стыки окон не будут такими плохими.
P.s. я начинаю чувствовать что сильно ошибаюсь...
aamonster
10.10.2023 08:12Это уже не FIR будет, а какая-то нелинейщина.
Формула с 500 отсчётами даёт одно значение на выходе. Так что чтобы получить 500 значений – вам надо 500 раз посчитать по ней, так что выгоды от работы по кускам нету (ну, есть другие фильтры, где есть – например, порезать по N отсчётов, бпф, фильтрация, обратное бпф – тут, понятно, по блокам надо)
Gudd-Head
10.10.2023 08:12частота оцифровки получится 24000000/512=46875Гц
Почему 512, если отчётов у вас только 501?
nckma Автор
10.10.2023 08:12Всё равно память в FPGA выделяется блоками. Нельзя сделать блок длиной 501 элементов.
Да и указатели циклического буфера легче перемещать. После ячейки по адресу 511 сразу идет ячейка по адресу ноль. Ну и коэффициенты по адресам выше 501 равны нулю.
alex_dow
10.10.2023 08:12Самый простой способ поиграть с ких - SigmaDSP ADAU1761, от чипдип, 1500р, все из коробки уже есть. Я на нем делал фонокорректор, ну и много чего там еще интересного.
И да нужен SigmaLink-USBi программатор 1700рnckma Автор
10.10.2023 08:12+1В мире вообще много чего есть готового и интересного. Но свой изобретенный велосипед всегда интересней и познавательней.
YakovK
КИХ фильтр это FIR?
nckma Автор
Да, КИХ Это фильтр с Конечной Импульсной Характеристикой. То же самое как FIR - Finite Impulse Response.