赞
踩
之前从来没有接触过FFT,但是工作需求,让我不得不对它下手。
具体的FFT的原理我不做过多说明,大概说下是做什么用的。简单说就是频谱分析,说的通俗点就是:一个50Hz的正弦波,对它进行FFT计算,可以得到以下一些信息: 1.该正弦波的频率 2.该正弦波的幅值
你可能会觉得这有什么用?但是你要知道,我输入进FFT的正弦波,事先你是不知道它的任何信息的,包括频率和幅值。FFT就相当于一个盲盒,你给我一杯水我还你一片湖泊。
好,你大概知道了FFT是干嘛的就好了,接下来说怎么用?本文是基于ALTERA的Quartus 18.0版本设计。
step1:产生正弦波
当然,如果你有信号发生器,那就简单多了,不必像我这么麻烦的调DAC,我是使用的查表法,利用ROM中存放的mif文件,依次通过DAC输出,产生的正弦波。(DAC部分的内容会单独给一个专题)
step2:采样正弦波
这个也是一个关键步骤,你需要足够了解ADC的工作时序,采样频率。将上一步产生的正弦波输入进ADC进行采样,转化后的数据保存到一个RAM中,供FFT调用。(ADC部分的内容同样会单独给一个专题)
step3:FFT计算
如果上面比较顺利,那么这里估计会耽误一点时间。因为调用FFT IP核的时候,会有很多的参数,需要很清楚这些参数的功能以及工作时序。
假设你现在已经采样到正弦波数据并且保存到RAM中,准备给FFT使用了,我们开始配置。
①找到FFT IP核
②配置相关参数
主要参数包括采样点数 数据模式 位宽等。这里需要注意,红框中的两个选项都要执行,不然运行会有报错。
IP核的配置基本就这些了,然后会生成可以例化的代码,如下
module fft3 ( input wire clk, // clk.clk IP核的参考时钟 input wire reset_n, // rst.reset_n 复位线 input wire sink_valid, // sink.sink_valid 输入数据有效信号 output wire sink_ready, // .sink_ready 表示FFT IP核准备好接收数据了,实测的时候该信号一直是高电平 input wire [1:0] sink_error, // .sink_error 输入报错标志 输入手动置0,不会有影响 input wire sink_sop, // .sink_sop 输入数据起始 input wire sink_eop, // .sink_eop 输入数据终止 input wire [15:0] sink_real, // .sink_real 输入数据的实部 input wire [15:0] sink_imag, // .sink_imag 输入数据的虚部 一般都会直接置0 input wire [0:0] inverse, // .inverse FFT模式选择 正计算置0 逆运算置1 output wire source_valid, // source.source_valid 输出数据有效 input wire source_ready, // .source_ready 准备好输出数据 一般直接拉高,表示一直准备着,随时可以输出数据 output wire [1:0] source_error, // .source_error 输出报错 output wire source_sop, // .source_sop 输出数据的起始 output wire source_eop, // .source_eop 输出数据终止 output wire [15:0] source_real, // .source_real 输出数据实部 output wire [15:0] source_imag, // .source_imag 输出数据虚部 output wire [5:0] source_exp // .source_exp 输出数据缩放因子,实际输出的实部和虚部需要乘以2^source_exp );
还是再上个图看看吧
这些就是在FFT计算的时候需要考虑的一些变量,需要做的就是控制几个输入的信号,来完成整个的FFT计算过程,那么这个就是需要参考各个信号的时序图了。
我们在配置IP核的时候,选择的模式是streaming,因此,找到官方文档查看时序图,如下:
将输入的波形放大,如下:
简单分析一下数据输入的时序流程: 当reset拉高后,sink_valid和sink_ready都被直接拉高,sink_valid是需要自己拉高的,sink_ready是IP核自己拉高的,这个值我实测好像是一直是高电平;inverse需要手动拉低;然后给sink_sop一个高脉冲,这时候数据就会开始往FFT中传输了;但是图中没有给出什么时候停止,当sink_eop拉高一个脉冲时,这一帧数据输入完成。FFT就会对sink_sop和sink_eop两个高脉冲之间输入的数据进行FFT计算。这之间的数据点数就是上面设置的1024,当然这个点数由你自己定,数据越多,精度会越高,占用内存也就越高。
然后看一下数据输出的时序图,如下:
和输入数据类似的:source_ready和sink_valid拉高,表示可以输出了,注意这里的source_ready是手动拉高的,默认一直是高,sink_valid是FFT核拉高的,实测好像一直是高;然后当数据可以输出时,FFT核会将source_sop拉高一个高脉冲,当输出完成1024个数据时,FFT核会将source_eop拉高一个高脉冲,表示一帧数据完成输出。这样就完成了对一帧数据的FFT计算,请注意,这里的一帧数据实际是要计算1024次的,因为有1024个点啊。
最终需要用到的数据就是source_real和source_imag。将这两个值进行平方和计算,然后再开根号,得到的结果我们暂时记作fft_out吧,就是我们需要使用的值。
具体怎么用呢?一开始我们设置了对1024个输入的点进行FFT计算,每一点都会计算得到一组source_real和source_imag,对应的就可以计算出一个fft_out;那么最终就会有1024个fft_out,我们需要在这1024个数据中找到fft_out最大的那个点,假设是第n个点(点号从0-1023计算),那么采样的这个波形的频率就是Fout=(n-1)*Fs/N;其中,Fout是需要测量的信号的频率,n是计算的点号,Fs是ADC的采样频率(这个会在ADC专题中说明),N是采样点数,就是这里的1024。
简单的流程说完了,上示例:
module FFT_Control_Module ( //输入端口 CLK_50M,RST_N,data_real_in_int, //输出端口 fft_bit_cnt,fft_x_cnt, vga_freq,vga_fengzhi, fft_out,sink_sop, sink_ready,sink_valid, sink_eop,fft_start,source_error,source_sop,source_eop,source_valid,max_cnt,position,data_max ); //--------------------------------------------------------------------------- //-- 外部端口声明 //--------------------------------------------------------------------------- input CLK_50M; input RST_N; input [ 15:0] data_real_in_int;//[ 9:0] output [10:0] fft_bit_cnt; output [10:0] fft_x_cnt; output [31:0] vga_freq; output [31:0] vga_fengzhi; output [ 15:0] fft_out;// output sink_sop; output sink_ready; output reg sink_valid; output sink_eop; output reg fft_start; output [1:0] source_error; output source_sop; output source_eop; output source_valid; output reg [10:0] max_cnt; output reg [10:0] position; output reg [15:0] data_max; //--------------------------------------------------------------------------- //-- 内部端口声明 //--------------------------------------------------------------------------- wire inverse; wire [ 15:0] data_imag_in_int;//[ 9:0] wire [ 9:0] fftpts_array; wire [ 1:0] sink_error; //wire sink_ready; //reg sink_valid; reg sink_valid_n; wire fft_ready_valid; reg [10:0] fft_bit_cnt; reg [10:0] fft_bit_cnt_n; //wire sink_sop; //wire sink_eop; reg [ 15:0] sink_real; reg [ 15:0] sink_real_n; reg [ 15:0] sink_imag; reg [ 15:0] sink_imag_n; wire end_input; reg end_test; reg end_test_n; wire source_ready; wire source_valid; wire [ 15:0] source_real; reg [ 15:0] fft_real_out_int; reg [ 15:0] fft_real_out_int_n; wire [ 15:0] source_imag; reg [ 15:0] fft_imag_out_int; reg [ 15:0] fft_imag_out_int_n; wire [ 5:0] source_exp; reg [ 5:0] exponent_out_int; reg [ 5:0] exponent_out_int_n; reg [31:0] fft_real_image_data; reg [31:0] fft_real_image_data_n; wire [15:0] sqrt_data_out; wire fft_read; //wire source_sop; reg [ 10:0] fft_x_cnt; reg [ 10:0] fft_x_cnt_n; //reg [ 15:0] data_max; reg [ 15:0] data_max_n; reg [ 15:0] data_max2; reg [ 15:0] data_max2_n; //reg [ 10:0] max_cnt; reg [ 10:0] max_cnt_n; reg [ 10:0] max_cnt2; reg [ 10:0] max_cnt2_n; reg [15:0] fengzhi_max; reg [15:0] fengzhi_max_n; reg [15:0] fengzhi_min; reg [15:0] fengzhi_min_n; wire [31:0] vga_freq; wire [31:0] vga_fengzhi; wire [ 10:0] fft_out; reg [15:0] frames_in_index; reg [15:0] frames_out_index; reg [9:0] position_r; //reg fft_start; reg fft_start_n; //--------------------------------------------------------------------------- //-- 逻辑功能实现 //--------------------------------------------------------------------------- /* FFT控制信号,1:IFFT,0:FFT */ assign inverse = 1'b0; /* FFT输入数据信号 */ assign data_imag_in_int = 16'b0; /* FFT数据的个数为1024 */ assign fftpts_array = 10'd1023; /* 输入错误信号 */ assign sink_error = 2'b0; //start valid for first cycle to indicate that the file reading should start. always@(posedge CLK_50M or negedge RST_N) begin if(!RST_N) fft_start <= 1'b1; else fft_start <= fft_start_n; end always@(*) begin if(fft_ready_valid)//sink_valid为1时该式成立,则fft_start_n变为0,也就是两个信号同时会有脉冲信号产生 fft_start_n = 1'b0; else fft_start_n = 1'b1; end //输入数据帧数 // count the input frames and increment the index always @(posedge CLK_50M or negedge RST_N) begin if (RST_N == 1'b0) frames_in_index <= 'd0; else begin if (sink_eop == 1'b1 & sink_valid == 1'b1 & sink_ready == 1'b1 & frames_in_index < 3000)输入数据的次数,即输入进1024个点就算一帧数据 frames_in_index <= frames_in_index + 1'b1; end end // count the output frames and increment the index 这个定义的是输入数据帧数,实际我没使用,而是一直输入进行FFT计算 always @(posedge CLK_50M or negedge RST_N) begin if (RST_N == 1'b0) frames_out_index <= 'd0; else begin if (source_eop == 1'b1 & source_valid == 1'b1 & source_ready == 1'b1 & frames_out_index < 3000) frames_out_index <= frames_out_index + 1'b1; end end /* 时序电路,用来给sink_valid寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) sink_valid <= 1'b0; else sink_valid <= sink_valid_n; end /* 组合电路,输入的有效信号 */ //***********************无效 always @ (*) begin if(end_test || end_input)//end_input = (sink_eop && fft_ready_valid) ? 1'b1 : 1'b0; sink_valid_n = 1'b0; else if(fft_ready_valid || (fft_start == 1'b1 & !(sink_valid == 1'b1 & sink_ready == 1'b0)) )//(fft_start & !(sink_valid && sink_ready == 1'b0))) sink_valid_n <= 1'b1; else sink_valid_n = 1'b1; end //*********************************/ //always@(*)//组合逻辑一般用阻塞赋值 //begin // if(end_test || end_input) // sink_valid_n = 1'b1; // else // sink_valid_n = 1'b1; //end /* 输入开始进行FFT转换运算 */ assign fft_ready_valid = (sink_valid && sink_ready);//sink_ready貌似都是正的 /* 时序电路,用来给fft_bit_cnt寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) fft_bit_cnt <= 10'b0; else fft_bit_cnt <= fft_bit_cnt_n; end /* 组合电路,FFT位计数器 */ always @ (*) begin if(fft_ready_valid && (fft_bit_cnt == fftpts_array)) fft_bit_cnt_n = 1'b0; else if(fft_ready_valid)//这个判断,一开始这里因为sink_valid为0进不来 fft_bit_cnt_n = fft_bit_cnt + 1'b1; else fft_bit_cnt_n = fft_bit_cnt; end /* 输入流的开始 */ assign sink_sop = (fft_bit_cnt == 1'b0) ? 1'b1 : 1'b0 ; /* 输入流的结束 */ assign sink_eop = (fft_bit_cnt == fftpts_array) ? 1'b1 : 1'b0; /* 时序电路,用来给sink_real寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) sink_real <= 10'b0; else sink_real <= sink_real_n; end /* 组合电路,输入的实部信号 */ always @ (*) begin if(end_test || end_input)//1024个数据已经输入完成了 sink_real_n = 10'b0; else if (fft_ready_valid)//(sink_valid && sink_ready) sink_real_n = data_real_in_int; else sink_real_n = sink_real; end /* 时序电路,用来给sink_imag寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) sink_imag <= 10'b0; else sink_imag <= sink_imag_n; end /* 组合电路,输入的虚部信号 */ always @ (*) begin if(end_test || end_input) sink_imag_n = 10'b0; else if (fft_ready_valid) sink_imag_n = data_imag_in_int; else sink_imag_n = sink_imag; end /* 停止输入信号 */ //signal when all of the input data has been sent to the DUT assign end_input = (sink_eop && fft_ready_valid&& frames_in_index == 3000) ? 1'b0 : 1'b0; /* 时序电路,用来给end_test寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) end_test <= 1'b0; else end_test <= end_test_n; end /* 组合电路,结束测试信号 */ always @ (*) begin if(end_input) end_test_n = 1'b1; else end_test_n = end_test; end /* 源准备信号 */ assign source_ready = 1'b1; /* 时序电路,用来给fft_real_out_int寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) fft_real_out_int <= 10'b0; else fft_real_out_int <= fft_real_out_int_n; end /* 组合电路,FFT输出的实部信号 */ always @ (*) begin if(source_valid && source_ready) fft_real_out_int_n = source_real[15] ? (~source_real[15:0]+1) : source_real; else fft_real_out_int_n = fft_real_out_int; end /* 时序电路,用来给fft_imag_out_int寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) fft_imag_out_int <= 10'b0; else fft_imag_out_int <= fft_imag_out_int_n; end /* 组合电路,FFT输出的虚部信号 */ always @ (*) begin if(source_valid && source_ready) fft_imag_out_int_n = source_imag[15] ? (~source_imag[15:0]+1) : source_imag; else fft_imag_out_int_n = fft_imag_out_int; end /* 时序电路,用来给exponent_out_int寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) exponent_out_int <= 10'b0; else exponent_out_int <= exponent_out_int_n; end /* 组合电路,用于生成FFT输出的指数信号 */ always @ (*) begin if(source_valid && source_ready) exponent_out_int_n = source_exp; else exponent_out_int_n = exponent_out_int; end /* 时序电路,用来给fft_real_image_data寄存器赋值 */ always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) fft_real_image_data <= 32'b0; else fft_real_image_data <= fft_real_image_data_n; end /* 组合电路,用于生成FFT输出的数据信号 */ always @ (*) begin if(source_valid && source_ready) fft_real_image_data_n = fft_real_out_int*fft_real_out_int + fft_imag_out_int*fft_imag_out_int; else fft_real_image_data_n = fft_real_image_data; end /* 平方根模块 */ SQRT_Module SQRT_Init ( .radical (fft_real_image_data ), .q (sqrt_data_out ) ); /* 开始进行FFT转换运算 */ assign fft_read = (source_valid && source_ready); /* 时序电路,用来给fft_x_cnt寄存器赋值 */ //***************找最大值可以用,但是最大位置值会复位累计 always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) begin position <= 1'b0; fft_x_cnt <= 1'b0; data_max <= 1'b0; end else begin position <= position_r; data_max <= data_max_n; fft_x_cnt <= fft_x_cnt_n; end end //********/ /* 组合电路,用于生成FFT输出数据的计数器 */ //**********找最大值可以用,但是最大位置值会复位累计 always @ (*) begin if(fft_ready_valid && (source_sop || (fft_x_cnt == fftpts_array)))begin//输出完成后 fft_x_cnt_n = 1'b0; position_r = 1'b0; data_max_n= 1'b0; end else if(fft_read) begin //assign fft_read = (source_valid && source_ready); fft_x_cnt_n = fft_x_cnt + 1'b1; if(sqrt_data_out > data_max && fft_x_cnt > 2 && fft_x_cnt < 512) begin data_max_n= sqrt_data_out; position_r= fft_x_cnt -2'd2; end else data_max_n= data_max_n; position_r= position_r; end else fft_x_cnt_n = fft_x_cnt; end //****************/ *// always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) fengzhi_max <= 16'b0; else fengzhi_max <= fengzhi_max_n; end always @ (*) begin if(data_real_in_int > fengzhi_max) fengzhi_max_n = data_real_in_int; else fengzhi_max_n = fengzhi_max; end always @ (posedge CLK_50M or negedge RST_N) begin if(!RST_N) fengzhi_min <= 16'd255; else fengzhi_min <= fengzhi_min_n; end always @ (*) begin if(fengzhi_min > data_real_in_int) fengzhi_min_n = data_real_in_int; else fengzhi_min_n = fengzhi_min; end assign vga_freq = (position) * 32'd147000 >> 8'd10;//ADC的采样频率147KHz F=Fs*n/N;Fs是采样频率,n是第几个点,N是采样点数,本例中是1024 assign vga_fengzhi = ((fengzhi_max - fengzhi_min) * 10'd50) >> 8'd8; assign fft_out = sqrt_data_out; fft3 fft_init ( .clk (CLK_50M ),// .reset_n (RST_N ),// .inverse (inverse ),// .sink_valid (sink_valid ),//input .sink_sop (sink_sop ),// .sink_eop (sink_eop ),// .sink_real (sink_real ),// .sink_imag (sink_imag ),// .sink_error (sink_error ),// .source_ready (source_ready ),// .sink_ready (sink_ready ),//output .source_error (source_error ),// .source_sop (source_sop ),// .source_eop (source_eop ),// .source_valid (source_valid ),// .source_exp (source_exp ),// .source_real (source_real ),// .source_imag (source_imag )// ); endmodule
代码中有很多的测试代码也放在里面了,也是自己调试过程中的记录,当然,这个是FFT部分的代码,并不是整个工程的,也仅供参考。在计算最大值方面还存在一些问题,就是每次有新的数据帧输入的时候,上一步中计算出来的最大值会被复位,导致输出不会一直稳定,所以在输入一个稳定正弦波的时候,无法一直输出稳定的频率,而是一直在查找过程,不过基本是验证了该方法的可行性,高手可以帮修改一下。
下面是测试波形图:
我输入的是2000Hz的正弦波,计算出来的值是2009,允许误差。可以增加采样点数,提高精度。
以上就是FFT调试过程的记录,供以后查阅。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。