ADC采集调试

目录

基础知识

转换精度

分辨率

转换误差

采样速度

低速ADC

并行模式

串行模式

高速ADC

设计方法

数据采集处理方法

第一种处理方法:

第二种方法使用IDELAY:

第三种方法使用超采样:

数据滤波处理方法

第一种方法:

第二种方法:

第三种方法:


基础知识

已经学习一些ADC的调试经验,现在特地总结ADC在FPGA上的应用技巧。

ADC(Analog to Digital Converter)模拟到数字的装换。他是连接模拟世界和数字世界的桥梁。

自然界的各种信号都是模拟的,对于这种信号计算机是不能进行处理的。所以我们需要把各种模拟信号转换为数字信号。

初次接触ADC,你会听到很多诸如,这款ADC的精度怎么样,这款ADC的速度怎么样。

转换精度

分辨率

其实很好理解,分辨率就是一款ADC拥有的数据位宽,这说明ADC对输入模拟电压的分辨能力。例如一个数据位宽只有4位的ADC,如使用他去采集0-5V的电压。我们指定4位二进制数最大表示的十进制数为16,这时候就可以计算出,数据的每一位代表的电压值。

LSB=\frac{5V}{2^{4}}=0.3125V

即一位代表的电压值为0.3125V,这时候就可以根据测得数值与LSB相乘。就可以算出测得的电压值。

转换误差

在AD转换过程中,转换误差表示AD转换器实际输出的数字量和理想输出的数字量的差别。例如给出的相对误差为<LSB/2时,表示最大误差不超过不大于最低位的1/2。

采样速度

对于不同的ADC其速度有很大的区别,一般来说速度和精度成反比,对于精度低的ADC最快速度可以达到GSPS。对于高精度的ADC如数据位其速度一般在MSPS期间。

SPS反应了ADC在一秒内所能采集的样本个数。入1MSPS,反应了ADC的采样频率为1MHZ,即每秒能产生100万个数据。说到这里就不得不说一下取样定理,为了能更加清晰的还原波形,就需要满足。

f_{s}\geqslant 2f_{imax}

其中,f_{s}表示取样频率,f_{imax}为待测信号的最高频率。

 然而在工业是一般是取:

 f_{s}\geqslant 5f_{imax}

即取样频率一般要大于待测信号最高频率的五倍,才能更好的还原波形。

如何使用FPGA做好ADC采集相关的电路呢。

对于大多数电路来说,外部的输入信号需要先经过模拟信号的处理,经过处理过的信号在满足ADC采集的安全电压范围内,经过ADC采集后,缓存在FPGA存储的空间,或者存储器上(FIFO或者DDR中)。对于整体的框图一般如下所示。

低速ADC

对于低速ADC例如常见的ADC7606,16位,200ksps ADC。

这一款ADC具有两种数据读取模式,串行和并行数据读取模式。对于逻辑设计来说,首先去看原理图,对ADC采用的模式进行分析。翻看芯片手册,对比其引脚配置和功能描述。详细的检查芯片的引脚模式是否对接正常,如有异常立即和硬件工程师联系。

如果连接模式为并行模式,在芯片手册中找到对应并行连接模式的时序图。分析时序图。根据手册写出逻辑。

 

 

分析时序图,从其中可以看出来,ADC转换开始信号起始于CONVSTA,B两信号的上升沿,ADC检测到该信号上升沿后,启动转换,同时拉高BUSY信号,表示转换正在进行中。等待转换结束,BUSY信号的下降沿,表示装换数据正被锁存至输出数据寄存器,可以读取数据。当然大部分ADC为了提高采样率,可以在BUSY转换期间继续读取数据(因为此时数据并未更新),但是需要保证在转换结束之前读取完成,以免造成数据错误。

在时序图中,CONVSTA,B信号在两次上升沿过程之中的时间间隔,就是ADC所谓的采样率。上图中的 t_{conv}表示ADC的采样速率。即我们所说的SPS。表示ADC的转换时间。对于并行模式CS和RD独立,拉低CS时间为FPGA从ADC中读出数据的读取时间。

这三个时间一般有如下的关系。

