本文章使用Xilinx的fft ip完成了经过参数化的任意个信号的基频测量,完整代码以及代码解释在文章中给出。如有错误,希望指出。
SIGNAL_NUM = 2, //*需要检测的信号个数
FFT_LEN = 8192,//*fft运算采样长度
FFT_WIDTH = 32, //*fft ip输出数据宽度,实部和虚部位宽为FFT_WIDTH/2
SAMPLE_RATE = 50, //*ADC采样率,单位Mhz,比如此时为50Mhz
ADC_WIDTH = 16, //*ADC数据位宽
FFT_CONFIG_WIDTH = 8 //*FFT ip的配置信号位宽(未使用)
ps:只使用了fft ip进行频率测量未测量幅度,为防止错误不对幅度测量进行讲解,且幅度测量因为未使用其中获得的幅度是未经处理的原始数据,直接使用应该会出现问题。该代码无法满足所有场景需求,仅起到抛砖引玉的作用。
1.FFT ip的配置
对于FFT ip核的各个配置的介绍在网上已经有很多,故不再详细解释各个配置定义,直接附上我对这个ip的配置。
因为使用ADC为20M采样率,需要测到kHz级别的精度,所以这里目标时钟速率为20Mhz,采样点数为8192。这里根据自己需求设定即可。
2.FFT ip的使用介绍
FFT ip输入为时域信息,而输出为信号的频域信号,所以需要自己针对频域信号来分析出傅里叶变换后的结果,在FFT ip配置中勾选了XK_INDEX这里后面将配合测试出信号的频率。
对于fft ip直接输出的实部数据和虚部数据是不可以直接进行频率的获取,需要经过一部分处理才可以得到信号的频谱,傅里叶变换的结果是一个复数,包括实部和虚部,信号的频谱是通过计算复数的模得到的。对于信号频谱的计算,按以下公式: abs(fft_P)= sqrt(实部的平方+虚部的平方)
然后根据这个得到的频谱,得到最大值的点,这个点转换后对应的频率,就是信号的频率,比如对于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
);
获得检测频率使用的频谱后,传到下面一个自定义的信号处理部分,这部分主要是根据信号的频谱,获取频谱的最大值位置(如果有多个信号就获取多个极值点),之后根据这个位置对应的x_index来计算出信号的频率。
比如对于50Mhz的采样率,8192点的采样深度,这样的话每一个x_index值对应的频率便是50M/8192≈6.1kHz,这样的话FFT运算精度大概就在6kHz
,比如对于X_index值未2的点,该信号的频率就是12kHz。
下面为得到x_index后,根据采样率的参数定义来计算出具体的信号频率部分代码。
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转K单位,因为MHz相比于KHz后面多3个0,所以乘1000转换单位,其中FFT_SIZE为采样深度
//右移的时候-10是为了左移10bit,左移十位为了表示为低10位代表小数,高位代表整数
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];//*计算出信号频率后,高六位为整数部分,后面为小数部分
fft_mag[i] <= post_fft_mag[i];
end
end
end
3.代码仿真
为了便于观察出仿真结果,所以使用了两个Xilinx的dds ip分别输出了20kHz的信号和50kHz的信号,然后将这两个信号的输出混合后作为输入,一次性测量两个信号的频率。参数配置如文章顶部的参数,未作更改。
下图为输入的20kHz和50kHz信号,混合信号为这两个信号之和:
下图为获得的频谱实部和虚部以及将他们求绝对值后的结果,可以看出求出实部虚部的模之后即为两个尖峰,分别对应两个不同的频率:
下图为经过处理后获得的信号频率,第一个为48.8kHz,第二个为18.3kHz,由于上面说明过在50M的采样率下,只有6.1kHz的精度,所以只能不会完全准确在这里,但是在当前精度下,精确测量出了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] ,//!需要根据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传入数据
.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] ),//!根据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 实现了求根,xfft_0 为fft ip,fifo_generator_0 为一个异步FIFO用于数据对齐,下面给出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数据
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为整数,低10bit为小数
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转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
如有疑问或者文章中有错误,请在评论区指出💕。文章来源:https://www.toymoban.com/news/detail-809501.html
参考链接:利用Vivado的 FFT IP 核估计信号的幅度和频率
文章来源地址https://www.toymoban.com/news/detail-809501.html
到了这里,关于基于Xilinx vivado FFT ip进行信号频谱测量的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!