本帖最后由 正点原子运营 于 2021-1-21 11:41 编辑
1)实验平台:正点原子超越者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=631660290421
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
4)本章实例源码下载:
24_hs_dual_ad.rar
(487.26 KB, 下载次数: 17)
5)正点原子FPGA交流群:905624739点击加入:![]()
6)关注正点原子公众号,获取最新资料更新
第三十一章双路高速AD实验
ADC(Analog to Digital Converter即模数转换器)是大多数系统中必不可少的组成部件,用于将连续的模拟信号转换成离散的数字信号,它们是连接模电电路和数字电路必不可少的桥梁。在很多场合下,ADC的转换速度甚至直接决定了整个系统的运行速度。本章我们将使用双路高速AD模块采集外部模拟信号转换成数字信号,并在Chipscope中查看信号波形。
本章包括以下几个部分:
3131.1 简介
31.2 实验任务
31.3 硬件设计
31.4 程序设计
31.5 下载验证
31.1简介
本章我们使用的高速双路AD模块是正点原子推出的一款高速双路模数转换模块(ATK_DUAL_HS_AD),高速AD转换芯片由恩瑞浦公司生产的,型号是3PA1030。
ATK_DUAL_HS_AD模块的硬件结构图如下图所示。
图 31.1.1 ATK_DUAL_HS_AD模块硬件结构图
3PA1030芯片的输入模拟电压转换范围是0V~2V,所以电压输入端需要先经过电压衰减电路,使输入的-5V~+5V之间的电压衰减到0V~2V之间,然后经过3PA1030芯片将模拟电压信号转换成数字信号。
下面我们介绍下这个芯片。
3PA1030芯片
3PA1030是一款恩瑞浦推出的单电压芯片,10位,50 MSPS(Million Samples Per Second,每秒采样百万次)模数转换器,集成片上采样保持放大器和基准电压源。具有高性能低功耗的特点。
3PA1030的内部功能框图如下图所示:
图 31.1.2 3PA1030内部功能框图
3PA1030在时钟(CLK)的驱动下工作,3PA1030内置片内采样保持放大器(SHA),同时采用多级差分流水线架构,保证了50MSPS的数据转换速率下全温度范围内无失码;3PA1030内部集成了基准源,根据系统需要也可以选择外部高精度基准满足系统的要求。
3PA1030输出的数据以二进制格式表示,当输入的模拟电压超出量程时,会拉高OVR信号;当输入的模拟电压在量程范围内时,OVR信号为低电平,因此可以通过OVR信号来判断输入的模拟电压是否在测量范围内。另外3PA1030有一个OE信号,当该信号为高电平时3PA1030输出呈高阻态,低电平则可以正常输出。
3PA1030的时序图如下图所示:
图 31.1.3 3PA1030时序图
上图中,S1,S2,S3分别为三个采样点,可以看到,芯片在时钟的上升沿采样。需要注意的是,3PA1030芯片的最大转换速度是50MSPS,即输入的时钟最大频率为50MHz。
3PA1030支持输入的模拟电压范围是0V至2V,0V对应输出的数字信号为0,2V对应输出的数字信号为1023。而DA经外部电路后,输出的电压范围是-5V~+5V,因此在3PA1030的模拟输入端增加电压衰减电路,使-5V~+5V之间的电压转换成0V至2V之间。那么实际上对我们用户使用来说,当3PA1030的模拟输入接口连接-5V电压时,AD输出的数据为0;当3PA1030的模拟输入接口连接+5V电压时,AD输出的数据为1023。
当3PA1030模拟输入端接-5V至+5V之间变化的正弦波电压信号时,其转换后的数据也是成正弦波波形变化,转换波形如下图所示:
图 31.1.4 3PA1030正弦波模拟电压值(左)、数据(右)
由上图可知,输入的模拟电压范围在-5V至5V之间,按照正弦波波形变化,最终得到的数据也是按照正弦波波形变化。
31.2实验任务
本节实验任务是使用超越者开发板及双路AD扩展模块(ATK_DUAL_HS_AD模块)实现双路模数的转换,并在ILA中查看波形。本实验我们模拟输入源来自信号发生器,一个是正弦波,频率1Mhz,幅值9V;另一个是三角波,频率1Mhz,幅值5V。两路模拟信号分别接在双路AD模块的模拟信输入端。
31.3硬件设计
ATK_DUAL_HS_AD模块电路主要包括扩展口,AD芯片,电源电路模块和低通滤波器,衰减电路。下面是扩展口电源电路部分。
图 31.3.1 扩展口及电源电路
由上图可知,双路AD模块使用到的管脚连接道路JP1上,这些管脚包括十位的数据,时钟以及电源等信号。U2用于将5V电压转成VCC+(2.8V)供U1使用,U1将VCC+转成了VCC-(-2.8V),±2.8V电压供双电源运放TPH2501使用。U3负责将5V电压转成VCC(3.3V)。
衰减电路原理图如下图所示。
图 31.3.2 3PA1030原理图
上下两个电路是一样的,我们以上面的电路为例。上图中输入的模拟信号IN1(V1)经过衰减电路后得到AD_IN1(VO)信号,两个模拟电压信号之间的关系是VO=VI/5+1,即当VI=5V时,VO=2V;VI=-5V时,VO=0V。
ATK_DUAL_HS_AD模块的实物图如下图所示。
图 31.3.3 ATK_DUAL_HS_AD模块实物图
本实验中,各端口信号的管脚分配如下表所示。
表 31.3.1双路高速AD转换实验管脚分配
对应的UCF约束语句如下所示:
- <font size="4">NET sys_clk TNM_NET = sys_clk_pin;</font>
- <font size="4">TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 50000 kHz;</font>
- <font size="4">NET sys_clk LOC = N8 | IOSTANDARD = "LVCMOS33";</font>
- <font size="4">NET sys_rst_n LOC = G16 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_clk LOC = C9 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_clk LOC = E15 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_oe LOC = A9 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_oe LOC = D14 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_otr LOC = B8 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_otr LOC = C15 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<0> LOC = C5 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<1> LOC = A4 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<2> LOC = C4 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<3> LOC = A5 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<4> LOC = B5 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<5> LOC = A6 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<6> LOC = B6 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<7> LOC = A7 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<8> LOC = C7 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad0_data<9> LOC = A8 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<0> LOC = B10 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<1> LOC = A11 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<2> LOC = B15 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<3> LOC = B16 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<4> LOC = C16 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<5> LOC = D16 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<6> LOC = E16 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<7> LOC = F16 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<8> LOC = T10 | IOSTANDARD = "LVCMOS33"; </font>
- <font size="4">NET ad1_data<9> LOC = N12 | IOSTANDARD = "LVCMOS33";</font>
复制代码
31.4程序设计
根据本章的实验任务,高速双路AD模块同时采集两路外部模拟信号,在模块内部实现模数转换,将转换后的数字信号传给FPGA管脚,FPGA内部逻辑分析仪通过抓取数据将外部的模拟信号呈现出来。
图 31.4.1是根据本章实验任务画出的系统框图。我们事先准备两路模拟信号源,本实验我们使用信号发生器产生模拟输入源。接到双路AD芯片的信号输入端,在双路AD内部实现AD转换,将转换后的信号送给FPGA,在这里,FPGA只需要给AD芯片输出驱动时钟信号(AD_CLK)和使能信号(AD_OE),AD芯片便可完成模拟采集并转换成数字信号。
高速双路AD实验的系统框图如图 31.4.1所示:
图 31.4.1 双路AD系统框图
通过对系统框图的分析,我们可以将代码分成两个模块,一个是PLL模块用来生成对应的时钟去驱动AD外设模块,一个是顶层模块用来例化PLL IP核。所以这么一分析我们的代码结构还是非常简单的。我们给出顶层模块代码如下所示:
- <font size="4">1 module hs_dual_ad(</font>
- <font size="4">2 input sys_clk , //系统时钟</font>
- <font size="4">3 input sys_rst_n , </font>
- <font size="4">4 //AD0</font>
- <font size="4">5 input [9:0] ad0_data , //AD0数据</font>
- <font size="4">6 input ad0_otr , //输入电压超过量程标志</font>
- <font size="4">7 output ad0_clk , //AD0采样时钟</font>
- <font size="4">8 output ad0_oe , //AD0输出使能</font>
- <font size="4">9 //AD1</font>
- <font size="4">10 input [9:0] ad1_data , //AD1数据</font>
- <font size="4">11 input ad1_otr , //输入电压超过量程标志</font>
- <font size="4">12 output ad1_clk , //AD1采样时钟 </font>
- <font size="4">13 output ad1_oe , //AD1输出使能 </font>
- <font size="4">14 //打拍输出,用来防止输入端口被ISE优化,方便Chipscope抓取信号。 </font>
- <font size="4">15 output reg [9:0] ad0_data_n,</font>
- <font size="4">16 output reg [9:0] ad1_data_n,</font>
- <font size="4">17 output reg ad0_otr_n ,</font>
- <font size="4">18 output reg ad1_otr_n </font>
- <font size="4">19 </font>
- <font size="4">20 );</font>
- <font size="4">21 </font>
- <font size="4">22 //wire define</font>
- <font size="4">23 wire clk_out1;</font>
- <font size="4">24 wire clk_out2;</font>
- <font size="4">25 wire clk_buff; </font>
- <font size="4">26 </font>
- <font size="4">27 //*****************************************************</font>
- <font size="4">28 //** main code</font>
- <font size="4">29 //***************************************************** </font>
- <font size="4">30 assign ad0_oe = 1'b0;</font>
- <font size="4">31 assign ad1_oe = 1'b0;</font>
- <font size="4">32 //打拍输出,用来防止输入端口被ISE优化,方便Chipscope抓取信号。</font>
- <font size="4">33 always@(posedge CLK_OUT1 or negedge sys_rst_n)begin</font>
- <font size="4">34 if(!sys_rst_n)begin</font>
- <font size="4">35 ad0_data_n<=10'b0;</font>
- <font size="4">36 ad1_data_n<=10'b0;</font>
- <font size="4">37 ad0_otr_n <=1'b0;</font>
- <font size="4">38 ad1_otr_n <=1'b0; </font>
- <font size="4">39 end</font>
- <font size="4">40 else begin</font>
- <font size="4">41 ad0_data_n<=ad0_data ;</font>
- <font size="4">42 ad1_data_n<=ad1_data ;</font>
- <font size="4">43 ad0_otr_n <=ad0_otr ;</font>
- <font size="4">44 ad1_otr_n <=ad1_otr ;</font>
- <font size="4">45 end</font>
- <font size="4">46 end</font>
- <font size="4">47 </font>
- <font size="4">48 pll u_pll</font>
- <font size="4">49 (// Clock in ports</font>
- <font size="4">50 .CLK_IN1(clk_buff), // IN</font>
- <font size="4">51 // Clock out ports</font>
- <font size="4">52 .CLK_OUT1(CLK_OUT1), // OUT</font>
- <font size="4">53 .CLK_OUT2(CLK_OUT2)</font>
- <font size="4">54 ); // OUT </font>
- <font size="4">55 </font>
- <font size="4">56 ODDR2 #(</font>
- <font size="4">57 .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1" </font>
- <font size="4">58 .INIT(1'b0), // Sets initial state of the Q output to 1'b0 or 1'b1</font>
- <font size="4">59 .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset</font>
- <font size="4">60 ) ODDR2_inst_1 (</font>
- <font size="4">61 .Q(ad0_clk), // 1-bit DDR output data</font>
- <font size="4">62 .C0(CLK_OUT1), // 1-bit clock input</font>
- <font size="4">63 .C1(~CLK_OUT1), // 1-bit clock input</font>
- <font size="4">64 .CE(1'b1), // 1-bit clock enable input</font>
- <font size="4">65 .D0(1'b1), // 1-bit data input (associated with C0)</font>
- <font size="4">66 .D1(1'b0), // 1-bit data input (associated with C1)</font>
- <font size="4">67 .R(1'b0), // 1-bit reset input</font>
- <font size="4">68 .S(1'b0) // 1-bit set input</font>
- <font size="4">69 ); </font>
- <font size="4">70 </font>
- <font size="4">71 ODDR2 #(</font>
- <font size="4">72 .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1" </font>
- <font size="4">73 .INIT(1'b0), // Sets initial state of the Q output to 1'b0 or 1'b1</font>
- <font size="4">74 .SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset</font>
- <font size="4">75 ) ODDR2_inst_2 (</font>
- <font size="4">76 .Q(ad1_clk), // 1-bit DDR output data</font>
- <font size="4">77 .C0(CLK_OUT1), // 1-bit clock input</font>
- <font size="4">78 .C1(~CLK_OUT1), // 1-bit clock input</font>
- <font size="4">79 .CE(1'b1), // 1-bit clock enable input</font>
- <font size="4">80 .D0(1'b1), // 1-bit data input (associated with C0)</font>
- <font size="4">81 .D1(1'b0), // 1-bit data input (associated with C1)</font>
- <font size="4">82 .R(1'b0), // 1-bit reset input</font>
- <font size="4">83 .S(1'b0) // 1-bit set input</font>
- <font size="4">84 ); </font>
- <font size="4">85 </font>
- <font size="4">86 IBUFG #(</font>
- <font size="4">87 .IOSTANDARD("DEFAULT")</font>
- <font size="4">88 ) IBUFG_inst (</font>
- <font size="4">89 .O(clk_buff), // Clock buffer output</font>
- <font size="4">90 .I(sys_clk) // Clock buffer input (connect directly to top-level port)</font>
- <font size="4">91 ); </font>
- <font size="4">92 </font>
- <font size="4">93 endmodule</font>
复制代码
代码的第14~18行和第32~46行是冗余逻辑,本身对功能不起任何作用,之所以要写这些冗余逻辑是为了防止输入端口被ISE优化掉,导致Chipscope中抓不到信号。写了这些冗余逻辑,就将原本的输入信号打拍又输出出去,这样系统就不会认为输入端口是多余逻辑(在ISE中单纯的输入端口没有进行任何逻辑运算的,会被认为是多余逻辑,编译的时候ISE会把这些多余逻辑优化掉)。
代码的第48~54行例化了一个PLL IP核,产生了两个时钟信号,其中CLK_OUT1用来给AD外设模块提供驱动时钟,CLK_OUT2用来做采样时钟,这两个时钟频率都是50MHZ相位相差了180度,之所以用一个相位差180度的时钟作为采样时钟是为了让采样时刻落在数据稳定时间的正中间,确保采样更加准确。我们需要采样的数据是ad0_data和ad1_data,这连个数据就是AD外设将模拟信号转换成数字信号产生的数据,因为是双路AD所以有两路数据。
代码中还用了ODDR2和INBUFG原语,其中INBUFG原语是为了将晶振时钟挂到FPGA的时钟网络上做一个缓冲,ODDR2是为了将时钟通过IO输出做一个缓冲,具体的IC内部结构详情请参考Xilinx官方文档UG381和UG382。
代码写好后就可以创建一个Chipscope IP核了(不会创建Chipscope IP的请参考软件使用篇),因为我们写了冗余逻辑,所以输入信号不会被优化,我们把ad0_data和ad1_data信号(这两个信号在Chipscope中会带IBUF后缀)抓取出来观察他们的波形,注意这里要在Bus Plot窗口看波形,如果波形错乱不妨点击一下Reverse Bus Order按钮调整一下高低位(将10bit的单个数据线合并成一个总线,右击总线就会出现这个按钮)。
31.5下载验证
将AD模块插入超越者开发板J1扩展口(特别提醒:我们的开发板有J1和J2两个拓展口,因为拓展口和其他外设引脚有复用,其中J1拓展口主要和LCD显示屏复用,J2拓展口主要和数码管复用,这就导致AD外设模块将数据传入板子后并不是直接传到FPGA中去的,会受其他负载干扰,比如数码管或者LCD显示屏和AD模块同时工作就会导致AD传入的数据有严重的噪声干扰,此时应该去掉复用负载。其中数码管是固定负载无法去除,所以这里推荐使用J1拓展口,使用AD模块的时候不接LCD显示屏就行。DA模块则没这个问题,DA模块是FPGA将数据传给外设DA模块,即使有干扰DA芯片也会将干扰过滤掉,所以DA模块接J1、J2拓展口都行。如果大家没有信号发生器,完全可以把DA模块放在J2拓展口当信号发生器使用,将DA的信号接给AD模块使用,这样就可以在一块板子上同时接AD和DA两个外设了),连接时注意扩展口电源引脚方向和开发板电源引脚方向一致,然后将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。
超越者开发板硬件连接实物图如下图所示:
图 31.5.1 超越者开发板硬件连接实物图
打开Chipscope工具,将生成好的CDC文件加载到FPGA中后,然后打开波形发生器(如果没有波形发生器就用DA模块代替)将正弦波输入到AD外设的数据接口,此时运行Chipscope可以看到正弦波的波形,如下图所示:
图 31.5.2 Chipscope采集波形
|