本文章使用Xilinx的fft ip完成了經(jīng)過參數(shù)化的任意個信號的基頻測量,完整代碼以及代碼解釋在文章中給出。如有錯誤,希望指出。
SIGNAL_NUM = 2, //*需要檢測的信號個數(shù)
FFT_LEN = 8192,//*fft運算采樣長度
FFT_WIDTH = 32, //*fft ip輸出數(shù)據(jù)寬度,實部和虛部位寬為FFT_WIDTH/2
SAMPLE_RATE = 50, //*ADC采樣率,單位Mhz,比如此時為50Mhz
ADC_WIDTH = 16, //*ADC數(shù)據(jù)位寬
FFT_CONFIG_WIDTH = 8 //*FFT ip的配置信號位寬(未使用)
ps:只使用了fft ip進行頻率測量未測量幅度,為防止錯誤不對幅度測量進行講解,且幅度測量因為未使用其中獲得的幅度是未經(jīng)處理的原始數(shù)據(jù),直接使用應(yīng)該會出現(xiàn)問題。該代碼無法滿足所有場景需求,僅起到拋磚引玉的作用。
1.FFT ip的配置
對于FFT ip核的各個配置的介紹在網(wǎng)上已經(jīng)有很多,故不再詳細解釋各個配置定義,直接附上我對這個ip的配置。

因為使用ADC為20M采樣率,需要測到kHz級別的精度,所以這里目標(biāo)時鐘速率為20Mhz,采樣點數(shù)為8192。這里根據(jù)自己需求設(shè)定即可。

