目录
基础知识
已经学习一些ADC的调试经验,现在特地总结ADC在FPGA上的应用技巧。
ADC(Analog to Digital Converter)模拟到数字的装换。他是连接模拟世界和数字世界的桥梁。
自然界的各种信号都是模拟的,对于这种信号计算机是不能进行处理的。所以我们需要把各种模拟信号转换为数字信号。
初次接触ADC,你会听到很多诸如,这款ADC的精度怎么样,这款ADC的速度怎么样。
转换精度
分辨率
其实很好理解,分辨率就是一款ADC拥有的数据位宽,这说明ADC对输入模拟电压的分辨能力。例如一个数据位宽只有4位的ADC,如使用他去采集0-5V的电压。我们指定4位二进制数最大表示的十进制数为16,这时候就可以计算出,数据的每一位代表的电压值。
即一位代表的电压值为0.3125V,这时候就可以根据测得数值与LSB相乘。就可以算出测得的电压值。
转换误差
在AD转换过程中,转换误差表示AD转换器实际输出的数字量和理想输出的数字量的差别。例如给出的相对误差为<LSB/2时,表示最大误差不超过不大于最低位的1/2。
采样速度
对于不同的ADC其速度有很大的区别,一般来说速度和精度成反比,对于精度低的ADC最快速度可以达到GSPS。对于高精度的ADC如数据位其速度一般在MSPS期间。
SPS反应了ADC在一秒内所能采集的样本个数。入1MSPS,反应了ADC的采样频率为1MHZ,即每秒能产生100万个数据。说到这里就不得不说一下取样定理,为了能更加清晰的还原波形,就需要满足。
其中,表示取样频率,为待测信号的最高频率。
然而在工业是一般是取:
即取样频率一般要大于待测信号最高频率的五倍,才能更好的还原波形。
如何使用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所谓的采样率。上图中的 表示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的调试总结的差不多了。再接再厉。