作者:数字站
前文讲解了AD7606的功能及原理,本文通过FPGA实现AD7606的并行接口数据采集,对应时序如下所示。
回顾时序
上电后首先拉高RESET复位AD7606,每隔一段时间给CONSTA/CONSTB一个上升沿锁存八个模拟通道当前数据,AD7606转换锁存的数据,转换完成时会将BUSY信号拉低,FPGA检测BUSY下降沿,拉低片选CS开始读取转换后的数据。
![1736127572194168.jpg 图1 采样转换时序.JPG](https://cdn.eetrend.com/files/ueditor/108/upload/image/20250106/1736127572194168.jpg)
![1736127589744504.jpg 图2 并行接口时序.JPG](https://cdn.eetrend.com/files/ueditor/108/upload/image/20250106/1736127589744504.jpg)
采集数据时序如上所示,后文是使用FPGA实现该接口时序,注意前文提到的各个时序参数,比如复位RESET高电平最短持续时间为50ns,复位RESET下降沿与CONVSTA/CONVSTB上升沿最少间隔25ns等。
FPGA实现思路
![1736127606832276.jpg 图3 状态转换图.JPG](https://cdn.eetrend.com/files/ueditor/108/upload/image/20250106/1736127606832276.jpg)
代码实现
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
//复位状态指示信号,高电平表示上电后完成过复位; 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
//--############################################################################################### //--# //--# 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
文末提供了完整代码,后台不再提供获取方式,下文会提供对应工程获取方式。
本文转载自:数字站