2.FFT ip的使用介紹
FFT ip輸入為時域信息,而輸出為信號的頻域信號,所以需要自己針對頻域信號來分析出傅里葉變換后的結(jié)果,在FFT ip配置中勾選了XK_INDEX這里后面將配合測試出信號的頻率。
對于fft ip直接輸出的實部數(shù)據(jù)和虛部數(shù)據(jù)是不可以直接進行頻率的獲取,需要經(jīng)過一部分處理才可以得到信號的頻譜,傅里葉變換的結(jié)果是一個復(fù)數(shù),包括實部和虛部,信號的頻譜是通過計算復(fù)數(shù)的模得到的。對于信號頻譜的計算,按以下公式: abs(fft_P)= sqrt(實部的平方+虛部的平方)
然后根據(jù)這個得到的頻譜,得到最大值的點,這個點轉(zhuǎn)換后對應(yīng)的頻率,就是信號的頻率,比如對于20k的信號,會在x_index指代20k的地方幅度達到最大值,而對于多個信號,比如一個混合信號(20k的正弦波+50k的正弦波),這樣就檢查這個頻譜的兩個最大值的位置,就可以得到這兩個信號的頻率。
得到實部虛部的平方和并使用vivado的cordic ip進行求根獲得絕對值:
always @(posedge clk) begin : re2_im2_end
if(rst) begin
fft_im_end <= 'd0;
fft_re_end <= 'd0;
fft_end <= 'd0;
end
else if(m_axis_data_tvalid) begin
fft_im_end <= $signed(fft_im[ADC_WIDTH-1:0])*$signed(fft_im[ADC_WIDTH-1:0]); //*one
fft_re_end <= $signed(fft_re[ADC_WIDTH-1:0])*$signed(fft_re[ADC_WIDTH-1:0]);
fft_end <= fft_im_end + fft_re_end; //*two
end
end
cordic_0 u_cordic (
.aclk (clk ),// input wire aclk
.aresetn (~rst ),// input wire aresetn
.s_axis_cartesian_tvalid (cordic_valid_reg[1] ),// input wire s_axis_cartesian_tvalid
.s_axis_cartesian_tdata (fft_end ),// input wire [39 : 0] s_axis_cartesian_tdata
.m_axis_dout_tvalid (cordic_valid ),// output wire m_axis_dout_tvalid
.m_axis_dout_tdata (cordic_data ) // output wire [23 : 0] m_axis_dout_tdata
);
獲得檢測頻率使用的頻譜后,傳到下面一個自定義的信號處理部分,這部分主要是根據(jù)信號的頻譜,獲取頻譜的最大值位置(如果有多個信號就獲取多個極值點),之后根據(jù)這個位置對應(yīng)的x_index來計算出信號的頻率。
比如對于50Mhz的采樣率,8192點的采樣深度,這樣的話每一個x_index值對應(yīng)的頻率便是50M/8192≈6.1kHz,這樣的話FFT運算精度大概就在6kHz
,比如對于X_index值未2的點,該信號的頻率就是12kHz。
下面為得到x_index后,根據(jù)采樣率的參數(shù)定義來計算出具體的信號頻率部分代碼。
always @(posedge clk) begin : result
if (rst) begin
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
post_fft_freq[i] <= 0;
post_fft_mag[i] <= 0;
post_fft_index[i] <= 0;
end
end
else if(max_valid) begin
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
post_fft_freq[i] <= SAMPLE_RATE*detect_index[i]*(1000>>($clog2(FFT_SIZE)-10));
//乘1000為M轉(zhuǎn)K單位,因為MHz相比于KHz后面多3個0,所以乘1000轉(zhuǎn)換單位,其中FFT_SIZE為采樣深度
//右移的時候-10是為了左移10bit,左移十位為了表示為低10位代表小數(shù),高位代表整數(shù)
post_fft_mag[i] <= detect_data[i];
post_fft_index[i] <= detect_index[i];
end
end
end
always @(posedge clk) begin
if(rst) begin
for(integer i = 0;i < SIGNAL_NUM;i++) begin
{fft_int[i],fft_real[i]} <= 0;
fft_mag[i] <= 0;
end
end
else if(post_fft_valid) begin
for(integer i = 0;i < SIGNAL_NUM;i++) begin
{fft_int[i],fft_real[i]} <= post_fft_freq[i];//*計算出信號頻率后,高六位為整數(shù)部分,后面為小數(shù)部分
fft_mag[i] <= post_fft_mag[i];
end
end
end
3.代碼仿真
為了便于觀察出仿真結(jié)果,所以使用了兩個Xilinx的dds ip分別輸出了20kHz的信號和50kHz的信號,然后將這兩個信號的輸出混合后作為輸入,一次性測量兩個信號的頻率。參數(shù)配置如文章頂部的參數(shù),未作更改。
下圖為輸入的20kHz和50kHz信號,混合信號為這兩個信號之和:
下圖為獲得的頻譜實部和虛部以及將他們求絕對值后的結(jié)果,可以看出求出實部虛部的模之后即為兩個尖峰,分別對應(yīng)兩個不同的頻率:
下圖為經(jīng)過處理后獲得的信號頻率,第一個為48.8kHz,第二個為18.3kHz,由于上面說明過在50M的采樣率下,只有6.1kHz的精度,所以只能不會完全準確在這里,但是在當(dāng)前精度下,精確測量出了50kHz和20kHz的信號。(要想提高精度需要提高采樣深度或者降低采樣率)
4.完整代碼
對于我認為可能比較難理解的地方在上面已做出解釋。
module fft_trans
#(
SIGNAL_NUM = 2,
FFT_LEN = 8192,
FFT_WIDTH = 32,
SAMPLE_RATE = 50,
ADC_WIDTH = 16,
FFT_CONFIG_WIDTH = 8
)
(
input wire clk,//65m clk
input wire rst,
input wire [ADC_WIDTH-1:0] adc_data ,
input wire adc_valid,
input wire adc_last ,
output reg [$clog2(FFT_LEN)-1:0] fft_index[SIGNAL_NUM-1:0],
output reg [ 6:0] fft_int [SIGNAL_NUM - 1:0] ,
output reg [ 9:0] fft_real [SIGNAL_NUM - 1:0] ,
output reg [16:0] fft_mag [SIGNAL_NUM - 1:0] ,//!需要根據(jù)cordic ip的變化而配置
output reg fft_valid ,
output wire fft_slave_ready ,
output wire freq_valid ,
output wire [$clog2(FFT_LEN)-1:0]freq_index ,
output wire [ 16:0] freq_data
);
// region:************parameter************
localparam FFT_SECTION = FFT_WIDTH/2;
// endregion:parameter
// region:************logic define************
//*fft signal
wire [$clog2(FFT_LEN)-1:0]x_index ;
wire [FFT_CONFIG_WIDTH-1:0]s_axis_config_tdata = 'd1 ;
wire s_axis_config_tvalid = 1'b1;
wire s_axis_config_tready ;
wire [FFT_SECTION-1:0]fft_im,fft_re ;
wire m_axis_data_tvalid ;
wire m_axis_data_tlast ;
//*fft end
reg [ADC_WIDTH*2-1:0]fft_im_end,fft_re_end ;
reg [ADC_WIDTH*2:0] fft_end ;
reg cordic_valid_reg [1:0] ;
reg sync_index_en [17:0] ;
//*detect signal
wire post_fft_valid ;
wire [16:0] post_fft_freq [SIGNAL_NUM - 1:0];
wire [16:0] post_fft_mag [SIGNAL_NUM - 1:0];
wire [$clog2(FFT_LEN)-1:0] post_fft_index [SIGNAL_NUM - 1:0] ;
wire cordic_valid ;
wire [ 16:0] cordic_data ;
wire [$clog2(FFT_LEN)-1:0]x_index_sync ;
// endregion:logic define
// region:************assign************
assign freq_data = cordic_data; //*freq_data
assign freq_index = x_index_sync;
assign freq_valid = cordic_valid;
assign fft_index[0] =( post_fft_index[0]>post_fft_index[1]) ? post_fft_index[0] : post_fft_index[1];
assign fft_index[1] = (post_fft_index[1]<post_fft_index[0]) ? post_fft_index[1] : post_fft_index[0];
// endregion:assign
always @(posedge clk) begin : re2_im2_end
if(rst) begin
fft_im_end <= 'd0;
fft_re_end <= 'd0;
fft_end <= 'd0;
end
else if(m_axis_data_tvalid) begin
fft_im_end <= $signed(fft_im[ADC_WIDTH-1:0])*$signed(fft_im[ADC_WIDTH-1:0]); //*one
fft_re_end <= $signed(fft_re[ADC_WIDTH-1:0])*$signed(fft_re[ADC_WIDTH-1:0]);
fft_end <= fft_im_end + fft_re_end; //*two
end
end
always @(posedge clk) begin:cordic_valid_generate
if(rst) begin
for(integer i = 0;i < 2;i++) begin
cordic_valid_reg[i] <= 'd0;
end
end
else begin
cordic_valid_reg[1] <= cordic_valid_reg[0];
cordic_valid_reg[0] <= m_axis_data_tvalid;
end
end
always @(posedge clk) begin:sync_index
if(rst) begin
for(integer i = 0;i < 18;i++) begin
sync_index_en[i] <= 'd0;
end
end
else begin
for(integer i = 0;i < 18;i++) begin
if (i== 0) begin
sync_index_en[i] <= m_axis_data_tvalid;
end
else begin
sync_index_en[i] <= sync_index_en[i-1];
end
end
end
end
//*out signals
always @(posedge clk) begin
if(rst) begin
for(integer i = 0;i < SIGNAL_NUM;i++) begin
{fft_int[i],fft_real[i]} <= 0;
fft_mag[i] <= 0;
end
end
else if(post_fft_valid) begin
for(integer i = 0;i < SIGNAL_NUM;i++) begin
{fft_int[i],fft_real[i]} <= post_fft_freq[i];
fft_mag[i] <= post_fft_mag[i];
end
end
end
always @(posedge clk) begin
if(rst) begin
fft_valid <= 1'b0;
end
else begin
fft_valid <= post_fft_valid;
end
end
xfft_0 u_xfft_0(
.aclk (clk ),
.aresetn (~rst ),
//*fft配置信號
.s_axis_config_tdata (s_axis_config_tdata ),
.s_axis_config_tvalid (s_axis_config_tvalid ),
.s_axis_config_tready (s_axis_config_tready ),
//*前級adc傳入數(shù)據(jù)
.s_axis_data_tdata ({{{FFT_WIDTH-ADC_WIDTH}{1'b0}},adc_data}),
.s_axis_data_tvalid (adc_valid ),
.s_axis_data_tready (fft_slave_ready ),
.s_axis_data_tlast ( ),
//*out channel
.m_axis_data_tdata ({fft_im,fft_re} ),
.m_axis_data_tuser (x_index ),
.m_axis_data_tvalid (m_axis_data_tvalid ),
.m_axis_data_tlast (m_axis_data_tlast ),
//*status channel
.m_axis_status_tdata ( ),
.m_axis_status_tvalid ( ),
//*event channel
.event_frame_started ( ),
.event_tlast_unexpected ( ),
.event_tlast_missing ( ),
.event_data_in_channel_halt ( )
);
//*17 latency
cordic_0 u_cordic (
.aclk (clk ),// input wire aclk
.aresetn (~rst ),// input wire aresetn
.s_axis_cartesian_tvalid (cordic_valid_reg[1] ),// input wire s_axis_cartesian_tvalid
.s_axis_cartesian_tdata (fft_end ),// input wire [39 : 0] s_axis_cartesian_tdata
.m_axis_dout_tvalid (cordic_valid ),// output wire m_axis_dout_tvalid
.m_axis_dout_tdata (cordic_data ) // output wire [23 : 0] m_axis_dout_tdata
);
fifo_generator_0 u_fifo_generator_0(
.clk (clk ),
.srst (rst ),
.din (x_index ),
.wr_en (m_axis_data_tvalid ),
.rd_en (sync_index_en[17] ),//!根據(jù)cordic ip的變化而配置
.dout (x_index_sync ),
.full ( ),
.empty ( )
);
signal_detect
#(
.FFT_SIZE (FFT_LEN ),
.FFT_SIGNAL_NUM (SIGNAL_NUM ),
.SAMPLE_RATE (SAMPLE_RATE )
)
u_signal_detect(
.clk (clk ),
.rst (rst ),
.fft_data (cordic_data ),
.fft_index (x_index_sync ),
.fft_valid (cordic_valid ),
.post_fft_index (post_fft_index ),
.post_fft_valid (post_fft_valid ),
.post_fft_freq (post_fft_freq ),
.post_fft_mag (post_fft_mag )
);
endmodule //fft_trans
在例化中除了signal_detect均為Xilinx的 ip,其中cordic_0 實現(xiàn)了求根,xfft_0 為fft ip,fifo_generator_0 為一個異步FIFO用于數(shù)據(jù)對齊,下面給出signal_detect代碼
module signal_detect
#(
parameter FFT_SIZE = 4096,
parameter FFT_SIGNAL_NUM = 1,
parameter integer SAMPLE_RATE = 'd100 //100MHz,用來計算頻率
)
(
input wire clk,
input wire rst,
input wire [16:0] fft_data,//!輸入的cordic數(shù)據(jù)
input wire [$clog2(FFT_SIZE)-1:0] fft_index,
input wire fft_valid,
output reg [$clog2(FFT_SIZE)-1:0] post_fft_index [FFT_SIGNAL_NUM],
output reg post_fft_valid,
output reg [16:0] post_fft_freq [FFT_SIGNAL_NUM-1:0], //*高16bit為整數(shù),低10bit為小數(shù)
output reg [16:0] post_fft_mag [FFT_SIGNAL_NUM-1:0] //*低位最低,高位最高
);
localparam DETECT_RANGE = FFT_SIZE/2;
reg [$clog2(FFT_SIZE)-1:0] detect_index [FFT_SIGNAL_NUM-1:0];
//wire [$clog2(FFT_SIGNAL_NUM):0] detect_cnt;
reg [16:0] detect_data [FFT_SIGNAL_NUM-1:0];
reg [$clog2(DETECT_RANGE):0] cnt;
reg [4:0] state;
reg max_valid;
//*1 state design
always @(posedge clk) begin
if (rst) begin
cnt <= {$clog2(DETECT_RANGE){1'b0}};
state <= 0;
end
else begin
case (state)
0: if (fft_valid && (fft_index == 0)) begin//*one clk delay
cnt <= 0;
state <= 1;
end
else begin
state <= 0;
end
1: if (fft_valid && (cnt < DETECT_RANGE)) begin
cnt <= cnt + 1;
state <= 1;
end
else if(cnt == DETECT_RANGE)begin
state <= 2;
cnt <= 0;
end
2: begin
state <= 0;
cnt <= 0;
end
default: begin
state <= 0;
cnt <= 0;
end
endcase
end
end
//*2 output design
generate if(FFT_SIGNAL_NUM == 1) begin
always @(posedge clk) begin
if (rst) begin
detect_data[0] <= 0;
detect_index[0] <= 0;
max_valid <= 0;
end
else begin
case (state)
0: begin
detect_data[0] <= 0;
detect_index[0] <= 0;
max_valid <= 0;
end
1: begin //*只捕獲一個信號時
if (cnt == DETECT_RANGE-1) begin
max_valid <= 1'b1;
end
else begin
max_valid <= 0;
end
if (fft_data > detect_data[0]) begin
detect_data[0] <= fft_data;
detect_index[0] <= cnt+1;
end
end
2: begin
max_valid <= 0;
detect_data[0] <= 0;
detect_index[0] <= 0;
end
default: begin
max_valid <= 0;
detect_data[0] <= 0;
detect_index[0] <= 0;
end
endcase
end
end
end
else begin //*捕獲多個信號時
always @(posedge clk) begin
if (rst) begin
max_valid <= 0;
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
detect_data[i] <= 0;
detect_index[i] <= 0;
end
end
else begin
case (state)
0: begin
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
detect_data[i] <= 0;
detect_index[i] <= 0;
end
max_valid <= 0;
end
1: begin
if( fft_data > detect_data[0] && detect_data[0] <= detect_data[1]) begin
detect_data[0] <= fft_data;
detect_index[0] <= cnt + 'd1;
end
else if(fft_data > detect_data[1] && detect_data[1] <= detect_data[0]) begin
detect_data[1] <= fft_data;
detect_index[1] <= cnt + 'd1;
end
if (cnt == DETECT_RANGE-1) begin
max_valid <= 1'b1;
end
else begin
max_valid <= 0;
end
end
2: begin
max_valid <= 0;
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
detect_data[i] <= 0;
detect_index[i] <= 0;
end
end
default: begin
max_valid <= 0;
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
detect_data[i] <= 0;
detect_index[i] <= 0;
end
end
endcase
end
end
end
endgenerate
//*output
always @(posedge clk) begin : result
if (rst) begin
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
post_fft_freq[i] <= 0;
post_fft_mag[i] <= 0;
post_fft_index[i] <= 0;
end
end
else if(max_valid) begin
for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
post_fft_freq[i] <= SAMPLE_RATE*detect_index[i]*(1000>>($clog2(FFT_SIZE)-10)); //<<10/1024 //*1000為M轉(zhuǎn)K單位
post_fft_mag[i] <= detect_data[i];
post_fft_index[i] <= detect_index[i];
end
end
end
always @(posedge clk) begin : valid
if (rst) begin
post_fft_valid <= 0;
end
else if(max_valid) begin
post_fft_valid <= 1;
end
else begin
post_fft_valid <= 0;
end
end
endmodule //signal_detect
如有疑問或者文章中有錯誤,請在評論區(qū)指出??。文章來源:http://www.zghlxwxcb.cn/news/detail-809501.html
參考鏈接:利用Vivado的 FFT IP 核估計信號的幅度和頻率
文章來源地址http://www.zghlxwxcb.cn/news/detail-809501.html
到了這里,關(guān)于基于Xilinx vivado FFT ip進行信號頻譜測量的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!