基于FPGA的AD7606并行接口驱动程序_ad7606 fpga
前文讲解了AD7606的功能及原理,本文通过FPGA实现AD7606的并行接口数据采集,对应时序如下所示。
1、回顾时序
上电后首先拉高RESET复位AD7606,每隔一段时间给CONSTA/CONSTB一个上升沿锁存八个模拟通道当前数据,AD7606转换锁存的数据,转换完成时会将BUSY信号拉低,FPGA检测BUSY下降沿,拉低片选CS开始读取转换后的数据。
图1 采样转换时序
将片选CS和读使能短接,生成读使能RD信号,AD7606在RD下降沿依次输出8个通道转换后的16位补码数据,FPGA在RD上升沿接收对应数据。
图2 并行接口时序
采集数据时序如上所示,后文是使用FPGA实现该接口时序,注意前文提到的各个时序参数,比如复位RESET高电平最短持续时间为50ns,复位RESET下降沿与CONVSTA/CONVSTB上升沿最少间隔25ns等。
2、FPGA实现思路
通过使用状态机嵌套两个计数器的方式实现该接口时序,状态转换图如下所示。
初始位于空闲状态,上电经过一段时间(确保模块也已经上电,最好不要FPGA复位后就直接复位AD7606)后跳转到复位状态RST,复位AD7606。
之后在空闲状态IDLE每经过5us(最大采样率200KSPS对应周期)会跳转到采样锁存状态CON,给AD7606提供一个CONVSTA/CONVSTB上升沿,CONVSTA/CONVSTB低电平最少持续25ns。
图3 状态转换图
然后跳转到BUSY状态,等待AD7606转换完成。当检测到BUSY下降沿时,状态机跳转到读取数据状态(DATA),FPGA产生片选CS和读使能RD,在RD的上升沿采集AD7606输出数据,当读取8个通道数据后,状态机回到空闲状态。
注意AD7606逻辑接口供电为3.3V,CS和RD的低电平持续时间必须大于21ns,高电平持续时间必须大于22ns。如果选择100MHz作为时钟信号,那么CS和RD的高低电平至少持续三个系统时钟周期。
复位(RST)状态、采样信号生成(CON)状态、读数据(DATA)状态会共用同一个计数器cnt,来计数该状态需要持续的时间。
3、代码实现
接口信号如下所示,data是采集到的16位补码数据,data_vld[7:0]用于指示data数据是哪个通道的有效数据。例如data_vld[0]为高电平时,表示data是通道一的有效数据。
module ad7606_drive #( parameter FCLK = 100_000_000 ,//系统时钟频率,单位Hz,默认100MHz; parameter SMAPLE = 200_000 //AD7606采样频率,单位Hz,默认200KHz;)( input clk ,//系统时钟,100MHz; input rst_n ,//系统复位,低电平有效; input busy ,//转换完成指示信号,下降沿有效; input frstdata ,//指示采集到的第一个数据; input [15 : 0] adc_din ,//AD7606所采集到的十六位数据信号; output reg cs = 1\'b1 ,//AD7606片选信号,读数据时拉低; output reg rd = 1\'b1 ,//AD7606读使能信号,读数据时拉低,下降沿时AD7606将数据发送到数据线上,上升沿时可以读出数据; output reg reset = 1\'b0 ,//AD7606复位信号,高电平有效,每次复位至少拉高50ns; output [2 : 0] os ,//AD7606过采样模式信号,默认不使用过采样; output reg convst = 1\'b1 ,//AD7606采样启动信号,无效时高电平,采样计数器完成时拉低两个时钟; output reg [15 : 0] data = \'d0 ,//AD7606采集到的数据.数据均为补码; output reg [7 : 0] data_vld= \'d0 //指示AD7606输出的数据来自哪个数据通道;);
以下是状态机的跳转,与前面的状态转换图相对应。
//状态机次态到现态的转换; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin state_c <= IDLE; end else begin state_c <= state_n; end end //状态机次态的跳转; always@(*)begin case(state_c) IDLE : begin if(end_delay_cnt)begin//延时计数器计数结束时 state_n = rst_flag ? CON : RST;//如果上电复位过,则直接采样数据,否则先进行复位; end else begin state_n = state_c; end end RST : begin//该状态高电平至少持续50ns;复位低电平到convst高电平至少持续25ns。 if(end_cnt)begin state_n = IDLE; end else begin state_n = state_c; end end CON : begin//该状态至少持续25ns; if(end_cnt)begin state_n = BUSY; end else begin state_n = state_c; end end BUSY : begin if(busy_neg)begin//检测到busy下降沿后,开始采集数据; state_n = DATA; end else begin state_n = state_c; end end DATA : begin//读数据状态的读使能高电平至少持续25ns,低电平至少持续32ns。 if(end_cnt)begin state_n = IDLE; end else begin state_n = state_c; end end default : begin state_n = IDLE; end endcase end
下面是生成复位标志信号rst_flag,初始值为低电平,状态机处于复位状态结束时拉高,表示上电后已经对AD7606复位一次了。
AD7606复位信号reset初始值为低电平,状态机处于复位状态时拉高,否则拉低。
//复位状态指示信号,高电平表示上电后完成过复位; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin rst_flag <= \'d0; end else if(state_c == RST && end_cnt)begin rst_flag <= 1\'b1;//复位完成时拉高,之后保持不变; end end
延时计数器delay_cnt对时钟计数,每隔采样率对应周期后清零。状态机处于空闲状态时检测到该计数器计数结束,如果复位过AD7606(rst_flag为高电平),则状态机跳转到采样保存状态(CON),生成CONVSTA/B信号上升沿。
//延时计数器,初始值为0。 always@(posedge clk or negedge rst_n)begin if(~rst_n)begin delay_cnt <= \'d0; end else if(end_delay_cnt)begin delay_cnt <= \'d0; end else begin delay_cnt <= delay_cnt + \'d1; end end //延时计数器计数到最大减2之后拉高,其余时间均拉低; always@(posedge clk)begin end_delay_cnt <= (delay_cnt == DIV_NUM - \'d2); end
复位信号RESET、采样保持信号CONVSTA/B的电平持续时间都有最短要求,因此需要计数器cnt计数系统时钟周期。同时可以用来计数状态机处于读数据状态时已经读取的并行数据个数。
//计数器cnt,加一条件处于采样,读数据,复位三个阶段,结束条件为计数cnt_num个时钟; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin cnt <= \'d0; end else if(add_cnt)begin if(end_cnt) cnt <= \'d0; else cnt <= cnt + \'d1; end end assign add_cnt = (state_c==CON) || ((state_c==DATA) && (rdata_cnt == \'d6)) || (state_c==RST); assign end_cnt = add_cnt && (cnt == cnt_num - \'d1);
计数器cnt的最大值取决于状态机的状态,CONVSTA/B低电平最少持续25ns,系统时钟周期10ns,此处取3个系统时钟,即30ns。复位RESET高电平最少持续50ns,此处取8个时钟,即80ns。每次需要读取8个通道的数据,因此读数据状态计数器最大值为8。
//cnt_num的值,采样阶段为3,读数据阶段为8,复位阶段为5(复位这里也使用8个时钟); always@(posedge clk or negedge rst_n)begin if(~rst_n)begin cnt_num <= \'d3; end else if(state_c == IDLE)begin cnt_num <= \'d3; end else if(state_c == BUSY)begin cnt_num <= \'d8; end else if(state_c == RST)begin cnt_num <= \'d8; end end
处于采样保持状态时拉低con,处于复位状态时拉高reset,如下所示。Busy是ad7606输出给FPGA的异步信号,因此先使用两级触发器同步信号,然后在检测下降沿作为状态机的判断条件。
always@(posedge clk)begin convst <= (state_c != CON);//采样触发信号,处于采样阶段时拉低,其余时间拉高; reset <= (state_c == RST);//复位电平最少持续50ns; busy_r <= {busy_r[1:0] , busy};//使用移位寄存器将采集的信号暂存; busy_neg <= busy_r[2] && (~busy_r[1]);//检测busy下降沿,表示数据是否完成采样; end
还需要一个计数器在读数据状态下计数片选CS和读使能RD的周期,当CS和RD短接(同时变化)时,逻辑接口3.3V供电,低电平持续最短32ns,高电平持续最短时间为22ns。此处低电平取4个时钟,高电平取3个时钟,因此计数器最大值为7-1,如下所示。
//计数器rdata_cnt,计数读数据时每读一个数据所需要的时钟,加一条件为处于读数据阶段,结束条件为计数6个时钟; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin rdata_cnt <= \'d0; end else if(state_c == DATA)begin if(rdata_cnt == 7 - 1) rdata_cnt <= \'d0; else rdata_cnt <= rdata_cnt + \'d1; end else begin rdata_cnt <= \'d0; end end
生成片选CS和读使能信号,当计数器rd_cnt等于2时拉低,等于6时拉高,状态机不处于读数据状态时拉高。
//片选和读使能信号,当处于读数据阶段并且计数器cnt1计数大于2时拉低,其余时间拉高; always@(posedge clk)begin if(state_c == DATA)begin if(rdata_cnt == 6)begin//当计数器计数结束时拉高,高电平最少持续22ns; cs <= 1\'b1; rd <= 1\'b1; end else if(rdata_cnt == 2)begin//当计数器计数到2时拉低,低电平最好大于32ns; cs <= 1\'b0; rd <= 1\'b0; end end else begin//状态机处于其余状态时片选和读使能信号拉高; cs <= 1\'b1; rd <= 1\'b1; end end
因此计数器rd_cnt等于6表示读使能RD上升沿,此时依次读取AD76068个通道转换完成的16位补码数据,同时将输出数据有效指示信号对应位拉高。
//当读数据计数器计数结束时,根据计数器的数值读取对应通道数据; always@(posedge clk)begin if((state_c == DATA) && (rdata_cnt == 6) && add_cnt)begin data <= adc_din; data_vld <= (8\'h01 << cnt); end else begin data <= data; data_vld <= 8\'d0; end end
整个模块参考代码如下所示,后文进行仿真和上板测试(不是所有触发器都有复位,很多没有必要的复位可以去掉,进而减小复位信号扇出,在大项目中很有用):
//--###############################################################################################//--#//--# File Name: ad7606_control//--# Designer: 数字站//--# Tool: Quartus 2018.1//--# Design Date: 2024.10.10//--# Description: ad7606接口驱动//--# Version: 0.0//--# Coding scheme: UTF-8(If the Chinese comment of the file is garbled, please do not save it and check whether the file is opened in GBK encoding mode)//--#//--###############################################################################################module ad7606_drive #( parameter FCLK = 100_000_000 ,//系统时钟频率,单位Hz,默认100MHz; parameter SMAPLE = 200_000 //AD7606采样频率,单位Hz,默认200KHz;)( input clk ,//系统时钟,100MHz; input rst_n ,//系统复位,低电平有效; input busy ,//转换完成指示信号,下降沿有效; input frstdata ,//指示采集到的第一个数据; input [15 : 0] adc_din ,//AD7606所采集到的十六位数据信号; output reg cs = 1\'b1 ,//AD7606片选信号,读数据时拉低; output reg rd = 1\'b1 ,//AD7606读使能信号,读数据时拉低,下降沿时AD7606将数据发送到数据线上,上升沿时可以读出数据; output reg reset = 1\'b0 ,//AD7606复位信号,高电平有效,每次复位至少拉高50ns; output [2 : 0] os ,//AD7606过采样模式信号,默认不使用过采样; output reg convst = 1\'b1 ,//AD7606采样启动信号,无效时高电平,采样计数器完成时拉低两个时钟; output reg [15 : 0] data = \'d0 ,//AD7606采集到的数据.数据均为补码; output reg [7 : 0] data_vld= \'d0 //指示AD7606输出的数据来自哪个数据通道;); localparam IDLE = 5\'b00001 ;//空闲状态; localparam CON = 5\'b00010 ;//采样状态; localparam BUSY = 5\'b00100 ;//等待模数转换完成; localparam DATA = 5\'b01000 ;//读数据状态; localparam RST = 5\'b10000 ;//复位ADC状态; localparam DIV_NUM = FCLK / SMAPLE ;//计算采样率对应时钟个数; localparam DIV_NUM_W = $clog2(DIV_NUM-1) ;//使用函数自动计算采样率对应位宽; reg [4 : 0] state_c ; reg [4 : 0] state_n ; reg [3 : 0] cnt ; reg [3 : 0] cnt_num ;// reg busy_neg = 1\'b0 ; reg [2 : 0] busy_r = 3\'b111; reg [2 : 0] rdata_cnt ; reg [DIV_NUM_W - 1 : 0] delay_cnt ; reg end_delay_cnt = \'d0 ; reg rst_flag ; wire add_cnt ; wire end_cnt ; assign os = 3\'b000;//默认不使用过采样; //状态机次态到现态的转换; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin state_c <= IDLE; end else begin state_c <= state_n; end end //状态机次态的跳转; always@(*)begin case(state_c) IDLE : begin if(end_delay_cnt)begin//延时计数器计数结束时 state_n = rst_flag ? CON : RST;//如果上电复位过,则直接采样数据,否则先进行复位; end else begin state_n = state_c; end end RST : begin//该状态高电平至少持续50ns;复位低电平到convst高电平至少持续25ns。 if(end_cnt)begin state_n = IDLE; end else begin state_n = state_c; end end CON : begin//该状态至少持续25ns; if(end_cnt)begin state_n = BUSY; end else begin state_n = state_c; end end BUSY : begin if(busy_neg)begin//检测到busy下降沿后,开始采集数据; state_n = DATA; end else begin state_n = state_c; end end DATA : begin//读数据状态的读使能高电平至少持续25ns,低电平至少持续32ns。 if(end_cnt)begin state_n = IDLE; end else begin state_n = state_c; end end default : begin state_n = IDLE; end endcase end //复位状态指示信号,高电平表示上电后完成过复位; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin rst_flag <= \'d0; end else if(state_c == RST && end_cnt)begin rst_flag <= 1\'b1;//复位完成时拉高,之后保持不变; end end //延时计数器,初始值为0。 always@(posedge clk or negedge rst_n)begin if(~rst_n)begin delay_cnt <= \'d0; end else if(end_delay_cnt)begin delay_cnt <= \'d0; end else begin delay_cnt <= delay_cnt + \'d1; end end //延时计数器计数到最大减2之后拉高,其余时间均拉低; always@(posedge clk)begin end_delay_cnt <= (delay_cnt == DIV_NUM - \'d2); end //计数器cnt,加一条件处于采样,读数据,复位三个阶段,结束条件为计数cnt_num个时钟; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin cnt <= \'d0; end else if(add_cnt)begin if(end_cnt) cnt <= \'d0; else cnt <= cnt + \'d1; end end assign add_cnt = (state_c==CON) || ((state_c==DATA) && (rdata_cnt == \'d6)) || (state_c==RST); assign end_cnt = add_cnt && (cnt == cnt_num - \'d1); //cnt_num的值,采样阶段为3,读数据阶段为8,复位阶段为5(复位这里也使用8个时钟); always@(posedge clk or negedge rst_n)begin if(~rst_n)begin cnt_num <= \'d3; end else if(state_c == IDLE)begin cnt_num <= \'d3; end else if(state_c == BUSY)begin cnt_num <= \'d8; end else if(state_c == RST)begin cnt_num <= \'d8; end end always@(posedge clk)begin convst <= (state_c != CON);//采样触发信号,处于采样阶段时拉低,其余时间拉高; reset <= (state_c == RST);//复位电平最少持续50ns; busy_r <= {busy_r[1:0] , busy};//使用移位寄存器将采集的信号暂存; busy_neg <= busy_r[2] && (~busy_r[1]);//检测busy下降沿,表示数据是否完成采样; end //计数器rdata_cnt,计数读数据时每读一个数据所需要的时钟,加一条件为处于读数据阶段,结束条件为计数6个时钟; always@(posedge clk or negedge rst_n)begin if(~rst_n)begin rdata_cnt <= \'d0; end else if(state_c == DATA)begin if(rdata_cnt == 7 - 1) rdata_cnt <= \'d0; else rdata_cnt <= rdata_cnt + \'d1; end else begin rdata_cnt <= \'d0; end end //片选和读使能信号,当处于读数据阶段并且计数器cnt1计数大于2时拉低,其余时间拉高; always@(posedge clk)begin if(state_c == DATA)begin if(rdata_cnt == 6)begin//当计数器计数结束时拉高,高电平最少持续22ns; cs <= 1\'b1; rd <= 1\'b1; end else if(rdata_cnt == 2)begin//当计数器计数到2时拉低,低电平最好大于32ns; cs <= 1\'b0; rd <= 1\'b0; end end else begin//状态机处于其余状态时片选和读使能信号拉高; cs <= 1\'b1; rd <= 1\'b1; end end //当读数据计数器计数结束时,根据计数器的数值读取对应通道数据; always@(posedge clk)begin if((state_c == DATA) && (rdata_cnt == 6) && add_cnt)begin data <= adc_din; data_vld <= (8\'h01 << cnt); end else begin data <= data; data_vld <= 8\'d0; end end endmodule
文末提供了完整代码,后台不再提供获取方式,下文会提供对应工程获取方式。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!