09 ADC模块FEP-DAQ7606采集显示波形方案
软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA
登录米联客(MiLianKe)FPGA社区-www.uisrc.com观看免费视频课程、在线答疑解惑!
1 概述
本方案通过把DAQ7606采集到的数据,通过前面已经完成的示波器显示驱动进行在屏幕上显示ADC采集的波形数据。
2 系统框图
3 产品介绍
FEP-DAQ001-16-200K-8集成了一颗AD7606-8 高精度ADC芯片,AD7606-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 带宽。
FEP-DAQ001-16-200K-8采用SPI通信,实现了同时8通道ADC同时采集。信号通过SMA接口引出,默认焊接4路SMA接口。
3.1硬件参数概述
FEP-DAQ001-16-200K-8 | |
ADC芯片 | AD7606 |
采样精度 | 16bit |
-3db带宽 | -5V~+5V 15KHZ -10V~+10V 23KHZ |
IO电平 | 1.8V或者3.3V可选(ad7606只支持3.3V通过电平芯片转换) |
采样频率 | 200KSPS |
模拟通道 | 8个 |
触发IO | 0个 |
输入电平 | -5V~+5V 或-10V~+10V |
数据格式 | 二进制补码(~32768 ~ +32767) |
信噪比1KHZ(SNR) | -5V~+5V 89db -10V~+10V 90db |
无杂散动态范围170M(SFDR) | -108db |
功耗165M | Ad7606 100mW |
占用IO数量 | 10个GPIO |
3.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了解。
3.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代码设计就要能够满足这些参数指标。
3.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路。
4硬件电路分析
硬件接口和子卡模块请阅读"附录 1"
配套工程的 FPGA PIN 脚定义路径为 fpga_prj/uisrc/04_pin/ fpga_pin.xdc。
5 程序源码
关于HDMI输出IP的部分这里不再介绍,VTC时序设计部分这里也不详细介绍。如果读者这些基础知识不清楚的,请阅读前面的实验。
5.1 AD7606 FPGA程序设计
5.1.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 |
5.1.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.2 波形显示驱动
5.2.1 波形绘制显示原理
通过前面关于VTC IP视频时序驱动的学习,以及TPG IP测试图形显示的学习,我们知道,对于显示器上的图像,是从液晶屏的做上角,一个像素点一个像素点绘制,当一行所有绘制完成,进行下一行的绘制。利用肉眼的视觉暂留原理,一般1秒显示25帧以上,我们就能看到视频是动态的。
本方案中,我们绘制的波形曲线只需要显示波形的数据点,比如对于1920*1080的显示区域,我们时间上只要绘制1920点波形点,即可。
为了方便我们理解,我们定义HS方向是X坐标,VS方向是Y坐标。
比如我们这里设计的是显示1024个波形数据点,在绘制每一行的图像的时候,比对每一个数据和VS的Y坐标是否相等,如果相等就绘制这个波形点。这样我们就能完成1024个波形点在整个屏幕的显示。
5.2.2 画中画的vtc视频时序模块设计
我们这里显示的波形数据点是1024,高度是256,因此我们需要实现一个画中画的功能。栅格绘制,以及波形数据点会以画中画的有效区域进行显示。
支持画中画的uivtc.v源码
/*************uivtc(video timing controller)视频时序控制器************* --版本号1.1 --以下是米联客设计的uivtc(video timing controller)视频时序控制器 --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨 --2.使用方便,只需要输入6个参数既可以实现对不同视频分辨率时序的控制 --3.该视频时序控制,一个时钟对应一个像素 --4.通常我们说的像素,比如1080P代表了1920*1080是指视频的有效显示区域,实际的视频还包含不能显示的区域,比如行同步,场同步时间 --5.通常我们说的行视频信号,也称之为视频的水平像素信号;场视频信号,也称之为视频的垂直像素信号; --6.针对波形绘制,增加画中画绘制区域功能 *********************************************************************/ `timescale 1ns / 1ns //仿真时间刻度/精度
module uivtc# ( parameter H_ActiveSize = 1980, //视频时间参数,行视频信号,一行有效(需要显示的部分)像素所占的时钟数,一个时钟对应一个有效像素 parameter H_FrameSize = 1920+88+44+148, //视频时间参数,行视频信号,一行视频信号总计占用的时钟数 parameter H_SyncStart = 1920+88, //视频时间参数,行同步开始,即多少时钟数后开始产生行同步信号 parameter H_SyncEnd = 1920+88+44, //视频时间参数,行同步结束,即多少时钟数后停止产生行同步信号,之后就是行有效数据部分
parameter V_ActiveSize = 1080, //视频时间参数,场视频信号,一帧图像所占用的有效(需要显示的部分)行数量,通常说的视频分辨率即H_ActiveSize*V_ActiveSize parameter V_FrameSize = 1080+4+5+36, //视频时间参数,场视频信号,一帧视频信号总计占用的行数量 parameter V_SyncStart = 1080+4, //视频时间参数,场同步开始,即多少行数后开始产生场同步信号 parameter V_SyncEnd = 1080+4+5, //视频时间参数,场同步结束,即多少场数后停止产生场同步信号,之后就是场有效数据部分
parameter H2_ActiveSize = 640, parameter V2_ActiveSize = 480 ) ( input I_vtc_rstn,//系统复位 input I_vtc_clk, //系统时钟 output reg O_vtc_vs, //场同步输出 output reg O_vtc_hs, //行同步输出 output reg O_vtc_de, //视频数据有效 input [11:0] I_vtc2_offset_x,//相对屏幕原点(左上角)X方向偏移 input [11:0] I_vtc2_offset_y,//相对屏幕原点(左上角)Y方向偏移 output reg O_vtc2_de //绘制有效的显示区域 );
reg [11:0] hcnt = 12'd0; //行像素计数器,寄存器 reg [11:0] vcnt = 12'd0; //场像素计数器,寄存器 reg [2 :0] rst_cnt = 3'd0; //复位计数器,寄存器 wire rst_sync = rst_cnt[2]; //同步复位
always @(posedge I_vtc_clk or negedge I_vtc_rstn)begin //通过计数器产生同步复位 if(I_vtc_rstn == 1'b0) rst_cnt <= 3'd0; else if(rst_cnt[2] == 1'b0) rst_cnt <= rst_cnt + 1'b1; end
//行像素计数器 always @(posedge I_vtc_clk)begin if(rst_sync == 1'b0) //复位 hcnt <= 12'd0; else if(hcnt != (H_FrameSize - 1'b1))//计数范围从0 ~ H_FrameSize-1 hcnt <= hcnt + 1'b1; else hcnt <= 12'd0; end
//场计数器,用于计数已经完成的行视频信号 always @(posedge I_vtc_clk)begin if(rst_sync == 1'b0) vcnt <= 12'd0; else if(hcnt == (H_ActiveSize - 1'b1)) begin//是否一行像素结束 vcnt <= (vcnt == (V_FrameSize - 1'b1)) ? 12'd0 : vcnt + 1'b1;//每一行计数,场计数器加1,计数范围0~V_FrameSize - 1 end end
wire hs_valid = hcnt < H_ActiveSize; //行信号有效像素部分 wire vs_valid = vcnt < V_ActiveSize; //场信号有效像素部分 wire vtc_hs = (hcnt >= H_SyncStart && hcnt < H_SyncEnd);//产生hs,行同步信号 wire vtc_vs = (vcnt > V_SyncStart && vcnt <= V_SyncEnd);//产生vs,场同步信号 wire vtc_de = hs_valid && vs_valid;//只有当行像素有效和场像素同时有效,视频数据部分才是有效
//画中画,波形绘制区域 wire hs2_valid = (hcnt>=I_vtc2_offset_x)&& (hcnt<(I_vtc2_offset_x+H2_ActiveSize)); //画中画,波形绘制区域HS有效信号 wire vs2_valid = (vcnt>=I_vtc2_offset_y)&& (vcnt<(I_vtc2_offset_y+V2_ActiveSize)); //画中画,波形绘制区域VS有效信号 wire vtc2_de = hs2_valid && vs2_valid; //画中画,数据有效绘制信号
//完一次寄存打拍输出,有利于改善时序,尤其对于高分辨率,高速的信号,打拍可以改善内部时序,以运行于更高速度 always @(posedge I_vtc_clk)begin if(rst_sync == 1'b0)begin O_vtc_vs <= 1'b0; O_vtc_hs <= 1'b0; O_vtc_de <= 1'b0; O_vtc2_de <= 1'b0; end else begin O_vtc_vs <= vtc_vs; //场同步信号打拍输出 O_vtc_hs <= vtc_hs; //行同步信号打拍输出 O_vtc_de <= vtc_de; //视频有效信号打拍输出 O_vtc2_de <= vtc2_de; //画中画,数据有效绘制信号 end end
endmodule |
5.2.3 uiwave.v源码
uiwave.v
/*************uiwave简易波形绘制驱动****************************** --版本号1.0 --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨 --2.使用方便,只需要输入ADC的值,就能完成波形绘制 --3.占用资源少,波形输入8bits ADC值,存储到BLOCK RAM 只需要1048*8bit 大小的BRAM,即可完成1通道的波形存储 --4.乒乓绘制,当绘制一个波形的时候,另外个波形存储到另外一段地址空间 --5.绘制过程中,每一行数据都读出和Y坐标匹配,如果匹配成功,使能O_pixel_en绘制这个数据点 --6.背景绘制黑色 --7.栅格绘制白色 --8.支持多通道绘制 *********************************************************************/
`timescale 1ns / 1ns module uiwave ( //波形1 input I_wave1_clk, //波形1时钟 input [7 :0] I_wave1_data, //波形1数据 input I_wave1_data_de, //波形1数据有效
//波形2 input I_wave2_clk, //波形2时钟 input [7 :0] I_wave2_data, //波形2数据 input I_wave2_data_de, //波形2数据有效
//VTC时序输入 input I_vtc_rstn, //时序复位输入 input I_vtc_clk, //时序时钟输入 input I_vtc_vs, //VS-帧同步,信号同步输入 input I_vtc_de, //de有效区域,信号同步输入
//同步时序输出,以及像素输出 output O_vtc_vs, //帧同步输出 output O_vtc_de, //de信号同步后输出 output reg [23:0] O_vtc_rgb //同步输出显示颜色 );
reg [1 :0] vtc_vs_r; //vs寄存器 reg [1 :0] vtc_de_r; //de寄存器 reg [11 :0] vcnt,hcnt;//vcnt计数有多少行,hcnt计数有多少列
reg grid_de; //栅格绘制使能
assign O_vtc_vs = vtc_vs_r[0]; //同步后输出O_vtc_vs assign O_vtc_de = vtc_de_r[0]; //同步后输出O_vtc_de
//寄存,同步 always @(posedge I_vtc_clk)begin vtc_vs_r <= {vtc_vs_r[0],I_vtc_vs}; vtc_de_r <= {vtc_de_r[0],I_vtc_de}; end
//以下hcnt用于计数列,vcnt用于计数行数
//hcnt像素计数器 always @(posedge I_vtc_clk)begin if(hcnt == 1023) hcnt <= 12'd0; else if(vtc_de_r[0] && (hcnt != 1023)) //hcnt计数列,共计1024个像素 hcnt <= hcnt + 1'b1; end
//vcnt计数有多少行 always @(posedge I_vtc_clk)begin if(vtc_vs_r == 2'b01) vcnt <= 8'd0; else if((vtc_de_r == 2'b10) && (vcnt != 255)) //以de信号用于计数行,共计256行 vcnt <= vcnt + 1'b1; end
//栅格绘制 always @(posedge I_vtc_clk)begin if((hcnt[2:0]==7&&(vcnt[5:0]==63||vcnt == 0))||((hcnt[5:0]==63||hcnt==0)&&vcnt[2:0]==7)||(vcnt == 0 && hcnt==0)) grid_de <= O_vtc_de; else grid_de <= 1'b0; end
//1--绘制波形曲线1,绿色点 //2--绘制波形曲线2,黄色点 //3--绘制栅格虚线,白色点 //4--绘制背景色,黑色 always @(posedge I_vtc_clk)begin casex({grid_de,wave2_pixel_en,wave1_pixel_en}) 3'bxx1: O_vtc_rgb <= {8'h00,8'hff,8'h00}; //wave1信号显示像素颜色 3'bx10: O_vtc_rgb <= {8'hff,8'hff,8'h00}; //wave2信号显示像素颜色 3'b100: O_vtc_rgb <= {8'h96,8'h96,8'h96}; //网格显示像素为白色点 default: O_vtc_rgb <= {8'h00,8'h00,8'h00}; //黑色背景 endcase end
//波形缓存1,以及波形绘制像素点输出使能 uiwave_buf uiwave1_buf_inst ( .I_wave_clk(I_wave1_clk), //写数据输入时钟,和ADC采集时钟同步 .I_wave_data(I_wave1_data),//写数据 .I_wave_data_de(I_wave1_data_de),//写数据有效 .I_vtc_clk(I_vtc_clk), //VTC时序发生器时钟输入 .I_vtc_rstn(I_vtc_rstn), //VTC时序发生器复位 .I_vtc_de_r(vtc_de_r[0]), //VTC时序发生器的de有效区域输入 .I_vtc_vs(I_vtc_vs), //VTC时序发生器的VS同步信号输入 .I_vtc_vcnt(vcnt), //vtc的数据偏移,主要对有符号数据进行调整 .O_pixel_en(wave1_pixel_en) //输出输出使能 );
//波形缓存2,以及波形绘制像素点输出使能 uiwave_buf uiwave2_buf_inst ( .I_wave_clk(I_wave2_clk), //写数据输入时钟,和ADC采集时钟同步 .I_wave_data(I_wave2_data), //写数据 .I_wave_data_de(I_wave2_data_de),//写数据有效 .I_vtc_clk(I_vtc_clk), //VTC时序发生器时钟输入 .I_vtc_rstn(I_vtc_rstn), //VTC时序发生器复位 .I_vtc_de_r(vtc_de_r[0]), //VTC时序发生器的de有效区域输入 .I_vtc_vs(I_vtc_vs), //VTC时序发生器的VS同步信号输入 .I_vtc_vcnt(vcnt), //vtc的数据偏移,主要对有符号数据进行调整 .O_pixel_en(wave2_pixel_en) //输出输出使能 );
endmodule |
uiwave_buf.v源码中有以下代码
assign O_pixel_en = I_vtc_de_r&(I_vtc_vcnt[7:0] == wave_data[7:0]); |
pixel_en_o信号就是wave1_pixel_en和wave2_pixel_en信号,在上级对wave1_pixel_en跟wave2_pixel_en
有效的像素赋值,输出波形点颜色,grid_de有效的区域显示栅格点,其他区域显示黑色背景。
Uiwave.v绘制背景、栅格、波形点的代码如下:
//1--绘制波形曲线1,绿色点 //2--绘制波形曲线2,黄色点 //3--绘制栅格虚线,白色点 //4--绘制背景色,黑色 always @(posedge I_vtc_clk)begin casex({grid_de,wave2_pixel_en,wave1_pixel_en}) 3'bxx1: O_vtc_rgb <= {8'h00,8'hff,8'h00}; //wave1信号显示像素颜色 3'bx10: O_vtc_rgb <= {8'hff,8'hff,8'h00}; //wave2信号显示像素颜色 3'b100: O_vtc_rgb <= {8'h96,8'h96,8'h96}; //网格显示像素为白色点 default: O_vtc_rgb <= {8'h00,8'h00,8'h00}; //黑色背景 endcase end |
5.2.4 uiwave_buf.v通过BRAM缓存ADC数据
通过控制buf_flag信号,每次读写的地址进行切换,确保不会对同一个地址同时进行读写,造成数据冲突。
/*************uiwave_buf简易波形绘制驱动****************************** --版本号1.0 --1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨 --2.使用方便,只需要输入ADC的值,就能完成波形绘制 --3.占用资源少,波形输入8bits ADC值,存储到BLOCK RAM 只需要1048*8bit 大小的BRAM,即可完成1通道的波形存储 --4.乒乓绘制,当绘制一个波形的时候,另外一个波形存储到另外一段地址空间 --5.绘制过程中,每一行数据都读出和Y坐标匹配,如果匹配成功,使能O_pixel_en绘制这个数据点 *********************************************************************/ `timescale 1ns / 1ns //仿真时间刻度/精度
module uiwave_buf ( input I_wave_clk, //写数据输入时钟,和ADC采集时钟同步 input [7 :0] I_wave_data, //写数据 input I_wave_data_de,//写数据有效 input I_vtc_clk, //VTC时序发生器时钟输入 input I_vtc_rstn, //VTC时序发生器复位 input I_vtc_vs, //VTC时序发生器的VS同步信号输入 input I_vtc_de_r, //VTC时序发生器的de有效区域输入 input [7 :0] I_vtc_vcnt, //vtc的数据偏移,主要对有符号数据进行调整 output O_pixel_en //输出输出使能 );
//BRAM 简单双口BRAM reg [9 :0] addra = 0; //BRAM 通道A地址 //reg ena = 0; //BRAM 通道A使能 reg wea = 0; //BRAM 通道A写使能 reg [9 :0] addrb = 0; //BRAM 通道B地址 reg enb = 0; //BRAM 通道B读使能 reg [0 :0] WR_S,RD_S; //写状态机,读状态机 reg buf_flag;//buf_flag用于乒乓地址缓存切换 reg addr0_en;//用于设置写第一个数据相对地址0
wire [7 :0] wave_data;//写波形数据到BRAM reg [3 :0] async_vtc_vs =0; //同步信号
always @(posedge I_wave_clk)begin //对异步I_vtc_vs采样 async_vtc_vs <= {async_vtc_vs[2:0],I_vtc_vs}; end
//绘制波形数据点使能,绘制原理: //当匹配到存储的ADC数据和正在扫描的Y坐标值一致就输出,每个X坐标方向绘制1个波形点 assign O_pixel_en = I_vtc_de_r&(I_vtc_vcnt[7:0] == wave_data[7:0]);
//写BRAM 状态机 always @(posedge I_wave_clk or negedge I_vtc_rstn)begin if(I_vtc_rstn == 1'b0)begin //复位重置所有寄存器 addra <= 10'd0; addr0_en <= 1'b1; wea <= 1'b0; buf_flag <= 1'b0; WR_S <= 1'd0; end else begin case(WR_S) //写状态机 0:begin if(I_wave_data_de)begin //有效波形数据点 if(addra == 1023)begin //1024个数据写完 wea <= 1'b0; //停止写 addra <= 0; //相对地址设置0 addr0_en <= 1'b1; WR_S <= 1'd1;//进入状态机1 end else begin //写入1024个数据 wea <= 1'b1; //写使能 addr0_en <= 1'b0; addra <= (addr0_en == 1'b0) ? (addra + 1'b1) : 0;//相对地址递增 end end else begin wea <= 1'b0; end end 1:begin //等待VTC时序同步 if(async_vtc_vs[3:2] == 2'b10)begin//当数据同步后,准备下一次写 WR_S <= 1'd0; //回到状态0 buf_flag <= ~buf_flag;//乒乓地址切换 end end default:WR_S <= 2'd0; endcase end end
//读BRAM 状态机 always @(posedge I_vtc_clk or negedge I_vtc_rstn)begin if(I_vtc_rstn == 1'b0)begin//复位重置所有寄存器 addrb <= 10'd0; RD_S <= 1'd0; end else begin case(RD_S) 0:begin if(I_vtc_de_r)begin //I_vtc_de_r代表了有效绘制区域 if(addrb == 1023)begin //1024个数据读完 addrb <= 0; //相对地址设置0 RD_S <= 1'd1; //进入状态1 end else //没一样都会扫描所有的ADC数据 addrb <= addrb + 1'b1;//相对地址递增 end end 1:begin if(I_vtc_de_r == 0) //等待de变为0 RD_S <= 0; //回到状态0重新扫描
end default:RD_S <= 1'd0; endcase end end
wave_ram buf_inst( .dina(I_wave_data), //写入波形数据 .addra({buf_flag,addra}), //写地址,其中addra是相对地址,buf_flag是地址高位,用于读写的乒乓切换 .wea(wea), //写使能 .clka(I_wave_clk),//写时钟 .doutb(wave_data), //读出的波形数据 .addrb({~buf_flag,addrb}), //写地址,其中addrb是相对地址,buf_flag是地址高位,用于读写的乒乓切换 .clkb(I_vtc_clk)//读时钟 ); endmodule |
5.2.5
BRAM IP设置
数据输入8bits,深度2048,这样设计可以使用最少的BRAM完成2段曲线的存储。所以如果是ADC数据,输入高8bit即可。
5.3 顶层模块调用程序
/**********************AD7606 ADC采集波形显示************************* *********************************************************************/ `timescale 1ns / 1ns//仿真时间刻度/精度
module ad7606_top ( input I_sysclk, //系统时钟输入 input I_ad_busy, //ad7606 忙标志位 output O_ad_cs, //ad7606 CS信号输出,低电平SPI数据线输出AD7606寄存器数据 output 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 O_card_power_en, //子卡电源使能
output O_hdmi_clk_p, //HDMI时钟输出 P端 output O_hdmi_clk_n, //HDMI时钟输出 N端 output [2:0] O_hdmi_tx_p, //HDMI数据输出 P端 output [2:0] O_hdmi_tx_n //HDMI数据输出 N端 );
assign O_card_power_en = 1'b1; //子卡上电
wire pclkx1,pclkx5,clk100M,locked; //MMCM/PLL时钟信号
//例化MMCM/PLL IP clk_wiz_1 clk_hdmi_pll_inst ( .clk_in1(I_sysclk), .reset(!rst_cnt[7]), .locked(locked), .clk_out1(pclkx1),//像素时钟 .clk_out2(pclkx5),//HDMI输出5倍像素时钟 .clk_out3(clk100M)//100M时钟,给ADC采集用 );
wire vtc_rstn,vtc_clk,vtc_vs,vtc_hs,vtc_de,vtc2_grid_de_o,vtc2_de_o; wire [23:0] rgb_o; //RGB颜色寄存器 assign vtc_clk = pclkx1;//像素时钟 assign vtc_rstn = locked;//VTC复位信号
//ad7606 ip相关信号 wire ad_clk_i,ad_rst_i,ad_cap_en; wire [63:0] ad_out_a,ad_out_b; assign ad_clk_i = clk100M; //ADC时钟 assign ad_rst_i = !locked; //ADC IP内部代码复位
uispi7606 uispi7606_inst ( .I_ad_clk(ad_clk_i), //系统时钟输入 .I_ad_rst(ad_rst_i), //系统复位输入 .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_rst), //ad7606 复位输出 .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组通道采集输入,V1,V2,V3,V4 .I_O_ad_out_b(I_O_ad_out_b), //串行B组通道采集输入, V5,V6,V7,V8 .O_ad_out_a(ad_out_a), //A组通道采集有效数据输出 .O_ad_out_b(ad_out_b), //B组通道采集有效数据输出 .O_ad_cap_en(ad_cap_en) //采集完成使能 );
wire [15:0] ad_ch1 = ad_out_a[63:48];//ADC 通道1 wire [15:0] ad_ch2 = ad_out_a[47:32];//ADC 通道2 wire [15:0] ad_ch3 = ad_out_a[31:16];//ADC 通道3 wire [15:0] ad_ch4 = ad_out_a[15: 0];//ADC 通道4 wire [15:0] ad_ch5 = ad_out_b[63:48];//ADC 通道5 wire [15:0] ad_ch6 = ad_out_b[47:32];//ADC 通道6 wire [15:0] ad_ch7 = ad_out_b[31:16];//ADC 通道7 wire [15:0] ad_ch8 = ad_out_b[15: 0];//ADC 通道8
//例化 ILA IP 用于在线逻辑分仪观察ADC采样值 ila_0 ila_debug ( .clk(ad_clk_i), //系统时钟 .probe0(ad_cap_en), //AD采集数据使能,ILA中使用capture功能,可以观察到更多有效数据 .probe1(ad_ch1),//ADC 通道1 .probe2(ad_ch2),//ADC 通道2 .probe3(ad_ch3),//ADC 通道3 .probe4(ad_ch4),//ADC 通道4 .probe5(ad_ch5),//ADC 通道5 .probe6(ad_ch6),//ADC 通道6 .probe7(ad_ch7),//ADC 通道7 .probe8(ad_ch8) //ADC 通道8 );
//上电延迟复位 reg [7:0] rst_cnt=0; //复位计数器 wire rstn = rst_cnt[7];//用高位复位
always @(posedge I_sysclk)begin if (rst_cnt[7]) rst_cnt <= rst_cnt; else rst_cnt <= rst_cnt+1'b1; End
//例化HDMI 输出IP uihdmitx # ( .FAMILY("7FAMILY") //选择芯片所支持的系列"7FAMILY" "UFAMILY" ) uihdmitx_inst ( .I_rstn(locked),//复位 .I_hs(vtc_hs),//hs信号 .I_vs(vtc_vs),//vs信号 .I_de(vtc_de),//de信号 .I_rgb({rgb_o}),//RGB数据 .I_pclkx1(pclkx1),//像素时钟 .I_pclkx2_5(1'b0),//2.5倍像素时钟,只有UFAMILY需要 .I_pclkx5(pclkx5),//5倍像素时钟 .O_hdmi_tx_clk_p(O_hdmi_clk_p),//HDMI时钟输出P端 .O_hdmi_tx_clk_n(O_hdmi_clk_n),//HDMI时钟输出N端 .O_hdmi_tx_p(O_hdmi_tx_p),//HDMI输出数据P端 .O_hdmi_tx_n(O_hdmi_tx_n)//HDMI输出数据N端 );
//此VTC IP 用于产生绘制波形的有效区域,波形绘制区域大小未1024*600 uivtc# ( .H_ActiveSize(1280), //视频时间参数,行视频信号,一行有效(需要显示的部分)像素所占的时钟数,一个时钟对应一个有效像素 .H_FrameSize(1280+88+44+239), //视频时间参数,行视频信号,一行视频信号总计占用的时钟数 .H_SyncStart(1280+88), //视频时间参数,行同步开始,即多少时钟数后开始产生行同步信号 .H_SyncEnd(1280+88+44), //视频时间参数,行同步结束,即多少时钟数后停止产生行同步信号,之后就是行有效数据部分 .V_ActiveSize(720), //视频时间参数,场视频信号,一帧图像所占用的有效(需要显示的部分)行数量,通常说的视频分辨率即H_ActiveSize*V_ActiveSize .V_FrameSize(720+4+5+28), //视频时间参数,场视频信号,一帧视频信号总计占用的行数量 .V_SyncStart(720+4), //视频时间参数,场同步开始,即多少行数后开始产生场同步信号 .V_SyncEnd (720+4+5), //视频时间参数,场同步结束,即多少场数后停止产生场同步信号,之后就是场有效数据部分 .H2_ActiveSize(1024), //波形绘制区域行像素大小 .V2_ActiveSize(256) //波形绘制区域场像素大小 ) uivtc_inst ( .I_vtc_clk(vtc_clk), //系统时钟 .I_vtc_rstn(vtc_rstn), //系统复位 .I_vtc2_offset_x(128), //X坐标相对屏幕的原始坐标的偏移 .I_vtc2_offset_y(200), //Y坐标相对屏幕的原始坐标的偏移 .O_vtc_vs(vtc_vs),//场同步输出 .O_vtc_hs(vtc_hs),//行同步输出 .O_vtc_de(vtc_de),//视频数据有效 .O_vtc2_de(vtc2_de_o)//绘制波形显示区域的有效区域 );
//测试数据产生,通过test_data产生测试数据,可以用于测试波形显示器的基本功能测试 //reg [7:0]test_data =0; //always @(posedge vtc_clk) // if(vtc2_de_o) // test_data[7:0] = test_data + 1'b1;
//例化波形显示器 IP,默认支持2个通道数据,可以扩展支持更多通道 uiwave uiwave_inst ( //波形1 .I_wave1_clk(ad_clk_i),//系统时钟输入 .I_wave1_data(ad_ch1[15:8]+8'h80),//ADC只显示高8bits 数据,其中8'h80是对有符号的ADC的计算偏移,把负数坐标转为对应的整数坐标 .I_wave1_data_de(ad_cap_en),//ADC数据有效信号
//波形2 .I_wave2_clk(ad_clk_i),//系统时钟输入 .I_wave2_data(ad_ch2[15:8]+8'h80),//ADC只显示高8bits 数据,其中8'h80是对有符号的ADC的计算偏移,把负数坐标转为对应的整数坐标 .I_wave2_data_de(ad_cap_en),//ADC数据有效信号 .I_vtc_rstn(vtc_rstn),//时序发生复位 .I_vtc_clk (vtc_clk), //像素时钟 .I_vtc_vs (vtc_vs), //场同步输出 .I_vtc_de (vtc2_de_o),//同步,绘制波形显示区域的有效区域 .O_vtc_rgb(rgb_o)//同步RGB数据 绘制数据输出
);
endmodule |
6 测试结果
6.1 硬件接线
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
我们使用米联客3V3的FEP-DAQ7606子卡。请确保下载器和开发板已经正确连接,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)
6.2 测试结果
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/17931268.html