逻辑需要关注的几个时间,一是CNV即采样速率,这个直接反应了你ADC所能达到的最高采样率。再就是BUSY和SPI读数据时钟,为了保证读出的数据正常,必须要在下一次转换完成前完成对ADC采集的数据的读取。

对于这里的AD7606的并行模式。从时序图中可以看出来,数据DATA信号在RD信号的下降沿更新在数据总线上,逻辑上可以在RD信号的上升沿读取该数据。一片ADC可以实现8个通道的电压读取。

并行模式

下面是关于此并行模式参考代码。(楼主仿照AX309上面代码改动的),该模块实现了两个ADC对十六路数据的采集。

// *********************************************************************************/
// Project Name :
// Author       : i_huyi
// Email        : i_huyi@qq.com
// Creat Time   : 2021/8/12 14:59:23
// File Name    : .v
// Module Name  : 
// Called By    :
// Abstract     :
//
// CopyRight(c) 2020, xxx xxx xxx Co., Ltd.. 
// All Rights Reserved
//
// *********************************************************************************/
// Modification History:
// 1. initial
// *********************************************************************************/
// *************************
// MODULE DEFINITION
// *************************
`timescale 1 ns / 1 ps
module ad7606#(
parameter   U_DLY = 1
                                        )
                                        (
input   wire   		clk                 ,//50mhz
input   wire  		rst_n               ,

input   wire[15:0] 	ad_data             ,//ad7606 采样数据
input   wire		ad_busy             ,//ad7606 忙标志位 
input   wire    	first_data          ,//ad7606 第一个数据标志位 	    
output  wire[2:0] 	ad_os               ,//ad7606 过采样倍率选择
output  reg   		ad_cs               ,//ad7606 AD_1 cs
output  reg   		ad_cs2              ,//ad7606 AD_2 cs
output  reg   		ad_rd               ,//ad7606 AD data read
output  reg   		ad_reset            ,//ad7606 AD reset
output  reg   		ad_convstab         ,//ad7606 AD convert start

output  reg [4:0]	state               ,		

output  reg [15:0]  ad_ch1              ,//AD_1第1通道的数据
output  reg [15:0]  ad_ch2              ,//AD_1第2通道的数据
output  reg [15:0]  ad_ch3              ,//AD_1第3通道的数据
output  reg [15:0]  ad_ch4              ,//AD_1第4通道的数据
output  reg [15:0]  ad_ch5              ,//AD_1第5通道的数据
output  reg [15:0]  ad_ch6              ,//AD_1第6通道的数据
output  reg [15:0]  ad_ch7              ,//AD_1第7通道的数据
output  reg [15:0]  ad_ch8              ,//AD_1第8通道的数据	

output  reg [15:0]  ad2_ch1             ,//AD_2第1通道的数据
output  reg [15:0]  ad2_ch2             ,//AD_2第2通道的数据
output  reg [15:0]  ad2_ch3             ,//AD_2第3通道的数据
output  reg [15:0]  ad2_ch4             ,//AD_2第4通道的数据
output  reg [15:0]  ad2_ch5             ,//AD_2第5通道的数据
output  reg [15:0]  ad2_ch6             ,//AD_2第6通道的数据
output  reg [15:0]  ad2_ch7             ,//AD_2第7通道的数据
output  reg [15:0]  ad2_ch8             //AD_2第8通道的数据	
	
                                        );
//--------------------------------------
// localparam
//--------------------------------------
parameter IDLE			=5'd0           ;
parameter AD_CONV		=5'd1           ;
parameter Wait_1		=5'd2           ;
parameter Wait_busy	    =5'd3           ;
parameter READ_CH1	    =5'd4           ;
parameter READ_CH2	    =5'd5           ;
parameter READ_CH3	    =5'd6           ;
parameter READ_CH4	    =5'd7           ;
parameter READ_CH5	    =5'd8           ;
parameter READ_CH6	    =5'd9           ;
parameter READ_CH7	    =5'd10          ;
parameter READ_CH8	    =5'd11          ;

parameter AD1_TO_AD2	=5'd12          ;//从ad1芯片装换到ad2芯片

parameter READ_2_CH1	=5'd13          ;//AD2芯片读数据
parameter READ_2_CH2	=5'd14          ;
parameter READ_2_CH3	=5'd15          ;
parameter READ_2_CH4	=5'd16          ;
parameter READ_2_CH5	=5'd17          ;
parameter READ_2_CH6	=5'd18          ;
parameter READ_2_CH7	=5'd19          ;
parameter READ_2_CH8	=5'd20          ;

parameter READ_DONE	    =5'd21          ;
//--------------------------------------
// register
//--------------------------------------
reg [15:0]  cnt                         ;
reg [15:0]  i                           ;
//reg [3:0] state;

reg [15:0]	cnt_conv                    ;
reg [7:0]	ad_busy_cnt                 ;
//--------------------------------------
// wire
//--------------------------------------
wire 		ad_busy_1                   ;
//--------------------------------------
// assign
//--------------------------------------
assign      ad_os   = 3'b000            ;
//模拟AD busy信号计时250次后模拟busy拉低
assign ad_busy_1 =(ad_busy_cnt<=8'd250)?1'b1:1'b0;
//------------------------------------------------------------
//------------------------------------------------------------
always@(posedge clk)
begin
        if(!rst_n)
                ad_busy_cnt <= 8'd0;
        else if(state == Wait_busy)
                ad_busy_cnt <= ad_busy_cnt + 8'b1;
        else
                ad_busy_cnt <= 8'd0;
end				


//AD 复位电路
always@(posedge clk)
begin
        if(cnt<16'hffff) 
		begin
			cnt<=cnt+1;
			ad_reset<=1'b1;
		end
		else
			ad_reset<=1'b0;       
end


always @(posedge clk) 
 begin
	 if (ad_reset==1'b1) begin 
			 state<=IDLE; 
			 ad_ch1<=0;
			 ad_ch2<=0;
			 ad_ch3<=0;
			 ad_ch4<=0;
			 ad_ch5<=0;
			 ad_ch6<=0;
			 ad_ch7<=0;
			 ad_ch8<=0;
			 ad_cs<=1'b1;
			 ad_cs2<=1'b1;//ad2的片选
			 ad_rd<=1'b1; 
			 ad_convstab<=1'b1;
			 i<=0;
	 end		 
	 else begin
		  case(state)
		  IDLE: begin
				 ad_cs<=1'b1;
				 ad_cs2<=1'b1;
				 ad_rd<=1'b1; 
				 ad_convstab<=1'b1; 
				 if(i==12500) begin
					 i<=0;			 
					 state<=AD_CONV;
				 end
				 else 
					 i<=i+1'b1;
		  end
		  AD_CONV: begin	   
				 if(i==2) begin                        //等待2个clock
					 i<=0;			 
					 state<=Wait_1;
					 ad_convstab<=1'b1;       				 
				 end
				 else begin
					 i<=i+1'b1;
					 ad_convstab<=1'b0;                     //启动AD转换
				 end
		  end
		  Wait_1: begin            
				 if(i==5) begin                           //等待5个clock, 等待busy信号为高
					 i<=0;
					 state<=Wait_busy;
				 end
				 else 
					 i<=i+1'b1;
		  end		 
		  Wait_busy: begin            
				 if(ad_busy_1==1'b0) begin                    //等待busy信号为低
					 i<=0;			 
					 state<=READ_CH1;
				 end
		  end
		  READ_CH1: begin 
				 ad_cs<=1'b0;                              //cs信号有效	  
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch1<=ad_data;                        //读CH1
					 state<=READ_CH2;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH2: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch2<=ad_data;                        //读CH2
					 state<=READ_CH3;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH3: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch3<=ad_data;                        //读CH3
					 state<=READ_CH4;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH4: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch4<=ad_data;                        //读CH4
					 state<=READ_CH5;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH5: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch5<=ad_data;                        //读CH5
					 state<=READ_CH6;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH6: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch6<=ad_data;                        //读CH6
					 state<=READ_CH7;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH7: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch7<=ad_data;                        //读CH7
					 state<=READ_CH8;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_CH8: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad_ch8<=ad_data;                        //读CH8
					 state<=AD1_TO_AD2;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  AD1_TO_AD2:begin
					 ad_rd<=1'b1;	 
					 ad_cs<=1'b1;
					 state<=READ_2_CH1;
		  end
		  READ_2_CH1: begin 
				 ad_cs2<=1'b0;                              //cs2信号有效	  
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch1<=ad_data;                        //读AD2的CH1
					 state<=READ_2_CH2;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH2: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch2<=ad_data;                        //读AD2的CH2
					 state<=READ_2_CH3;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH3: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch3<=ad_data;                        //读AD2的CH3
					 state<=READ_2_CH4;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH4: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch4<=ad_data;                        //读AD2的CH4
					 state<=READ_2_CH5;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH5: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch5<=ad_data;                        //读AD2的CH5
					 state<=READ_2_CH6;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH6: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch6<=ad_data;                        //读AD2的CH6
					 state<=READ_2_CH7;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH7: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch7<=ad_data;                        //读AD2的CH7
					 state<=READ_2_CH8;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_2_CH8: begin 
				 if(i==3) begin
					 ad_rd<=1'b1;
					 i<=0;
					 ad2_ch8<=ad_data;                        //读AD2的CH8
					 state<=READ_DONE;				 
				 end
				 else begin
					 ad_rd<=1'b0;	
					 i<=i+1'b1;
				 end
		  end
		  READ_DONE:begin
					 ad_rd<=1'b1;	 
					 ad_cs2<=1'b1;
					 state<=IDLE;
		  end		
		  default:	state<=IDLE;
		  endcase	
    end	  
				 
 end

//------------------------------------------------------------
//------------------------------------------------------------

//------------------------------------------------------------
//------------------------------------------------------------
endmodule

对于ADC7606的串行模式。在串行模式下,ADC输入的数据总线将降低至两根,分别为DoutA和DoutB。

 

从时序图中可以看出来,输入的串行数据线有两个,DA采集V1,V2,V3,V4的数据,DB采集V5,V6,V7,V8的数据并从时序图中可以看出来,ADC输出的数据在SCLK的上升沿跟新数据,FPGA内部在SCLK的上升沿可以读出稳定的数据。

串行模式

部分核心代码如下。

//------------------------------------------------------------
//------------------------------------------------------------
//复位
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
        begin
                cnt_reset   <= 8'b0;
                reset       <= 1'b0;
        end
        else if(cnt_reset <= RESRT)
        begin
                cnt_reset   <= cnt_reset + 1'b1;
                reset       <= 1'b1;
        end
        else
        begin
                cnt_reset   <= cnt_reset;
                reset       <= 1'b0;
        end
end

//控制信号打拍
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
        begin
                en_start_r  <= 4'd0;
                en_stop_r   <= 4'd0;
        end
        else
        begin
                en_start_r  <= {en_start_r[2:0],en_start};
                en_stop_r   <= {en_stop_r[2:0],en_stop};
        end
end


//开始采集 
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                ad_start    <= 1'b0;
        else if(en_start_r[1] == 1'b1 && en_start_r[2] == 1'b0)
                ad_start    <= 1'b1;
        else if(en_stop_r[1] == 1'b1 && en_stop_r[2] == 1'b0)
                ad_start    <= 1'b0;
        else
                ad_start    <= ad_start;
end

//AD启动标志信号
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                ad_start_flog   <= 1'b0;
        else if(ad_start == 1'b1)
                ad_start_flog   <= 1'b1;
        else if(ad_start == 1'b0 && cnt_sample  == 16'd0)
                ad_start_flog   <= 1'b0;
        else
                ad_start_flog   <= ad_start_flog;
end


//开始计数产生cnv
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                cnt_sample  <= 16'd0;
        else if(ad_start_flog == 1'b1)
        begin
                if(cnt_sample >= CNV_MAX )
                        cnt_sample  <= 16'd0;
                else
                        cnt_sample  <= cnt_sample + 1'b1;
        end
        else
                cnt_sample  <= 16'd0;
end


//产生采样时钟20khz,cnv最小25ns
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                signal_cnv  <= 1'd1;
        else if(cnt_sample >= CNV_LOW && cnt_sample <= CNV_HIGH)
                signal_cnv  <= 1'd0;
        else
                signal_cnv  <= 1'd1;
end

//检测busy信号下降沿,busy信号一般值为4us,而本次采样率要求20khz,满足要求
//busy信号打拍
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                busy_r  <= 8'd0;
        else
                busy_r  <= {busy_r[14:0],busy};
end

//采集busy下降沿开始读数
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                sclk_flog   <= 1'b0;
        else if(busy_r[14] == 1'b0 && busy_r[15] == 1'b1)
                sclk_flog   <= 1'b1;
        else if(cnt_sclk == 8'd64)
                sclk_flog   <= 1'b0;
        else
                sclk_flog   <= sclk_flog;
end


//产生CS标志

always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                cs_flog <= 1'b1;
        else if(busy_r[1] == 1'b0 && busy_r[2] == 1'b1)
                cs_flog <= 1'b0;
        else if(cnt_sclk == 8'd64)
                cs_flog <= 1'b1;
        else
                cs_flog <= cs_flog;
end




//sclk_flog_r打拍
//这一段代码看看
//这里需要将SCLK延迟输出30ns左右,保证数据正常进入

always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                sclk_flog_r   <= 1'b0;
        else if(sclk_spi == 1'b1)
                sclk_flog_r   <= sclk_flog;
        else
                sclk_flog_r   <= sclk_flog_r;
end

//产生10mhz时钟
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
        begin
                cnt_sclk_spi    <= 8'd0;
                sclk_spi        <= 1'b0;
        end
        else if(cnt_sclk_spi == SPI_CLK)
        begin
                cnt_sclk_spi    <= 8'd1;
                sclk_spi        <= ~sclk_spi;
        end
        else
        begin
                cnt_sclk_spi    <= cnt_sclk_spi + 1'b1;
                sclk_spi        <= sclk_spi;
        end
end

//AD复位
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                ad_reset_n  <= 1'b1;
        else if(busy == 1'b1)
                ad_reset_n  <= 1'b0;
        else
                ad_reset_n  <= 1'b1;
end



//spi时钟上升沿计数
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                cnt_sclk    <= 8'd0;
        else if(ad_reset_n == 1'b0)
                cnt_sclk    <= 8'd0;
        else if(sclk_flog_r == 1'b1 && sclk_spi == 1'b0 && cnt_sclk_spi == SPI_CLK)
                cnt_sclk    <= cnt_sclk + 1'b1;
        else
                cnt_sclk    <= cnt_sclk;
end



//spi时钟下降沿读数
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
        begin
                data_a  <= 64'd0;
                data_b  <= 64'd0;
        end
        else if(ad_reset_n == 1'b0)
        begin
                data_a  <= 64'd0;
                data_b  <= 64'd0;
        end

        else if(sclk_flog_r == 1'b1 && sclk_spi == 1'b0 && cnt_sclk_spi == 8'd3  )
        begin
                data_a  <= {data_a[62:0],d_a};
                data_b  <= {data_b[62:0],d_b};
        end
        else
        begin
                data_a  <= data_a;
                data_b  <= data_b;
        end
end


//产生数据有效
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
                sample_valid    <= 1'b0;
        else if(sclk_flog == 1'b0 && sclk_flog_r == 1'b1)
                sample_valid    <= 1'b1;
        else
                sample_valid    <= 1'b0;
end

数据赋值
always@(posedge clk ,negedge rst_n)
begin
        if(rst_n == 1'b0)
        begin
                dout_1      <= 16'd0;
                dout_2      <= 16'd0;
                dout_3      <= 16'd0;
                dout_4      <= 16'd0;
                dout_5      <= 16'd0;
                dout_6      <= 16'd0;
                dout_7      <= 16'd0;
                dout_8      <= 16'd0;
                dout_valid  <= 1'b0;
        end
        else if(sample_valid == 1'b1 )
        begin
                dout_1      <= data_a[63:48];
                dout_2      <= data_a[47:32];
                dout_3      <= data_a[31:16];
                dout_4      <= data_a[15:0];
                dout_5      <= data_b[63:48];
                dout_6      <= data_b[47:32];
                dout_7      <= data_b[31:16];
                dout_8      <= data_b[15:0]; 
                dout_valid  <= 1'b1;
        end
        else
        begin
                dout_1      <= 16'd0;
                dout_2      <= 16'd0;
                dout_3      <= 16'd0;
                dout_4      <= 16'd0;
                dout_5      <= 16'd0;
                dout_6      <= 16'd0;
                dout_7      <= 16'd0;
                dout_8      <= 16'd0;
                dout_valid  <= 1'b0;
        end
end
//

仿真如下。

 前面说明了针对低速ADC采集做了详细的介绍。

高速ADC

设计方法

但是一般的项目中对ADC的要求在MHZ。下面就记录一下我在这方面踩的坑。

对于硬件工程师,在实现对多路信号采样时。往往在外部使用多路选择器将外部输入信号切换输入,以实现对信号多路采集。一般模型如下:

对硬件的模式做简单的分析。外部输入的信号首先经过衰减电路,发送到多路选择器上,FPGA控制多路选择器将那一路的数据输入进来。(逻辑需要注意此多路选择器切换稳定时间)。在此期间逻辑需要协调启动ADC的采集,保证ADC采集的电压是切换后稳定的电压。(这里实际情况往往和芯片手册叙述的有一点差距逻辑需要注意)

用这种模式可以实现用低速ADC跑高速采集的效果。效果就是N个ADC采样速率的和。

例如,先在有一款ADC最高速率是1mhz。但是实际需要达到3MHZ的采样率,但是3mhz采样率的ADC价钱又贵了几倍,这时候就可以采用这种方式。

 从图中可以看出来,为了实现要求的3mhz的采样率,我们采用了三个相同的ADC来进行数据采集。在总的采样率为3mhz的情况下,每一个ADC都能发挥1mhz的采样速率。这样可以实现对外部输入信号的高精度采样。

在设计上需要注意的是,需要确定多路选择器的切换到切换后电压稳定的时间小于3mhz采样率所需要的时间。这样才能保证输入的电压稳定,ADC采集的电压值才能准确反应这个通道的电压值。

当然也可以选择直接使用高速ADC来实现对数据的采集。

整体的框图如下。

即使用单路的ADC来实现对数据的采集,这时候往往需要更加高速的SPI来读取ADC转化后的数据,如,使用一款采样率为5msps的ADC,在数据转换完成后,需要使用300mhz的SPI总线来读取ADC返回的数据。才能保证读取的数据正确性。

但是使用高速的SPI来读取数据,往往会出现数据和时钟不对其的情况(这里跟外部的电路的设计有关系)。即你在芯片手册上看到的是当你给出SPI的时钟SCLK的下降沿时,ADC接受到时钟下降沿,将第一个数据发送到SPI的串行数据总线上,逻辑部分需要在上升沿对该数据进行读取,并保存。但是往往实际情况并不是这样的。

而是在给出SPI时钟后,数据并未立马更新,而是在你逻辑内部快到时钟上升沿的时候才开始更新数据。这样你在逻辑内部使用上升沿读出的数据就会亚稳态。如果在你读取时钟的上升沿之前数据更新完毕,即可以读出稳定的数据。如果在你数据的上升沿之后数据才更新,那么你将获取不到正确的数据。

 这里有几种处理不同的处理方法,是我在实际中使用的。

数据采集处理方法

第一种处理方法:

这也是最简单的处理方法,使用PLL产生主时钟的相移,偏移度数为0°,45°,90°,135°。在读数据的时候,分别使用这四个时钟去读取串行输入的数据。

使用0°时钟读取。

//接收BUF、接收数据个数
always@(posedge clk or posedge rst)
begin
        if(DataReset == 1 || rst == 1'b1)
                RecBuf <= 32'hFFFF_FFFF;
        else if(DataReceive)
                RecBuf <= {RecBuf[30:0],AD7961_DATA};
        else
                RecBuf <= RecBuf;

end

使用45°时钟读取。

//接收BUF、接收数据个数
always@(posedge clk_45 or posedge rst)
begin
        if(DataReset == 1 || rst == 1'b1)
                RecBuf <= 32'hFFFF_FFFF;
        else if(DataReceive)
                RecBuf <= {RecBuf[30:0],AD7961_DATA};
        else
                RecBuf <= RecBuf;

end

使用90°时钟读取。

//接收BUF、接收数据个数
always@(posedge clk_90 or posedge rst)
begin
        if(DataReset == 1 || rst == 1'b1)
                RecBuf <= 32'hFFFF_FFFF;
        else if(DataReceive)
                RecBuf <= {RecBuf[30:0],AD7961_DATA};
        else
                RecBuf <= RecBuf;

end

使用135°时钟读取。

//接收BUF、接收数据个数
always@(posedge clk_135 or posedge rst)
begin
        if(DataReset == 1 || rst == 1'b1)
                RecBuf <= 32'hFFFF_FFFF;
        else if(DataReceive)
                RecBuf <= {RecBuf[30:0],AD7961_DATA};
        else
                RecBuf <= RecBuf;

end

这样你就获取了同一个数据的四种反馈数据。需要观察并使用ILA采集看看那一路数据处于最稳定的状态。同时保存当前通道的数据。

第二种方法使用IDELAY:

在FPGA中一般拥有IDELA和ODELAY两种,他们可以控制你输入和输出数据的延迟值。这部分的介绍可以看我的另一篇文章《IDELAY输入延迟分析》

(16条消息) IDELAY输入延迟分析_hy_520520的博客-CSDN博客

注意IDELAY拥有几种模式,可以先在VAR_LOAD模式下调试出最准确的值,然后使用FIXED模式固定延时值。注意在IDELAY模式下,使用200mhz的参考时钟,拥有0.6ns的基础延时,在写满延迟参数的情况下拥有0.6ns+78ps*31=3.018ns的延迟。

例化ILELAY模块

//ADC输入的数据延迟
idelay u_idelay_p(
    .clk                        (clk                        ),
    .rst                        (rst                        ),
//需要延时的信号
    .data_in                    (AD7961_DATA                ),
//延迟数据
    .data_out                   (AD7961_DATA_DALAY          ),
//
    .delay_data                 (reg_ai_delay_data          ),
    .delay_valid                (reg_ai_delay_valid         ));

第三种方法使用超采样:

超采样和第一种方法比较类似,同样是使用不同相位的时钟对同一信号的采样。

 如图所示,同样的,使用FPGA的MMCM产生四组周期相同相位不同的时钟,在CLK的下降沿ADC读取到SPI,开始在数据总线上更新数据,由于使用的不同相位的时钟对数据进行了采样,即在同一点对同一位数据进行了四次采样。所以叫做超采样。对于这种做法采集后的数据往往更加准确。逻辑需要对采集后的数据进行处理后在进行保存。

使用以上三种采集方法一般就能保证采集到准确的数据,不会因为时许原因产生数据读出错造成的错误。

但是ADC是一种非常敏感的器件,往往外部电路的一些波动会操作采集的数据波动,造成使用ADC采集的数据还原出的波形呈现波动。这时候就需要进行对采集的数据进行滤波处理,对于滤波我也总结了几种常用的方法。

数据滤波处理方法

第一种方法:

均值滤波。适合高采样率数据量大的情况,对波形有很好的平滑处理。但是对个别极值效果不明显。使用5msps的采样率去采集1khz的正弦波,一个周期将产生5000个数据点。采集的波形不平,导致还原出来的波形出现明显的波动。这时候就可以使用均值滤波器对输入的数据进行滤波处理。如使用十六次平均滤波。滤波后产生的数据点数将降低至300个左右,这时候在还原出波形。由于使用了平均数代替原数据,对波形的峰峰值将产生一定的影响。

第二种方法:

滑动滤波器。使用滑动滤波器对波形有很好的还原,同样的的对波形的峰峰值有一定的影响。设计思路是使用一个双口RAM来实现对原始数据的存储,第一次到达设定个数后,下一次进来前,将第一个进来的数据删除,同时将新数据放入末尾。对数据进行均值运算后输出均值。

设计模型。

 计算模型。

 即每一次计算都需要将数据框右移一位,然后依次输入计算后的均值。

第三种方法:

滑动去极滤波器。在使用滑动滤波器数据进行处理的时候,发现对数据出现的个别尖峰值处理并不好。所以设计了这种在滑动处理的过程中使用均值代替出现的尖峰值。然后再输出均值。

设计模型。

思路如下,使用N(奇数)个寄存器分别寄存输入的数据。带第一将寄存器写满后,进行去极值。使用中心数据前后数据的均值和中心数据进行比较,如果数据远大于中心数据就使用均值将中心数据替换。在求N个数据的均值输出。

实际输入一个正弦波,在其中一个数据产生尖峰,滤波效果如图所示。

到这里对ADC的调试总结的差不多了。再接再厉。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值