18 SPI接口ADC采集驱动设计
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA
登录米联客(MiLianKe)FPGA社区-www.uisrc.com观看免费视频课程、在线答疑解惑!
1 概述
一些低速高精度的ADC/DAC都具有SPI接口,SPI的速率最高可以到几百M,另外由于接口少硬件设计简单,通信时序容易实现,而被广泛应用一些AD/DA数据采集场合,笔者在诸多的项目中都使用到了串行SPI接口的ADC/DAC芯片方案。
本文实现AD7606 SPI通信方式实现8路ADC信号采集,本文也将灵活使用SPI的通信时序,而不是拘泥于前面编写的通用的SPI接口。更多时候我们在FPGA项目应用中会根据实际的外设接口编写最佳的时序方案,来实现最佳的性能。
2 AD7606介绍
2.1芯片功能概述
DAQ7606 数据采集卡,AD 转换芯片为AD7606-8,AD760-8 是16 位8 通道同步采样模数数据采集系统(DAS)。AD7606 内置模拟输入箝位保护、二阶抗混叠滤波器、跟踪保持放大器、16 位电荷再分配逐次逼近型ADC、灵活的数字滤波器、2.5V 基准电压源、基准电压缓冲以及高速串行和并行接口。
AD7606 采用5V单电源供电,可以处理±10V 和±5V 真双极性输入信号,同时所有通道均能以高达200 kSPS 的吞吐速率采样。输入箝位保护电路可以耐受最高达±16.5V 的电压。无论以何种采样频率工作,AD7606 的模拟输入阻抗均为1 MΩ。它采用单电源工作方式,具有片内滤波和高输入阻抗,因此无需驱动运算放大器和外部双极性电源。
AD7606 抗混叠滤波器的3 dB 截止频率为22 kHz;当采样速率为200 ksps 时,它具有40 dB 抗混叠抑制特性。灵活的数字滤波器采用引脚驱动,可以改善信噪比(SNR),并降低3 dB 带宽。
2.2ADC功能框图
很多FPGA程序开发者不懂硬件,这是不行的,FPGA程序员不需要去画PCB但是必须可以阅读硬件的芯片datasheet,从datasheet中提取关键的编程信息,并且原理图的外设和FPGA芯片之间的连接关系。
对于AD7606的datasheet大家可以网上查到,从这副图我们可以看到AD7606芯片的关键结构图。
1)、8路18bit ADC输入,支持双极性输入
2)、8路16bit ADC实际通过多路复用的方式实现的,内部真正只有1个16bitADC
3)、支持并口传输和SPI串行传输
4)、具有内部的2.5V基准,通常采用内部电压基准,可以省去一个外部基准。
5)、还有一些比如BUSY、FRSTDATA、信号需要下面继续根据datasheet了解。
2.3 ADC转换控制时序
CONVST时序-转换之后读取
CONVST时序-转换过程种读取
AD7606支持2种时序转换,由于我们采用的时串行SPI模式,本身SPI读取数据就会耽误很多时间,所以必须采用第二种工作时序,才能确保200Kbps的采样率。
通过这个转换时序,我们需要把上图的时序中的所有信号作用搞清楚。
1、RESET信号用于对AD7606芯片的复位,复位的高电平时间参数tRESET=50ns,复位和转换信号CONVSTA/ONVSTB的上升沿时间参数t7=25ns
2、CONVSTA/ CONVSTB的低电平时间参数t2=40ns
3、CONVSTA/ CONVSTB如果不是同步,那么他们之间的时间差不能超过t5=0.5ms,我们这里时同时的
4、转换周期tCYCLE=5us
5、BUSY信号为高电平代表数据正在转换,转换时间参数tCONV为最大4.15us
6、CS信号和BUSY信号有一个时间参数t6不能大于25ns
由于设计FPGA接口芯片程序,都是和时序相关联的,所以了解了以上时序参数后,我们FPGA代码设计就要能够满足这些参数指标。
2.4 ADC SPI时序
当数据完成转换后,CS为电平期间,每个时钟的上升沿完成数据的采样,这里有一个FRSTDATA的表中,代表每次转换的第一个数据,这里实际可以不用这个信号,因为每次CS之后的第一个上升沿时钟我们就是开始采样数据,每16次完成一路ADC的采集。
对于AD7606具有8路ADC,DOUTA对于1~4路ADC通道,DOUTB对应5~8路ADC通道。而且ADC的2组4个通道可以一次性完成采集,也就是说16X4共计64个时钟上升沿完成4路ADC采集,DOUTA和DOUTB各4路。
3 硬件电路分析
硬件接口和子卡模块请阅读"附录 1"
配套工程的 FPGA PIN 脚定义路径为 fpga_prj/uisrc/04_pin/ fpga_pin.xdc。
4 AD7606 FPGA程序设计
4.1 AD7606SPI.v
/*******************************AD7606 SPI串行采样********************* --以下是米联客设计的AD7606 SPI串行采样驱动程序 --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨 --2.AD7606可以工作于并行模式,和串行模式,串行模式可以使用更少的IO --3.AD7606最高可以工作于200K 8通道同时采样,默认就是工作于200K 采样,SPI_DIV 和T5US_DIV根据不同的系统时钟需要正确设置 *********************************************************************/
`timescale 1ns / 1ns//仿真时间刻度/精度
module uispi7606 ( input I_ad_clk, //系统时钟输入 input I_ad_rst, //系统复位输入 input I_ad_busy, //ad7606 忙标志位 output [2:0] O_ad_os, //ad7606 过采样倍率选择,本驱动不使用 output O_ad_cs, //ad7606 CS信号输出,低电平SPI数据线输出AD7606寄存器数据 output reg O_ad_sclk, //ad7606 SCLK时钟输出 output O_ad_rst, //ad7606 复位输出 output O_ad_convsta, //ad7606 A组通道转换 output O_ad_convstb, //ad7606 B组通道转换 output O_ad_range, //ad7606 模拟输入范围,设置1范围:±10V,设置0范围±5V input I_O_ad_out_a, //串行A组通道采集输入,V1,V2,V3,V4 input I_O_ad_out_b, //串行B组通道采集输入, V5,V6,V7,V8 output reg [63:0] O_ad_out_a, //A组通道采集有效数据输出 output reg [63:0] O_ad_out_b, //B组通道采集有效数据输出 output O_ad_cap_en //采集完成使能 ); localparam CLK_FREQ = 100000000; localparam SET_5US = 5; //5us周期 localparam T5US_DIV = CLK_FREQ*SET_5US/1000000 -1; //计算5us 分频系数 localparam SPI_DIV = CLK_FREQ/16666666 -1 ;//产生SPI时钟,设置最高16.666667MHZ
assign O_ad_range = 1'b1; //±10V真直流输入范围 assign O_ad_os = 3'b000; //无过采样
//ad复位时间高电平,复位时间最少50ns reg [23: 0] rst_cnt; assign O_ad_rst = !rst_cnt[23]; always@(posedge I_ad_clk or posedge I_ad_rst)begin if(I_ad_rst) rst_cnt <= 24'd0; else if(!rst_cnt[23]) rst_cnt <= rst_cnt + 1'b1; end
//设置采样频率,AD7606为8通道可以工作于200Kbps采样率,因此采样周期为5us reg [9:0] tcnt5us; wire cycle_end = (tcnt5us == T5US_DIV); always@ (posedge I_ad_clk)begin if(O_ad_rst) tcnt5us <= 10'd0; else if(tcnt5us < T5US_DIV) tcnt5us <= tcnt5us + 1'b1; else tcnt5us <= 10'd0; end
localparam [9:0] SPI_DIV1 = SPI_DIV/2; //半周期分频 reg [9:0] clk_div = 10'd0;//分频计数器
//SPI 时钟分频,对于200K采样,设置20M时钟 always@(posedge I_ad_clk)begin if(clk_div < SPI_DIV) clk_div <= clk_div + 1'b1; else clk_div <= 10'd0; end
//产生SPI时钟 wire clk_en1 = (clk_div == SPI_DIV1);// 半周期使能,控制输出0 wire clk_en2 = (clk_div == SPI_DIV);// 半周期使能,控制输出1
always@(posedge I_ad_clk)begin if(clk_en2) O_ad_sclk <= 1'b1; //输出高电平 else if(clk_en1) O_ad_sclk <= 1'b0; //输出高低平 end
//AD转换状态机 reg [1:0] AD_S; reg ad_convst; //ADC转换控制信号,A组通道和B组通道采用同一通道 //ADC工作于SPI模式,以及边读边转换模式,本次数据转换的同时,可以读出前一次转换的结果 assign O_ad_cs = ~((AD_S == 2'd3)&&I_ad_busy);//当AD7606工作在串行模式,cs=0代表输出通过SPI数据总线,输出之前的采样数据 assign O_ad_convsta = ad_convst; //ADC转换控制信号,A组通道和B组通道采用同一通道 assign O_ad_convstb = ad_convst; //ADC转换控制信号,A组通道和B组通道采用同一通道
always @(posedge I_ad_clk) begin if(O_ad_rst||I_ad_rst)begin ad_convst <= 1'b1; AD_S <= 2'd0; end else begin case(AD_S) 2'd0:if(clk_en2)begin //clk_en2,控制SCLK输出1,因此这里相当于SCLK上升沿 ad_convst <= 1'b0; //设置ad_convst=0 AD_S <= 2'd1;//下一状态 end 2'd1:if(clk_en2)begin //延迟50ns,ad_convst低电平至少25ns,对于SCLK时钟是20MHZ,所以这里ad_convst低电平是50ns ad_convst <= 1'b1;//ad_convst 低电平50ns后拉高为高电平 AD_S <= 2'd2;//下一状态 end 2'd2:if(clk_en2&&I_ad_busy)//延迟50ns,并且等待busy高,只要ADC正常工作,busy必然为高,说明ADC处于采样转换下 AD_S <= 2'd3; 2'd3:if(cycle_end)//ADC转换时间最大4.2us,5us周期结束,代表本次8通道完全采集结束 AD_S <= 2'd0; //回到初始状态,进行下一次采样 endcase end end
//SPI采样 reg [7 : 0] nbits; // bits计数器,用于计数完成了从AD7606 SPI总线读出的bits数 wire ad_cap_en_r1 = (nbits==8'd64); //数据同步输出使能 reg ad_cap_en_r2 = 1'b0; //数据同步输出使能寄存一次 assign O_ad_cap_en = ({ad_cap_en_r1,ad_cap_en_r2}==2'b10);//高电平输出系统时钟的一个周期
always@(posedge I_ad_clk) begin //寄存一次ad_cap_en_r1 ad_cap_en_r2 <= ad_cap_en_r1; end
//当CS信号为低电平,每个SCL的下降沿输出采样数据,每组通道采样64bits wire cap_en = (!O_ad_cs)&&clk_en1&&(nbits<8'd64);
//ADC 串并转换模块 always@(posedge I_ad_clk) begin if(O_ad_rst) begin //ADC复位期间重置相关寄存器 O_ad_out_a <= 64'd0; O_ad_out_b <= 64'd0; nbits <= 8'd0; end else if(O_ad_cs)begin //高电平,重置 nbits nbits <= 8'd0; end else if(cap_en)begin//当CS信号为低电平,每个SCL的下降沿采样数据,每组通道采样64bits nbits <= nbits + 1'b1; // O_ad_out_a <= {O_ad_out_a[62:0],I_O_ad_out_a};//保存A组通道数据,V1~V4,每个通道16bits O_ad_out_b <= {O_ad_out_b[62:0],I_O_ad_out_b};//保存B组通道数据,V5~V8,每个通道16bits end end
endmodule |
4.2 AD7606SPI程序分析
本文的SPI接口不是标准的SPI接口,主要区别是传输位宽达到了64bit,而之前我们学习SPI通信的文章是采用的标准的SPI接口去讲解的。标准的接口使用起来会比较方便,但是对于我们这种ADC采集往往编写专用的SPI接口更加实用。
本文的ADC采集程序主要需要注意3点:
1、ADC转换状态机
这个状态机需要设置相关的AD采集转换采集时序
2、SPI采样时序
这个比较简单,和我们之前讲解过标准的SPI接口类似,这里只是把位宽扩展到64bit
3、关键功能设计
1)、时钟单元
时钟单元通过时钟IP产生一个100MHZ的时钟用于AD转换采集时序的状态机以及SPI7606 IP核内部使用,SPI7606 IP核通过计数器,生成采样频率及SPI时钟。
//设置采样频率,AD7606为8通道可以工作于200Kbps采样率,因此采样周期为5us reg [9:0] tcnt5us; wire cycle_end = (tcnt5us == T5US_DIV); always@ (posedge I_ad_clk)begin if(O_ad_rst) tcnt5us <= 10'd0; else if(tcnt5us < T5US_DIV) tcnt5us <= tcnt5us + 1'b1; else tcnt5us <= 10'd0; end
localparam [9:0] SPI_DIV1 = SPI_DIV/2; //半周期分频 reg [9:0] clk_div = 10'd0;//分频计数器
//SPI 时钟分频,对于200K采样,设置20M时钟 always@(posedge I_ad_clk)begin if(clk_div < SPI_DIV) clk_div <= clk_div + 1'b1; else clk_div <= 10'd0; End
//产生SPI时钟 wire clk_en1 = (clk_div == SPI_DIV1);// 半周期使能,控制输出0 wire clk_en2 = (clk_div == SPI_DIV);// 半周期使能,控制输出1
always@(posedge I_ad_clk)begin if(clk_en2) O_ad_sclk <= 1'b1; //输出高电平 else if(clk_en1) O_ad_sclk <= 1'b0; //输出高低平 end |
2)、AD7606复位信号
AD7606的复位时间设置为41943040ns远大于芯片要求的50ns,所以满足要求。
//ad复位时间高电平,复位时间最少50ns reg [23: 0] rst_cnt; assign O_ad_rst = !rst_cnt[23]; always@(posedge I_ad_clk or posedge I_ad_rst)begin if(I_ad_rst) rst_cnt <= 24'd0; else if(!rst_cnt[23]) rst_cnt <= rst_cnt + 1'b1; end
|
3)、采样周期计数器
为了确保200kbps的采样率,我们需要设置一个时间计数器,每间隔5us,定义是T5US_DIV = 10'd999进行下一次的采样。
//设置采样频率,AD7606为8通道可以工作于200Kbps采样率,因此采样周期为5us reg [9:0] tcnt5us; wire cycle_end = (tcnt5us == T5US_DIV); always@ (posedge I_ad_clk)begin if(O_ad_rst) tcnt5us <= 10'd0; else if(tcnt5us < T5US_DIV) tcnt5us <= tcnt5us + 1'b1; else tcnt5us <= 10'd0; end |
4)、AD转换及采样状态机
AD7606芯片的模拟转数字过程由状态机控制,一些控制信号必须满足芯片手册给出的时序要求。我们可以结合程序,以及芯片给出的AD转换时序图分析程序。
上图种的,t1、 t5 、tReset等时间参数都可以在芯片手册里面找到,我们的状态机设计的AD控制及转换时序需要满足上图的要求。
//AD转换状态机 reg [1:0] AD_S; reg ad_convst; //ADC转换控制信号,A组通道和B组通道采用同一通道 //ADC工作于SPI模式,以及边读边转换模式,本次数据转换的同时,可以读出前一次转换的结果 assign O_ad_cs = ~((AD_S == 2'd3)&&I_ad_busy);//当AD7606工作在串行模式,cs=0代表输出通过SPI数据总线,输出之前的采样数据 assign O_ad_convsta = ad_convst; //ADC转换控制信号,A组通道和B组通道采用同一通道 assign O_ad_convstb = ad_convst; //ADC转换控制信号,A组通道和B组通道采用同一通道 always @(posedge I_ad_clk) begin if(O_ad_rst||I_ad_rst)begin ad_convst <= 1'b1; AD_S <= 2'd0; end else begin case(AD_S) 2'd0:if(clk_en2)begin //clk_en2,控制SCLK输出1,因此这里相当于SCLK上升沿 ad_convst <= 1'b0; //设置ad_convst=0 AD_S <= 2'd1;//下一状态 end 2'd1:if(clk_en2)begin //延迟50ns,ad_convst低电平至少25ns,对于SCLK时钟是20MHZ,所以这里ad_convst低电平是50ns ad_convst <= 1'b1;//ad_convst 低电平50ns后拉高为高电平 AD_S <= 2'd2;//下一状态 end 2'd2:if(clk_en2&&I_ad_busy)//延迟50ns,并且等待busy高,只要ADC正常工作,busy必然为高,说明ADC处于采样转换下 AD_S <= 2'd3; 2'd3:if(cycle_end)//ADC转换时间最大4.2us,5us周期结束,代表本次8通道完全采集结束 AD_S <= 2'd0; //回到初始状态,进行下一次采样 endcase end end |
4)、SPI采样单元
完成一次64bit数据传输需要64个时钟,采样的条件需满足三个(!ad_cs_o)&&clk_en1&&(nbits<8'd64),下降沿的时候开始采样,通过nbits计数。这里只要简单实用移位寄存器实现操作就可以。
//SPI采样 reg [7 : 0] nbits; // bits计数器,用于计数完成了从AD7606 SPI总线读出的bits数 wire ad_cap_en_r1 = (nbits==8'd64); //数据同步输出使能 reg ad_cap_en_r2 = 1'b0; //数据同步输出使能寄存一次 assign O_ad_cap_en = ({ad_cap_en_r1,ad_cap_en_r2}==2'b10);//高电平输出系统时钟的一个周期
always@(posedge I_ad_clk) begin //寄存一次ad_cap_en_r1 ad_cap_en_r2 <= ad_cap_en_r1; end
//当CS信号为低电平,每个SCL的下降沿输出采样数据,每组通道采样64bits wire cap_en = (!O_ad_cs)&&clk_en1&&(nbits<8'd64);
//ADC 串并转换模块 always@(posedge I_ad_clk) begin if(O_ad_rst) begin //ADC复位期间重置相关寄存器 O_ad_out_a <= 64'd0; O_ad_out_b <= 64'd0; nbits <= 8'd0; end else if(O_ad_cs)begin //高电平,重置 nbits nbits <= 8'd0; end else if(cap_en)begin//当CS信号为低电平,每个SCL的下降沿采样数据,每组通道采样64bits nbits <= nbits + 1'b1; // O_ad_out_a <= {O_ad_out_a[62:0],I_O_ad_out_a};//保存A组通道数据,V1~V4,每个通道16bits O_ad_out_b <= {O_ad_out_b[62:0],I_O_ad_out_b};//保存B组通道数据,V5~V8,每个通道16bits end end |
5 RTL仿真
AD7606 SPI串行仿真模型驱动
/*******************************AD7606 SPI串行仿真模型驱动********************* --以下是米联客设计的AD7606 SPI串行仿真驱动程序 --模拟AD7606的SPI 数据输出时序 *********************************************************************/
`timescale 1ns / 1ns//仿真时间刻度/精度
module ad7606( output reg ad_busy, //系统时钟输入 input ad_cs, //ad7606 CS信号输入,低电平SPI数据线输出AD7606寄存器数据 input ad_sclk, //ad7606 SCLK时钟输入 input ad_reset, //ad7606 复位输入 input ad_convsta, //ad7606 A组通道转换开始 input ad_convstb, //ad7606 B组通道转换开始 input ad_range, //ad7606 模拟输入范围,设置1范围:±10V,设置0范围±5V output ad_out_a, //A组通道采集有效数据输出 output ad_out_b //B组通道采集有效数据输出 );
//仿真信号,仿真busy忙 always @(posedge ad_convsta or posedge ad_reset) begin if(ad_reset)begin ad_busy = 1'b0; end else begin #10; ad_busy = 1'b1; #4100; //busy 持续4.1us ad_busy = 1'b0; end end
reg first ; //通过first设置,第一个时钟,不移位 reg[15:0] ad_da16 = 16'd0; //ad_da16,ADC的寄存器值 reg[63:0] ad_data; //4个16bit ADC值,放入此寄存器移位输出
assign ad_out_a = ad_data[63]; //a通道,SPI数据总线输出 assign ad_out_b = ad_data[63]; //b通道,SPI数据总线输出
always @(posedge ad_sclk or posedge ad_reset ) begin if(ad_reset)begin ad_data = 64'd0; first = 1'b0; end else begin if(!ad_cs) begin //当ad的CS为低电平,模拟AD寄存器串行数据输出 if(first == 1'b0) //通过first设置,第一个时钟,不移位 first = 1'b1; else ad_data[63:0] = {ad_data[62:0],ad_data[63]}; //模拟输出ADC的采集 end else if(ad_cs)begin//当ad的CS为高电平 first = 1'b0; ad_data ={ad_da16,ad_da16,ad_da16,ad_da16}; //初始化需要发送的ADC模拟值 end
end end
//每一个CS 上升沿,模拟ADC的值加1 always @(posedge ad_cs) begin ad_da16 = ad_da16[12:0] + 1'b1; end
endmodule
|
tb_AD7606SPI.v仿真文件
/*******************************AD7606 SPI串行采集仿真********************* *********************************************************************/ `timescale 1ns / 1ns//仿真时间刻度/精度
module tb_AD7606SPI();
reg I_sysclk; //系统时钟
initial begin #100 I_sysclk = 1'b0; end
always #10 I_sysclk = !I_sysclk; //50MHZ 系统时钟
//AD7606相关信号定义 wire I_ad_busy; wire O_ad_cs; wire O_ad_sclk; wire O_ad_reset; wire O_ad_convsta; wire O_ad_convstb; wire O_ad_range; wire I_O_ad_out_a; wire I_O_ad_out_b;
//例化AD7606的采集模块 ad7606_top ad7606_top_inst ( .I_sysclk(I_sysclk), //系统时钟输入 .I_ad_busy(I_ad_busy), //ad7606 忙标志位输入 .O_ad_cs(O_ad_cs), //ad7606 CS信号输出,低电平SPI数据线输出AD7606寄存器数据 .O_ad_sclk(O_ad_sclk), //ad7606 SCLK时钟输出 .O_ad_rst(O_ad_reset), //ADC复位输出 .O_ad_convsta(O_ad_convsta), //ad7606 A组通道转换 .O_ad_convstb(O_ad_convstb), //ad7606 B组通道转换 .O_ad_range(O_ad_range), //ad7606 模拟输入范围,设置1范围:±10V,设置0范围±5V .I_O_ad_out_a(I_O_ad_out_a), //A组通道采集有效数据输入 .I_O_ad_out_b(I_O_ad_out_b) //B组通道采集有效数据输入 );
//例化 AD7606 的SPI 仿真驱动 ad7606 ad7606_inst ( .ad_busy(I_ad_busy), //ad7606 忙标志位 输出 .ad_cs(O_ad_cs), //ad7606 CS信号输入,低电平SPI数据线输出AD7606寄存器数据 .ad_sclk(O_ad_sclk), //ad7606 SCLK时钟输入 .ad_reset(O_ad_reset), //ADC复位输入 .ad_convsta(O_ad_convsta), //ad7606 A组通道转换 .ad_convstb(O_ad_convstb), //ad7606 B组通道转换 .ad_range(O_ad_range),//ad7606 模拟输入范围,设置1范围:±10V,设置0范围±5V .ad_out_a(I_O_ad_out_a),//A组通道采集有效数据输出 .ad_out_b(I_O_ad_out_b)//B组通道采集有效数据输出 );
endmodule |
仿真波形图
在5us的采样周期内,我们可以看到convsta/convsta从高到低后,AD转换开始,busy信号变高,之后CS变并且可以看到CS的结束在busy信号后,所以可以确保最后读取的数据时已经转换好的数据。
上图通过把数据以模拟信号的方式显示可以看到锯齿波。
6 硬件接线
我们使用信号发生器产生两路频率分别为600HZ、300HZ,幅度为5V的正弦波。
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
我们使用米联客3V3的FEP-DAQ7606子卡。请确保下载器和开发板已经正确连接,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)
7 实验结果
设置Capture mode,设置cap_en信号为高时进行采样
测试输入波形只看通道1的输入,其他通道读者可以自行测试,结果一样。
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/17934682.html