LabVIEW FPGA PCIe开发讲解-实战篇:实验61:PCIe DMA+8位ADC(模拟数据采集卡)

1、实验内容
现在很多电脑PC或者工控机主板上面都集成了PCIe插座,可以直接插入PCIe板卡,优点是卡槽标准,插拔简单,传输速度极快。对于高速采集测试测量领域,PCIe用途非常广泛,最大极限带宽可以到6.6GB/s,这个速度可以直接用来做高速示波器卡、数字化仪、RF射频板卡和视频采集卡了。

本节实验我们准备采用黑金提供的AN108模块(AD9280),上面有一颗8位高速ADC芯片,结合PCIe总线实现一个采样率(最大32MS/s)可调节的PCIe数据采集卡,直接插到PC或者工控机或者工业树莓派上使用。

下面我们先来回顾一下PCIe接口和AN108硬件模块等相关内容。

黑金设计的ARTIX7-100T FPGA开发板(AX7103)上已经把PCIe接口引出来了,做成了X4接口模式,如图61-1所示。 在这里插入图片描述
图61-1:带PCIe X4接口的Aritx7 FPGA开发板(黑金AX7103)

用户可以直接将这个开发板插到工控机或者电脑主板上的PCIe母座里面,由于PCIe不支持热插拔,所以用户接入的时候,一定把机箱电源关掉,防止烧坏板卡;当然,如果用户闲PCIe插拔比较麻烦,还可以到淘宝上买一根PCIe延长线,将机箱里面的PCIe母座引出来,方便做实验。

由于我们已经把复杂的PCIe DMA通信协议封装成了LabVIEW FPGA下的Socket CLIP,用户只需要根据四线握手协议来调用对应的CLIP端口就可以了。 这部分我们在前面几节里面已经介绍过了。

本节PCIe DMA通信实验重点向大家讲解以下8个方面的内容:

  1. PCIe DMA通信下位机(FPGA)应用程序开发。
  2. PCIe 硬件驱动加载与识别(INF文件)。
  3. PCIe DMA通信上位机(PC)应用程序开发。
  4. FPGA内部多线程之间的数据交互与数据缓冲:FIFO。
  5. FPGA内部不同速率之间的匹配机制:四线握手。
  6. PCIe DMA通信IP核全双工通信的读写机制。
  7. PCIe DMA通信数据格式和类型转换(大小端与字节数组)(下行和上行都是小端)。
  8. 8位ADC芯片的数据读取方法(参考本书前面的“实验14:8位ADC数据采集实验-AD9280”)

关于PCIe DMA下位机(FPGA)通信应用程序开发,指的是FPGA芯片中的代码编写。譬如,本节实验我们准备开发一个基于PCIe 传输的高速采集卡,将ADC采集到的数据通过PCIe DMA实时发送到PC上,这段代码我们可以利用图形化的LabVIEW编程语言来开发,替代传统的VHDL/Verilog语言,这里我们称之为下位机LabVIEW FPGA编程。本节实验下位机FPGA程序里面的用户线程,我们会利用AD9280芯片(8位)采集外部真实的Sine正弦信号,然后发送到上位机进行显示或者流盘。

关于PCIe 硬件驱动加载与识别,可以参考前面7.3.2节里面的内容,不熟悉的用户一定要看一下,因为这个起到承上启下的作用。

关于PCIe DMA通信上位机(PC端)应用程序开发,指的是运行在windows或者linux系统中的LabVIEW上位机程序,用户可以通过LabVIEW调用我们封装好的FPGA PCIe lvlib库里面的多态VI与下位机FPGA进行PCIe DMA通信交互。我们把上位机收发线程分成两个独立的while循环来处理,这样调试起来方便一些。当然,如果有些用户不会LabVIEW,也可以使用C\C++\C#\Python来调用我们封装好的DLL驱动进行上位机程序开发。

关于FIFO的概念和操作,可以参考前面的“实验4-串口通信”,里面有详细的介绍和演示过程。

关于四线握手制的工作过程和原理,也可以参考前面的“实验4-串口通信”,这里不再赘述。

关于PCIe DMA通信IP核全双工通信机制,我们会通过两个独立的收发线程来演示。发送和接收可以同时并行执行,而且是8个上行和8个下行同时全双工工作。

最后一个需要注意的问题是,PCIe DMA下位机跟上位机之间的通信数据格式和数据类型,需要匹配上才能正确解析,因为Xillybus底层IP核走的是小端格式,而上位机LabVIEW里面默认是大端格式,因此,在前面7.4节里面,强调了FPGA里面数据格式转换的重要性;另外,模拟或者采集的AD波形,对应的数据类型跟PCIe内部的字节数组之间需要通过“强制类型转换”函数转换一下才能看到正确的波形曲线。

下面带着大家一起,利用LabVIEW编写符合标准四线握手制的PCIe DMA总线收发FPGA程序,来实现一个基于FPGA的PCIe 8位数据采集卡(最大采样率32MS/s,采集电压范围±5V),采集外部真实的Sine正弦信号,通过PCIe总线发送给上位机PC,同时还能接收上位机PC下发的指令和参数对FPGA板卡进行采样率和使能采集等控制。

2、硬件资源

2.1、先回顾一下PCIe总线接口方面的硬件知识

黑金设计的带PCIe接口的ARTIX7开发板,内部走线连接示意图如图61-2所示。可以看出,PCIe时钟走线是经过AC耦合的。一共使用了4对差分收发总线,所以最大位宽支持X4模式。 在这里插入图片描述
图61-2:Aritx7 FPGA开发板内部的PCIe走线连接示意图

打开本书配套的光盘/云盘里面1号文件夹里面的AX7103底板原理图,然后找到PCIe接口这部分原理图,如图61-3所示。 在这里插入图片描述
图61-3:打开PCIe底板原理图,找到PCIe总线互联的3大类引脚

这些引脚其实可以分成3类:PCIe总线复位引脚;PCIe总线差分时钟输入引脚;PCIe总线不同位宽的差分数据引脚。这3类引脚在前面图7-52里面都定义过了,如果用户自己画的板子或者网上买的其他家的板子引脚定义不一样,那么照葫芦画瓢对应修改一下就可以了。

然后,打开插在AX7103开发板上的核心板AC7100原理图,找到PCIe复位引脚,就是J20,如图61-4所示。接着需要寻找一下PCIe的差分时钟引脚,这个引脚非常重要,如果找不到或者设置不对,那么PCIe总线肯定初始化不了;在核心板原理图上可以看到有两个MGT_CLK信号,但是实际接到PCIe插槽的是MGT_CLK1,也就是F10,如图61-5所示。 在这里插入图片描述
图61-4:在AC7100核心板上找到PCIe复位引脚:J20 在这里插入图片描述
图61-5:在核心板AC7100原理图上找到PCIe差分时钟输入引脚:F10

但是细心的用户可能发现了,黑金的AC7100核心板在画板子布局布线的时候,将PCIe X4模式下的数据收发差分对引脚0和1弄反了,2和3是对的,如图61-6所示。这个细节导致无数用户付出了惨痛代价。很多用传统Verilog开发FPGA的用户,看原理图不仔细,或者说黑金太随意了,为了自己的布局方便,把0跟1故意调换了一下,那么在Vivado里面默认的PCIe引脚就不对了,必须要人为调整才行。 在这里插入图片描述
图61-6:仔细检查一下PCIe X4或者X8数据差分对顺序是否正确

为了方便我们广大LabVIEW FPGA用户开发,我们特地在顶层xdc里面将PCIe X4全部引脚定义拉出来,用户只要自己根据实际情况修改就行,而无需在网表里面定义,简化了编程的繁琐,也避免了一些不必要的错误。希望这一点能够引起开发者的注意!!!重要的事情说三遍,0跟1互换了,所以引脚定义也要变,如图61-7所示。 在这里插入图片描述
图61-7:PCIe数据差分对引脚要跟原理图保持一致

2.2、再回顾一下8位高速ADC芯片AD9280相关的硬件内容

本节实验需要用到的ADC采集模块是黑金设计的AN108高速AD/DA模块,这款模块上面同时集成了AD和DA芯片,可以直接插到AX7103开发板的扩展口上面,简单易用。

本节实验我们开发的是PCIe采集卡,所以会用到AN108模块上的AD芯片9280,另外一块DA芯片我们在后续的PCIe函数信号发生器板卡实验里面会用到。

AD9280芯片位于AN108的左下角,如图61-8所示。精度不高,只有8位,但是采样率很高,最高可以达到32MS/s。 在这里插入图片描述
图61-8:AN108模块上的AD9280芯片实物图

AD9280这款芯片的内部结构图,如图61-9所示。可以看出,芯片内部有个1V的基准参考电压,所以这款芯片的输入范围是02V。对用户来说,只要给定一个CLK采样时钟信号,然后直接读取Data0Data7引脚上的数据就可以了。 在这里插入图片描述
在这里插入图片描述
图61-9:AD9280芯片内部结构图

打开本书配套的光盘/云盘1号文件夹里面的AN108模块原理图,找到AD9280芯片这部分接线原理图,如图61-10所示。 在这里插入图片描述
图61-10:AD9280电路原理图

AD9280芯片上的AIN输入范围比较小,只有0~2V,很难满足实际应用,所以我们还需要在信号进入AD芯片之前,加上一片AD8065芯片构建前端衰减电路,如图61-11所示。 在这里插入图片描述
图61-11:AD8065衰减电路原理图

AD8065芯片可以将AN108模块BNC接口的实际输入电压范围-5V-+5V(10Vpp)衰减后变成满足 AD9280芯片所能接受的输入电压范围(0~2V)。二者之间的转换公式如下。 在这里插入图片描述
 当AN108模块BNC端的输入电压Vin=5(V)的时候,衰减后到AD的信号Vad=2(V)。
 当AN108模块BNC端的输入电压Vin=-5(V)的时候,衰减后到AD的信号Vad=0(V)。

黑金在设计AN108这个模块的时候,1脚和2脚分别表示GND和VCC,所以在把这个模块插入到AX7103开发板右上角的40针扩展口(J11)上,模块的Pin1脚和开发板扩展口的 Pin1 脚要对齐,如图61-12所示。 在这里插入图片描述
图61-12:AN108模块的1脚和2脚必须与主板一致

通过查看AN108模块与AX7103扩展口之间的线序关系,可以发现AD9280的8位数据引脚和转换时钟CLK引脚分别占用的是扩展口J11的21~29号引脚,如图61-13所示。 在这里插入图片描述
图61-13:AN108模块上的AD芯片9280占用的扩展口引脚序号
然后在AX7103主板原理图里面找到扩展口J11上面的这些引脚序号,如图61-14所示。通过与FPGA芯片引脚逐一对比可以找到二者之间的映射关系,如图61-15所示。 在这里插入图片描述
图61-14:FPGA开发板上的扩展口引脚序号图 在这里插入图片描述
图61-15:AD9280芯片与FPGA引脚映射关系

3、FPGA PCIe DMA下位机ADC采集程序开发(AD9280)

下面,我们带着用户一起利用LabVIEW开发一个FPGA PCIe DMA下位机FPGA数据采集应用程序,这个程序可以下载到FPGA芯片里面运行,为了更加全面形象的展示PCIe总线通信的魅力,我们模拟一个PCIe高速数据采集卡,将8位AD芯片采集到的Sine正弦信号通过64位位宽的PCIe DMA上行通道ch0发送到上位机进行显示,上位机通过8位位宽的下行通道ch4下发不同的指令参数来控制FPGA开始采集、停止采集和调整采集频率。以此向大家展示FPGA与PC之间通过PCIe总线进行双向高速通信的过程。

提醒:之所以使用64位位宽的ch0作为ADC数据上行通道,而没有选择位宽刚好是8位的ch4/5/6/7,是因为我们提供的B版本PCIE Data CLIP模板里面,ch4/5/6/7通道的吞吐率比较低,只有几MB/s,而AD9280芯片的最大采样率可以到32MS/s,换算成字节为单位,也就是32MB/s;当然我们可以在Xillybus官网上重新修改每个通道的带宽,但是需要重新编译。为此,这里我们给用户演示另外一种实现方式,就是并转串,将ADC每个采样周期读取的8位数据(U8)按照小端格式(因为PCIe CLIP通道默认是小端格式)拼接成64位(U64),再经过ch0发送到上位机PC,因为ch0的带宽是720MB/s,可以完全胜任AD9280的极限采样率。

3.1、LabVIEW FPGA项目创建
1)启动LabVIEW 2015 SP1软件,用户可以点击左上角的“文件>>新建”或者直接点击“新建”下方的“项目”选项。这里我们也可以直接打开前面实验里面已经创建好的“My_FPGA_Starter_Board_Artix7_AX7103.lvproj”这个项目,如图61-16所示。 在这里插入图片描述
图61-16:打开前面创建过的LabVIEW项目

由于本章我们引入了PCIe总线通信,之前创建的FPGA终端里面没有添加PCIe Socket CLIP,所以,我们需要先右击“我的电脑”选择新建“终端和设备”,如图61-17所示;然后再在弹出来的FPGA终端选择列表里面,选择一个带PCIe X4接口8通道的ARTIX7-100T FPGA终端设备,如图61-18所示。其中,B版本的PCIe总线带宽要比A版本的高一倍,也就是800MB/s,本书我们选择B版本进行测试,A版本感兴趣的用户可以自行测试。点击“确定”按钮后,创建好的ARTIX7 PCIe FPGA终端设备如图61-19所示。

提醒:新建含有FPGA PCIe Socket CLIP的过程,在前面实验54里面已经创建过了,所以这里可以忽略,如果是用户自己新建的项目,那么参考上面的步骤走一遍就可以了。 在这里插入图片描述
图61-17:右击我的电脑选择新建“终端和设备” 在这里插入图片描述
图61-18:选择一个PCIe X4 8通道的B版本ARTIX7-100T FPGA终端
在这里插入图片描述
图61-19:创建成功后的ARTIX7 FPGA PCIe终端设备

2)右击FPGA终端,选择“新建>>虚拟文件夹”,如图61-20所示。将添加到FPGA终端里面的虚拟文件夹,重命名为“实验61-PCIe DMA+8位ADC(模拟数据采集卡)”,如图61-21所示。 在这里插入图片描述
图61-20:右击FPGA终端,新建一个虚拟文件夹 在这里插入图片描述
图61-21:将虚拟文件夹重命名为“实验61-PCIe DMA+8位ADC(模拟数据采集卡)”

3.2、LabVIEW FPGA EIO节点与FIFO创建

1) 新建按键KEY和LED灯的EIO节点

(1)由于本节所有的PCIe通信实验里面都会用到LED指示灯和按键KEY等EIO资源,所以,我们可以在FPGA终端上面右击,选择“新建>>FPGA I/O”,如图61-22所示。然后在弹出来的I/O资源选择对话框里面,找到AX7103 FPGA开发板上的LED和KEY对应的管脚资源,如图61-23所示。单击对话框中间的“向右箭头”按钮,将这些引脚对应的EIO节点添加到右侧的FPGA资源列表里面,如图61-24所示。

提醒:实际上LED灯和按键KEY的EIO节点在前面实验54里面已经创建过了,所以这里可以忽略,如果是用户自己新建的项目,那么参考上面的步骤做一遍就可以了。 在这里插入图片描述
图61-22:新建FPGA I/O EIO节点 在这里插入图片描述
图61-23:找到AX7103开发板上LED指示灯和KEY按键对应的EIO节点 在这里插入图片描述
图61-24:将LED和KEY EIO节点全部添加到右侧的可用列表中

点击“确定”按钮后,这些EIO节点会被添加到FPGA终端项目里面,如图61-25所示。 在这里插入图片描述
图61-25:LED灯和KEY按键的EIO节点被添加到FPGA终端项目里面
(在前面做实验54的时候已经添加过了)

2) 新建AD9280芯片对应的EIO节点

(1)右击“FPGA终端 3”,选择“新建>>FPGA I/O”,如图61-26所示。然后在弹出来的I/O资源选择对话框里面,找到AN108模块上的AD9280芯片对应到AX7103 FPGA开发板J11扩展口关联的管脚资源(10P~14P),如图61-27所示。单击对话框中间的“向右箭头”按钮,将这些引脚资源添加到右侧的FPGA资源列表里面,如图61-28所示。因为,Vivado编译器不支持同一个物理IO在xdc约束文件里面有多处定义,所以这里我们直接使用原始的扩展口IO名称。在这里插入图片描述
图61-26:新建FPGA I/O EIO节点 在这里插入图片描述
图61-27:找到AD9280对应FPGA(XC7A100T)芯片上的引脚EIO节点 在这里插入图片描述
图61-28:将AD9280芯片的EIO节点添加到右侧的可用列表中

点击“确定”按钮后,这些EIO节点被添加到FPGA终端项目里面,如图61-29所示。 在这里插入图片描述
图61-29:AD9280芯片EIO节点被添加到FPGA终端项目里面

3) 新建FIFO

我们可以将PCIE通信IP核看成一个独立的功能模块,类似MCU里面的子函数或者子VI,FPGA程序只需要关心如何将不同类型的数据(U64、U32、U16、U8)通过PCIe总线发送出去以及收到的数据怎么反馈给FPGA用户程序,这个中间的桥梁,可以采用FIFO缓冲区来实现。FIFO非常适合在不同的线程之间传递数据,而且设计合理的话,数据不会丢失或者被覆盖。

注意:FPGA用户线程相当于生产者,PCIe DMA发送线程相当于消费者,所以,二者之间有个速率匹配的问题,生产者发送数据的速度不能快于消费者。比如,本节实验里面,我们使用的8位AD9280芯片最大采样率是32MS/s,需要的通道带宽至少大于32MB/s的上行通道(默认情况下,能满足这个指标的只有ch0和ch1)才能把下位机FPGA采集的数据无损的传输给上位机PC,否则会造成数据丢失。后面我们会根据具体程序进一步分析二者之间的速度匹配问题。

(1)右击FPGA终端项目,选择新建一个子的“虚拟文件夹”,如图61-30所示。然后重命名为“FIFO_ADC”,这个虚拟文件夹专门用来存放接下来我们创建的各种ADC芯片的FIFO缓冲区资源,如图61-31所示。 在这里插入图片描述
图61-30:新建一个子虚拟文件夹 在这里插入图片描述
图61-31:将虚拟文件夹重命名为“FIFO_ADC”

(2)右击子虚拟文件夹“FIFO_ADC”,选择“新建>>FIFO”,如图61-32所示。

提醒:考虑到后续我们需要将8个8位的AD9280数据(U8)通过并转串拼接成64位的U64,所以这里需要创建8个U8类型的AD9280临时缓冲区FIFO,分别命名为:FIFO_AD9280_U8_1……FIFO_AD9280_ U8_8。 在这里插入图片描述
图61-32:新建PCIe FIFO资源

(3)接着会自动弹出FIFO属性设置对话框,如图61-33所示。下面我们逐项解释一下每个选项的含义。

首先是“名称”,这里可以随便输入一个能够体现这个FIFO功能的名称,比如我们要把FPGA从AD9280芯片里面采集到的无符号8位数据(U8)存放到这个FIFO,那么可以重命名为“FIFO_AD9280_U8_1”。

其次是“类型”,这个下拉列表里面默认只有一个选项“终端范围”,意思就是我们创建的这个FIFO只能在这个FPGA芯片内部使用,不能用于其他终端,如果用户使用的是NI的RT+FPGA终端设备,那么这个地方会有更多的选择,这里不再赘述。

接着是“请求元素数量”,默认是1023,这个数值就是FIFO的深度,最大可以缓冲的数据长度,数值越大,编译消耗的FPGA资源就越多。很多客户以为这个数值设置越大越好,这样就不容易溢出了,实则不然,如果多线程之间的FIFO读写速度差别很大的话,再大的FIFO对于FPGA这种高速运行的器件来说,也是瞬间就会溢出或者覆盖丢点的。所以问题的关键不在于FIFO深度,而是要设计一个合理的读写匹配机制。这里我们可以将FIFO深度设置为128. 在这里插入图片描述
图61-33:FIFO属性对话框:名称、类型与深度

然后是“实现”方式,里面有3个选项“触发器”、“查找表”和“存储器块”,如图61-34所示。前两个就是FPGA最为宝贵的逻辑门资源了,一般不轻易使用,除非是用户已经将FPGA内部的存储器全部用完了,否则优先使用内部存储器块,这个存储器不占用FPGA内部的逻辑资源,不用也是浪费。 在这里插入图片描述
图61-34:FIFO属性对话框:实现方式

最后是“控制逻辑”,这个特别需要注意,默认选择的是“终端优化”,如图61-35所示。最下方有个提示框,里面有段文字“根据FIFO使用的时钟域和终端类型,应用程序将会通过逻辑片架构或内置控制逻辑实现FIFO,因此当使用“终端优化”控制逻辑时,元素的实际数量可能有所不同。”也就是说LabVIEW在生成VHDL代码的时候,真实生成的FIFO深度并没有达到我们前面实际设置的“请求元素长度”。如果用户忽略这个,可能会导致程序在运行的时候,出现一些跟预期目标不一致的情况。 在这里插入图片描述
图61-35:FIFO属性对话框:控制逻辑:终端优化

所以建议用户选择“逻辑片架构”,如图61-36所示。这时下面会提醒用户“元素数量已强制转换为FPGA可高效实现的值。”也就是说LabVIEW生成的FIFO深度会强制为我们申请的长度。 在这里插入图片描述
图61-36:FIFO属性对话框:控制逻辑:逻辑片架构

(4)接下来需要设置FIFO缓冲区单元的数据类型,在左侧的“类别”列表里面选择“数据类型”,然后在右侧的下拉列表里面选择U8无符号整形数据,如图61-37所示。因为我们从AD9280芯片里面采集回来的数据位宽是8位。 在这里插入图片描述
图61-37:FIFO属性对话框:数据类型:U8

(5)最后点击左侧的“接口”,切换到仲裁选项,如图61-38所示。将读取和写入的仲裁全部设置为“从不仲裁”,这样编译出来的代码执行速度也就是时钟约束可以提高不少,同时代码的健壮性和确定性也比仲裁来的稳。 在这里插入图片描述
图61-38:FIFO属性对话框:接口:从不仲裁

(6)全部设置完成后,点击“确定”按钮,即可创建出一个用来存放AD9280数据的缓冲区“FIFO_AD9280_U8_1”,然后再以同样的方式再创建7个接收缓冲区“FIFO_AD9280_U8_2”……“FIFO_AD9280_U8_8”,如图61-39所示。 在这里插入图片描述
图61-39:创建8个AD9280临时FIFO缓冲区(FIFO_AD9280_U8_1…8)

(7)因为我们给用户封装的LabVIEW FPGA PCIe Socket CLIP里面,支持64位、32位、16位和8位数据通道,用户可以根据实际情况进行选择。比如,本节实验,我们需要将最大32MS/s采样率的AD9280原始数据无损的传输到上位机PC中,那么可以选择64位位宽的ch0(默认720MB/s)或者32位位宽的ch1(默认50MB/s);这里我们选择ch0作为上行通道,这个在前面分析过了。

因此,为了将AD9280的数据通过PCIe上行通道传输到上位机PC端,我们还需要创建一个64位位宽的FIFO缓冲区作为不同线程里面的过渡。用户可以参考前面FIFO的创建步骤单独新建一个,也可以直接利用我们前面创建好的“FIFO_PCIe_Write_U64”这个FIFO,位于虚拟文件夹“FIFO_PCIe”里面,如图61-40所示。 在这里插入图片描述
图61-40:创建1个U64类型的PCIe上行通道FIFO缓冲区(实验54里面已经新建过了)

提醒:虽然用户可以根据实际情况选择U64或者I64,但是本节实验我们的AD9280输出的是无符号U8拼接而成的U64,在利用PCIe Socket CLIP上行通道往PC端传输的时候,必须要用无符号数据类型才行,所以这里需要将FIFO数据类型设置为U64,上位机接收到U8字节数组后利用强制类型转换成U8再在波形显示控件中显示出来即可。

(8)最后,我们还需要新建一个可以接收上位机PC下发指令和参数的接收FIFO缓冲区。需要注意的是:因为上位机下发的指令和参数一般速度不会太快,所以我们可以用低速的Channel4~Channel7作为下行数据通道;另外,上位机发送的数据一般会经过“强制类型转换”VI变成字符串或者字节数组,这两种类型的数据表示法默认都是U8,所以,这里我们创建的FPGA接收PC下发的读取FIFO的数据类型也要设置为U8,因此,“FIFO_PCIe_Read_U8”的数据类型要设置为U8,如图61-41所示。其实,这个FIFO我们在前面实验54里面也已经创建过了。如果用户没有新建,那么参考前面的FIFO创建步骤自己创建一个就可以了。 在这里插入图片描述
图61-41:创建1个U8类型的PCIe下行通道FIFO缓冲区(实验54里面已经新建过了)

(9)开始编写程序之前,我们先通过创建虚拟文件夹的方式,将接下来新建的主VI等文件分类进行管理,便于维护和调试。这里用到的方法建议用户在开发自己的项目时,优先分类管理。首先右击虚拟文件夹“实验61- PCIe DMA+8位ADC(模拟数据采集卡)”,新建1个子虚拟文件夹(Main),如图61-42所示。这个虚拟文件夹用来存放主VI文件。 在这里插入图片描述
图61-42:创建1个虚拟文件夹分别用来存放1类文件-便于管理

3.3、下位机FPGA PCIe DAQ主VI编写过程(基于PCIe DMA CLIP + AD9280)

(1)右击“实验61-PCIe DMA+8位ADC(模拟数据采集卡)”里面的子虚拟文件夹“Main”,选择“新建>>VI”,如图61-43所示。然后,新建一个VI,另存为“实验61-PCIe_DMA_8位数据采集卡(AD9280)-FPGA.vi”,如图61-44所示。 在这里插入图片描述
图61-43:右击虚拟文件夹Main新建一个主VI 在这里插入图片描述
图61-44:新建一个下位机FPGA PCIe 8位数据采集卡程序VI

3.3.1、编写8位高速ADC数据采集程序(AD9280用户线程)

(1)因为AD9280芯片的最大采样率是32MSps,这里我们选择25MSps比较合适,正好可以被5整除。所以我们需要提前创建两个衍生时钟50MHz和25MHz,其中,50MHz是用来通过软件二分频生成25MHz给AD9280作为CLK采样转换时钟。

(2)右击项目浏览器里面的板载时钟“200 MHz Onboard Clock”,如图61-45所示。在弹出的衍生时钟属性配置页面里面,将所需衍生频率设置为50MHz,如图61-46所示。点击“确定”按钮后,展开板载时钟“200 MHz Onboard Clock”可以看到这个新建的时钟源,再以相同的方式创建一个25MHz的衍生时钟,如图61-47所示。 在这里插入图片描述
图61-45:新建FPGA衍生时钟 在这里插入图片描述
图61-46:新建50MHz衍生时钟在这里插入图片描述
图61-47:本节实验需要的50MHz和25MHz两个衍生时钟

(3)打开主VI“实验61-PCIe_DMA_8位数据采集卡(AD9280)-FPGA.vi”,切换到程序框图,放置一个定时循环结构,然后双击打开这个定时循环的时钟源配置页面,展开“200 MHz Onboard Clock” 选择里面的25MHz作为时钟源,如图61-48所示。 在这里插入图片描述
图61-48:为定时循环配置时钟源

(4)接下来,我们需要将AD9280的8位数据输出引脚通过“创建数组”函数捆绑在一起,再利用“布尔数组至数值转换”函数变成U8字节数据;然后利用移位寄存器,将实时采集的每个周期点数依次存放到8个AD9280临时FIFO里面,实现环形FIFO存储的效果,为后续实现并转串拼接成U64打下基础,如图61-49所示;当采集到的数据个数=8时,也就是case条件的第7个分支,重新将条件置零,如图61-50所示。这样一个带环形FIFO的AD9280数据采集程序就完成了。 在这里插入图片描述
图61-49:AD9280环形FIFO数据采集线程程序框图(条件为0的分支) 在这里插入图片描述
图61-50:AD9280环形FIFO数据采集线程程序框图(条件为7的分支)

注意:AD9280输出的原始字节数据范围是0255,代表的是02V,因为经过了前端的衰减运放,实际代表的输入电压范围是±5V。

(5)为了后续能够通过上位机下发参数控制AD9280采样率,我们还需要在AD9280采集定时循环里面添加一段软件计数分频的代码。当定时循环计数值超过前面板上指定的“分频系数”时,执行case条件为“真”分支里面的代码,如图61-51所示。需要的注意的是:在这个条件为真的分支里面,将分频计数值清零就可以了。 在这里插入图片描述
图61-51:执行case条件为“真”里面的代码

(6)然后切换到case条件为“假”的分支,直接对分频计数值+1操作,其余保持不变就可以了,如图61-52所示。可以看出,这里面的case条件结构是用来筛选AD9280采集的数据,并没有真正影响和改变AD芯片的内部转换率,因为CLK时钟没有变,依然是25MHz。 在这里插入图片描述
图61-52:执行case条件为“假”里面的代码

(7)为了能够调节ADC的采样率,我们在下位机FPGA程序前面板上放置了一个“分频系数”,如图61-53所示。假设,分频系数为9,那么实际的采样率就是10分频(因为是从0开始计数),等效于25MSps/10=2.5MSps,以此类推,用户可以提前在前面板上设置分频系数,但是需要保存为默认值之后再编译,或者由上位机PC通过PCIe下发参数来控制这个分频系数,然后观察上位机PC端的波形图控件里面的信号周期点数是否发生符合预期的变化。

注意:这种方式相对来说比较简单,不会影响其他时序,特别是有些芯片需要严格时序逻辑时,不得不采用定时循环,但是同时又希望自己能够掌控实际采样率,那么可以采用这种设计模式,两全其美。 在这里插入图片描述
图61-53:FPGA下的8位低速AD采集程序前面板-定时循环软件分频

(8)上面这个定时循环里面只用到了AD9280的8位数据线,还有一个CLK时钟线没有赋值。因为AD9280数据采集线程工作在25MHz的定时循环内部,所以我们的CLK同样需要一路25MHz的时钟信号,因此,我们可以利用前面创建的50MHz衍生时钟通过软件2分频的方式,再利用定时循环+移位寄存器,周期性取反生成一路25MHz时钟通过FPGA上的AA15这个引脚输出给AD9280芯片的CLK引脚,程序框图,如图61-54所示。 在这里插入图片描述
图61-54:通过软件生成一路25MHz AD转换CLK时钟信号

3.3.2、编写并转串程序(8个U8拼接成U64)

接下来,我们需要将AD9280读出来的8个临时FIFO里面的U8数据按照小端格式,拼接之后转移到64位位宽的PCIe上行通信ch0发送FIFO里面。AD9280最大采样时钟频率是25MHz,那么为了能够快速实现8个字节的转移拼接,并转串线程的时钟只要≥25M/8,就不会发生数据溢出或者覆盖。由于FPGA创建的衍生时钟太低也不好,所以这里我们选择25M即可。

下面,我们单独开辟一个并转串线程,首先我们对8个AD9280临时FIFO里面的数据长度进行判断,只有当8个FIFO都存在U8数据时,我们才能在后续的case条件里面同时读取这8个FIFO进行拼接,否则容易产生丢点;由于Xillybus PCIe IP核底层的数据格式属于小端格式,所以我们在进行拼接的时候,将最低的字节也就是AD9280第8个FIFO里面的数据作为U64的最高字节,第1个FIFO里面的U8数据作为U64的最低字节,如图61-55所示。 在这里插入图片描述
图61-55:并转串+小端格式拼接(8个U8拼接之后转成U64)

3.3.3、编写下位机FPGA PCIe Send程序框图(FPGA–>PC):上行

(1)为了降低FPGA PCIe程序开发的难度,我们提前将Xillybus公司提供的Verilog版本的PCIe DMA IP核,利用NI公司的Socket CLIP技术,将其封装到FPGA终端下面,只把需要进行握手的FIFO端口预留出来,如图61-56所示。这样做的好处在于,用户即使完全不懂PCIe总线,也能在FPGA里面调用CLIP进行PCIe代码编写。关于这部分内容,前面7.4节已经做过了详细的讲解,不记得的用户一定要把前面7.4节温习一遍。 在这里插入图片描述
图61-56:利用NI Socket CLIP技术把Xillybus PCIe DMA IP核封装到LabVIEW里面

(2)打开主VI“实验61-PCIe_DMA_8位数据采集卡(AD9280)-FPGA.vi”,切换到程序框图,放置一个定时循环结构,然后双击打开这个定时循环的时钟源配置页面,选择100MHz的PCIe CLIP时钟作为时钟源,如图61-57所示。这个100MHz同步时钟驱动的定时循环,是专门用来处理用户创建的FIFO跟PCIe IP核内部的FIFO之间进行数据交互的,支持标准四线握手串联。 在这里插入图片描述
图61-57:为定时循环配置PCIe 100MHz同步时钟源

(3)接下来,我们可以将FPGA终端下的“pcie_lv_clip_ip_to_gpio_led”节点拖拽到刚刚创建的定时循环里面,如图61-58所示。然后利用“数值至布尔数组”转换VI将FXP<+4,4>定点数转换成布尔数组,再利用数组索引将前3个状态赋给AX7103开发板上的前3个LED,如图61-59所示。这样我们就可以通过LED_1(心跳1Hz闪烁)来判断FPGA里面的PCIe IP核是否正常工作;LED_2状态来判断FPGA是否发送了上行数据;LED_3状态来判断FPGA是否接收到了PC下发的下行数据。最后一个LED_4指示灯留给并转串线程里面的64位发送FIFO是否溢出用的。 在这里插入图片描述
图61-58:将FPGA终端下的PCIe CLIP里面的LED灯状态信号拖拽到定时循环里面 在这里插入图片描述
图61-59:将PCIe CLIP里面的前3个状态输出信号拉出来驱动板子上的LED灯(FPGA PCIe IP核工作状态显示程序框图)

(4)接下来,我们需要编写一下FPGA传递数据给PCIe IP核然后发送到上位机PC的代码。首先,我们把FPGA终端下的PCIE Data里面的通道0(64位位宽)的3个信号端口“pcie_lv_clip_fpga2host_in_64_tdata_ch0”、“pcie_lv_clip_fpga2host_out_64_tready_ch0”、“pcie_lv_clip_fpga2host_in_64_tvalid_ch0”,拖拽到程序框图里面来,如图61-60所示。这3个信号端口其实符合标准的四线握手。 在这里插入图片描述
图61-60:将FPGA终端下的PCIe CLIP上行通道0的3个信号端口拖拽到定时循环里面

(5)然后把我们前面创建好的64位位宽的用户FIFO(FIFO_PCIe_Write_U64)也拖到程序框图里面,然后根据四线握手的方式,将PCIe CLIP通道0的3个握手信号接到FIFO上面去,完整的PCIe数据上行发送程序框图,如图61-61所示。 在这里插入图片描述
图61-61:将FPGA终端下的PCIe CLIP上行通道0的3个握手信号连接到用户FIFO上(FPGA–>PC上行数据发送程序框图)

下面我们来讲解一下上面红色框框里面的程序框图含义:当PCIe IP核内部的FIFO没有满时,这个“pcie_lv_clip_fpga2host_out_64_tready_ch0”信号会拉高,也就是通知FPGA,可以将采集的数据发送给PCIe IP核内部的FIFO(这个FIFO默认是2K大小)。我们可以将这个信号接到FPGA里面用户创建的FIFO(FIFO_PCIe_Write_U64)的“输出就绪”端口,然后把“元素”和“输出有效”两个端口分别接到PCIe IP核的“pcie_lv_clip_fpga2host_in_64_tdata_ch0”和“pcie_lv_clip_fpga2host_in_64_tvalid_ch0”两个信号端口上;当用户FIFO里面有数据输出时,就会直接通过PCIe总线传输到上位机PC端了。

提醒:为了让上位机PC能够自由动态的控制下位机FPGA使能采集,我们加了一个布尔型控件“开始/停止”按钮,这个控件可以由上位机下发指令和参数进行控制,通过取反之后跟准备就绪“pcie_lv_clip_fpga2host_out_64_tready_ch0”信号“相或”之后接到用户FIFO上面,同时还直接接到case结构上面。这样设计的目的是:是为了让下位机FPGA能够自动把用户FIFO(FIFO_PCIe_Write_U64)残留剩余的数据读走等效于清空掉,比如上位机此时下发了一个停止采集指令,然后将布尔控件“开始/停止”设置为“假”,取反之后为“真”,接到了用户FIFO上面,如果此时用户FIFO里面依然残留了一些数据,直接就读出来,通过后面的case结构“假”分支过滤掉了;如果此时上位机PC再下发一个开始采集指令,那么用户FIFO里面一定是最新的数据,通过PCIe发送到上位机的数据也就是最新的了。利用这种巧妙的方式可以模拟一个真实的DAQ数据采集设备。

当然,我们还需要在AD9280采集线程里面加上这个“开始/停止”按钮来控制AD9280原始FIFO数据是否写入,如图61-62所示。只有当这个按钮为“真”时,才会启动采集。 在这里插入图片描述
图61-62:将“开始/停止”按钮添加到AD9280采集线程里面

3.3.4、编写下位机FPGA PCIe Receive程序框图(PC–>FPGA):下行

(1)接下来,我们还需要编写一个下行(PC–>FPGA)数据接收的程序框图。由于下行发送的数据一般都是经过“强制类型转换”函数转成了字节数组或者字符串形式,本质上数据类型属于U8,所以这里,我们可以使用FPGA终端下PCIe Data这个CLP里面的后4个8位位宽(ch4~ch7)的host2fpga通道来传输PC下发的字节数组。比如,本节实验我们使用下行的ch4通道来接收PC下发的指令和参数。

(2)首先,用户可以将FPGA终端下行通道4的3个握手信号(pcie_lv_clip_host2fpga_out_8_tdata_ch4、pcie_lv_clip_host2fpga_in_8_tready_ch4、pcie_lv_clip_host2fpga_out_8_tvalid_ch4)拖拽到FPGA程序框图里面,如图61-63所示。 在这里插入图片描述
图61-63:将FPGA终端下的PCIe CLIP下行通道4的3个信号端口拖拽到定时循环里面

(3)然后根据四线握手的方式,将PCIe CLIP下行通道4的3个握手信号接到用户创建的Read FIFO(FIFO_PCIe_Read_U8)上面去,完整的FPGA PCIe数据下行接收程序框图,如图61-64所示。 在这里插入图片描述
图61-64:将FPGA终端下的PCIe CLIP下行通道4的3个握手信号连接到Read FIFO上(PC --> FPGA下行数据接收程序框图)

至此,关于FPGA PCIe四线握手的通信线程代码就编写完成了,这个线程也是最为核心的一个程序,今后,用户可以直接拷贝这个线程到自己的程序里面,而无需从头编写。

3.3.5、编写下位机FPGA数据解析程序框图:下行数据解析线程

(1)为了能够真实的模拟上位机PC和下位机FPGA之间交互运行,我们还需要编写一个数据解析线程,将PC下发的指令和参数正确的解析出来,然后赋值给FPGA前面板上相应的控件。关于这个数据解析线程的框架,其实我们在本书前面的串口通信和千兆以太网通信实验里面都有提到,不熟悉的用户可以回顾一下前面的相关内容。

(2)数据解析线程的整体框架,其实很简单:就是一个普通while循环,首先通过FIFO“获取待读取元素数量”方法节点,轮询PCIe Read FIFO里面是否接收到指定长度的数据(比如,做定长解析),如果达到了指定长度的数据,则利用for循环一次性将这些字节数组读取出来,然后进行拼接合并转换,变成实际意义的参数或者指令。

(3)比如本节实验里面,上位机PC每次下发的指令和参数一共是9个字节数据,那么我们在下位机FPGA程序里面,就可以将case结构的条件设置为9…,也就是说,只要下位机FPGA接到了至少有9个字节的数据,就会进入case里面,然后将for循环的长度也设置为9,这样就能一次性把这9个字节数据全部读取出来变成一个字节数组,然后再对这个一维数组进行索引拼接合并,甚至于进行加减乘除计算,完整的数据解析线程框图,如图61-65所示。通过程序框图可以看出,我们将前面4个字节进行拼接之后赋值给了前面的用户线程(AD9280数据采集)里面的“分频系数(40ns)”这个控件,用来改变用户线程的循环速度,也就是相当于改变了采样率;第5个字节跟0进行比较,如果大于0就启动采集,小于等于0则停止采集,都是通过给布尔控件进行赋值实现的;最后4个字节是预留的,用户可以根据实际情况下发更多的参数或者指令给FPGA,只要改下对应的数据长度就行了,这里我们就不再赘述了。 在这里插入图片描述
图61-65:完整的下位机FPGA里面的数据解析线程

注意:有些细心的用户可能会问了:为啥上位机PC下发给FPGA的PCIe数据不需要像上行通道那样进行大小端格式转换呢?

这是因为我们在上位机已经提前将所有不同位宽的数据通过“强制类型”转换成字节数组了(单个字节是不存在大小端的),然后通过8位宽的ch4/5/6/7发送给FPGA的;如果直接利用ch0/1/2/3(虽然位宽超过了8位,但是这些函数的输入形参是U8[]或者String)这些超过8位位宽的通道来下发这些指令和参数,虽然LabVIEW里面的字节数组是大端格式,但是下位机FPGA接收到上位机PC下发的U64/32/16却是小端格式的,下位机LabVIEW FPGA读取出来之后,是不能直接参与运算的,我们需要把这些小端格式转成大端格式解析之后才能得到正确的指令和参数。

(4)将前面的AD9280数据采集线程、并转串拼接线程、FPGA PCIe DMA通信线程(上行+下行)、下发的数据解析线程等4个线程(循环)放在一起,就形成了一个完整的基于LabVIEW PCIe Socket CLIP实现的FPGA PCIe下位机数据采集DAQ程序,如图61-66所示。在这里插入图片描述
图61-66:完整的FPGA PCIe数据采集卡下位机程序框图

4、LabVIEW FPGA VI仿真

本节实验涉及的是PCIe总线通信,无法在计算机上进行模拟,所以这里的功能性仿真可以跳过,直接将LabVIEW FPGA主VI程序编译下载到FPGA芯片里面运行,然后重启电脑(热启动),再利用上位机通过下发指令和参数来测试插到电脑主板上的黑金AX7103开发板上的PCIe总线能否正常发送和接收数据包,并在上位机前面板上把采集到的波形数据显示出来。

5、LabVIEW FPGA VI编译下载

(1)直接点击FPGA主VI“实验61-PCIe_DMA_8位数据采集卡(AD9280)-FPGA.vi”上方工具栏里面的“运行”箭头按钮,保存VI之后,弹出Xilinx 编译服务器选择对话框,如图61-67所示。选择本地编译器,然后单击“取消”按钮,此时,在FPGA项目下的程序生成规范里面就会多出来一个与PCIe通信VI同名的程序生成规范(实验61-PCIe_DMA_8位数据采集卡(AD9280)-FPGA),如图61-68所示。双击打开这个程序生成规范属性配置页面,勾选“加载至FPGA时运行”复选框,最后点击“生成”或者“确定”按钮,如图61-69所示。如果点击的是“确定”按钮,那么需要重新运行一下VI即可再次启动Xilinx Vivado 2014.4编译器,进入编译状态。 在这里插入图片描述
图61-67:选择本地编译器进行编译 在这里插入图片描述
图61-68:自动生成的FPGA VI程序生成规范 在这里插入图片描述
图61-69:勾选加载至FPGA时运行

(2)因为7系列FPGA暂时不支持LabVIEW在线前面板调试,所以编译出来的bit文件需要在程序生成规范里面勾选“加载至FPGA时运行”,否则下载之后会无法启动运行。接下来,我们需要获取Vivado编译器对FPGA VI进行编译出来的原始bit位文件,而不是NI的lvbitx文件。运行我们为大家提供的“License-ID-Bitfile-ARTIX7.vi”这个软件即可导出原始bit文件,如图61-70所示。这个软件会跟Vivado编译器进行通信,实时监控Vivado编译过程,如图61-71所示。

注意:图61-70里面另存为出来的bit文件,路径尽量的短,因为后续使用Vivado工具下载bit文件时,路径不能太深,而且最好是英文路径,不要掺杂中文,否则会失败;另外,如果是win10系统,尽量选择非系统盘保存,因为win10系统默认情况下需要管理员权限才能对C盘进行复制保存。 在这里插入图片描述
图61-70:运行获取FPGA VI编译出来的原始bit文件 在这里插入图片描述
图61-71: Xilinx Vivado 2014.4编译器正在编译

(3)编译完成后,可以看到这个VI所占用的FPGA硬件资源和时钟约束情况,然后生成一个与之对应的lvbitx位文件,如图61-72所示。注意:这个lvbitx文件并非FPGA原始的bit位文件,而是NI在bit位文件的基础上又封装了一层,因此,不能通过其他软件或者工具直接下载lvbitx文件。同时,我们E盘里面多了一个原始的FPGA bit位文件,要比lvbitx文件小很多,如图61-73所示。这个bit文件正是后续我们需要用到的FPGA目标可执行文件。点击“关闭”按钮,退出ARTIX7原始bit文件获取软件。 在这里插入图片描述
图61-72:编译完成后的FPGA资源和时钟约束情况 在这里插入图片描述
图61-73:编译出来的8位PCIe数据采集FPGA VI对应的原始bit位文件

(4)编译结束后,如果出现编译失败的提示是“部分编译步骤未被执行”这个的话,如图61-74所示。这个并不影响最终的FPGA bit文件生成,这是因为NI原有的系统里面本身就没有ARTIX7这个家族芯片,我们自己在LabVIEW里面添加了这个芯片,所以编译会出现这样的提示,但是不影响整体的开发流程。用户可以直接忽略这个错误,因为我们已经获得了原始的FPGA bit文件。 在这里插入图片描述
图61-74:提示编译有未被执行的步骤(不影响,直接忽略)

6、PCIe DMA通信上位机PC端程序编写

下面我们需要编写一个PCIe DMA上位机应用程序来测试下位机FPGA PCIe程序是否满足设计需求,相当于开发一个PCIe采集卡上位机程序。这里用户可以调用我们提前给大家封装好的PCIe DMA DLL(使用C\C++\C#\Python)或者PCIe DMA lvlib库VI(使用LabVIEW直接调用),关于我们封装的lvlib库里面的驱动VI,在前面的7.3.3节里面已经做过详细的介绍了,不记得的用户,需要复习一下前面的内容,这里不再赘述。本节上位机程序框图具体的编写过程可以参考本节实验配套的视频教程。

(1)右击项目浏览器里面的“我的电脑”,选择新建一个虚拟文件夹,如图61-75所示。 在这里插入图片描述
图61-75:在“我的电脑”下面新建一个上位机虚拟文件夹

(2)然后对新建出来的虚拟文件夹重命名为“实验61-PCIe DMA+8位ADC(模拟数据采集卡)-上位机”,如图61-76所示。 在这里插入图片描述
图61-76:将新建的虚拟文件夹进行重命名

(3)然后右击上面的虚拟文件夹,选择新建一个VI,将这个VI保存为“实验61-PCIe_DMA_8位数据采集卡(AD9280)-PC.vi”,如图61-77所示。 在这里插入图片描述
图61-77:新建一个PCIe上位机通信程序

(4)为了加快讲解速度,这里我们直接给出PCIe上位机通信程序框图,如图61-78所示。程序框图由两个相互独立的线程组成,也就是两个while循环,分别是PCIe写线程(PC下发数据给FPGA)和PCIe读线程(PC读取FPGA上传的数据)。对应的前面板控件布局,我们也先贴出来,如图61-79所示。 在这里插入图片描述
图61-78:PCIe上位机通信程序框图
在这里插入图片描述
图61-79:PCIe上位机通信前面板

下面我们来分别介绍一下这两个线程对应的程序框图编写过程及其注意事项。

首先,程序启动的时候,一般都会有一个初始化过程,这里也不例外。由于前面板上有一些按键和指示灯等控件,为了让程序每次运行的时候,都能恢复到一个默认状态,这里利用顺序结构,对需要设置的控件进行赋值操作,如图61-80所示。 在这里插入图片描述
图61-80:PCIe通信上位机控件初始化代码

(6)接下来,我们看看上位机PCIe写线程代码是怎么实现的。首先需要利用位于lvlib库里面的子虚拟文件夹“PC_FIFO_Drirect_RW_Raw_Func”里面的PCIe Write Pipe Init函数(FPGA_FIFO_Write_Pipe_Init_DLW30.vi)对PCIe下行通道进行初始化,如图61-81所示。然后在“Pipe_Write_Name”下拉列表里面选择下位机FPGA里面用到的通道,比如,本节实验里面充当下行数据的是8位位宽的写通道ch4,如图61-82所示。 在这里插入图片描述
图61-81:利用PCIe Write Pipe Init函数对下行通道进行初始化 在这里插入图片描述
图61-82:根据下位机FPGA里面实际用到的下行数据通道来选择对应的pipe

(7)如果初始化成功,则这个VI会返回一个非零的句柄出来,也就是“fd_write”,用户可以通过这个引用实现数据的下发。为了人为控制下发的指令和参数,可以利用case条件结构将下行数据的发送代码框起来,如图61-83所示。 在这里插入图片描述
图61-83:下行数据的发送代码
这个下行数据发送,也就是Pipe管道写VI位于lvlib库里面,如图61-84所示。如果今后用户要自己单独编写,可以直接参考例程也可以从lvlib库文件里面拖拽。 在这里插入图片描述
图61-84:下行数据发送管道VI(FPGA_FIFO_Write_Pipe_Send_DLW30.vi)所在路径

为了让上位机PC能够控制下位机FPGA里面的采样率和信号采集的启动和停止,我们将上位机前面板上的U32类型的“分频系数(40ns)”控件通过“强制类型转换”函数变成字节数组;再把前面板上的布尔型“开始/停止采集”按钮转成U8类型的0或者1;然后再把预留的一个参数“size_read_U32”控件通过“强制类型转换”变成字节数组;最后将这3个转换后的数据进行数组拼接(实际上就是字节数组),赋给“FPGA_FIFO_Write_Pipe_Send_DLW30.vi”写VI,如图61-85所示。这样就可以将上位机数据通过PCIe总线下发给FPGA。 在这里插入图片描述
图61-85:将上位机的指令和参数转换成字节数组后下发给FPGA

(8)下面,我们再来看看上位机PC端是如何把FPGA采集上传的数据从DMA FIFO里面读取出来的。同样,需要先对PCIe DMA FIFO进行初始化,用户可以利用lvlib库里面的多态函数“PC_FIFO_DMA_Poly_Init_DLW30.vi”先打开指定的DMA上行数据通道,比如本节实验里面的64位位宽的ch0,同时根据实际情况向计算机申请开辟一段DMA缓冲区,如图61-86所示。这里我们申请了500MBytes的空间来高速缓冲下位机FPGA上传的波形数据。当然,真实应用中,如果用户同时开启了多个DMA上行通道,那么可以根据计算机可用内存容量和吞吐率来合理的分配每个DMA FIFO的缓冲区大小。

当PCIe DMA FIFO初始化成功后,为了让前面板上的“内存池”最大值显示范围随着用户申请的深度自适应,我们将这个申请的FIFO深度值赋给了“内存池”控件的“范围最大值”,再利用“PC_FIFO_DMA_Poly_Start_DLW30.vi”函数来开启下位机FPGA的数据上传功能(实际就是以最快的速度接收FPGA发送给PC端的数据)。通过这两个函数,我们就完成了对PCIe读线程的初始化工作。 在这里插入图片描述
图61-86:对PCIe DMA上行通道进行初始化并启动传输功能

(9)当PCIe DMA缓冲区初始化完成并且启动上传数据接收功能后,程序就会进入PCIe读线程的while循环里面,通过调用“PC_FIFO_Poly_Buffer_Length_DLW30.vi”函数获取当前DMA FIFO里面存在的数据长度,单位是字节,这个VI实现的功能有点类似于我们平时用的比较多的一个NI-VISA属性节点“串口缓冲区字节数”。如图61-87所示。 在这里插入图片描述
图61-87:获取指定PCIe通道的DMA FIFO缓冲区里面的字节数量

(10)为了提高读取效率或者为了方便定长数据的解析,我们可以给定一个长度阈值,只有当上位机PC端的PCIe DMA FIFO里面接收到了一定长度的字节数之后,再把这些字节数组一次性读取出来。因为PCIe传输的数据都是以字节为单位,所以如果用户希望读取的数据位宽是字节的倍数,本节实验,下位机FPGA上传的AD9280数据是U8类型的信号,正好就是1个字节,所以在判断之前,我们需要将U8点数×1,再做比较判断,这样读取出来的数据点数才是正好的,如图61-88所示。 在这里插入图片描述
图61-88:轮询判断上位机PC端的DMA FIFO缓冲区里面的当前字节数是否达到指定长度

(11)一般情况下,如果下位机发送的数据吞吐率很高,那么上位机读取的频率和每次读取的数量长度要满足一定的范围才能保证所有的数据不会丢失,如果每次读取的数据长度很短,那么需要读取的速度就要很快,这样不仅会极大的消耗CPU的资源,也有可能会造成数据溢出丢失;关于这个数值取多少合适,用户可以根据实际情况做实验决定。当DMA FIFO缓冲区里面的数据达到指定长度后,用户可以利用“PC_FIFO_DMA_Poly_Read_DLW30.vi”函数从DMA FIFO里面将指定长度的字节数组读取到LabVIEW应用程序当中来,程序如图61-89所示。在这里插入图片描述
图61-89:读取上位机PCIe DMA FIFO里面的数据到LabVIEW应用程序里面来

(12)读取出来的数据单位是字节数组,如果从FPGA里面接收到的数据物理意义并不是字节类型,那我们还需要借助“强制类型转换”函数将原始的字节数组转换真实的数据,本节实验里面,AD9280采集的数据正好是U8,所以不需要强制类型转换,程序如图61-90所示。注意:如果FPGA采集的数据不是U8,比如I8或者更高位宽的数据类型,则需要利用强制类型转换函数进行转换。 在这里插入图片描述
图61-90:如果下位机FPGA采集的正好是U8数据,则不需要强制类型转换

(13)当AD9280信号读到之后,我们可以在前面板上放置一个波形图显示控件,将FPGA发送上来的数据以波形的方式呈现出来,但是考虑到实际应用中,如果波形图控件里面每次显示的点数过大的话,会导致电脑CPU、内存和显卡压力过大,CPU运行速度会降低,这样不利于快速读取数据,因此,我们在PCIe-读线程里面加了两个控制按钮,可以人为控制读取出来的波形是否进行拼接以及是否开启波形显示功能,如图61-91所示。关于这几个功能的演示,我们在后续的实验演示环节里面再给用户做详细的介绍。 在这里插入图片描述
图61-91:是否开启波形显示和波形首尾拼接功能

(14)当用户需要将这个程序停止运行的时候,需要先把“PCIe-读线程”停下来,然后利用“PC_FIFO_DMA_Poly_Stop_DLW30.vi”、“PC_FIFO_DMA_Poly_Exit_Wait_DLW30.vi”和“PC_FIFO_DMA_Poly_Destroy_DLW30.vi”这3个VI将PCIe DMA读取通道销毁掉,如图61-92所示。 在这里插入图片描述
图61-92:停止程序后关闭、退出、销毁PCIe DMA读取通道

用户编程的时候,不能先销毁PCIe写通道,这是因为底层的PCIe DMA读通道内部调用的是非阻塞的文件读取函数,如果下位机FPGA停止上传数据,那么这个DMA Read函数就会阻塞,LabVIEW程序就会无法响应跟死机一般, 所以在销毁PCIe DMA Read之前,下位机FPGA必须要一直发送数据,中间不能停,发送快慢不影响,但是一定要发送数据才行。当PCIe DMA读取通道销毁之后,就可以下发停止指令给下位机FPGA,将FPGA里面的数据采集发送给停下来,最后再把PCIe Write写通道引用关闭掉,如图61-93所示。 在这里插入图片描述
图61-93:发送完停止FPGA采集指令后销毁PCIe Write写通道引用

注意:细心的用户发现了我们的“PCIe-读线程”while循环里面有一个禁用结构,里面是ms倍数延时,如图61-94。这个延时的作用是什么呢?这个ms倍数延时函数主要是用来控制我们的while循环读取频率,可以释放CPU资源。假设,当FPGA采样率很低的时候,也就意味着上位机PC端接收到的数据吞吐率低,那么我们就没有必要让“PCIe-读线程”跑的太快,这样会极大消耗CPU资源,如果主程序里面还有其他的事情要处理,那么CPU会显示吃力;如果下位机FPGA的采样率很高的时候,为了避免DMA FIFO缓冲区溢出,我们需要以最快的速度将里面的数据全部读取出来,这是我们可以给ms倍数延时函数赋值0或者直接将这延时函数禁用掉,这样“PCIe-读线程”这个while循环就会全速运行。 在这里插入图片描述
图61-94:控制PCIe读取速度的ms倍数延时函数

(15)最后,再来介绍一下这个PC端的上位机PCIe通信程序前面板上的控件有哪些功能和注意事项,完整的前面板如图61-95所示。 在这里插入图片描述
图61-95:PCIe上位机通信程序前面板

程序运行之后,不出意外的话,“申请开辟计算机内存成功?”布尔指示灯会点亮;然后用户可以通过“分频系数(40ns)”控件里面的数值参数控制下位机FPGA里面的采样率,比如当我们设置为9,换算一下也就是(9+1)40ns=400ns=0.4us<=>2.5MS/s采样率,AD9280换算成字节为单位的采样率的话,也就是2.5MByte/s,这个采样率算是很低的了;所以我们可以将前面板上的“开始/停止采集”和“显示波形?”两个按钮全部点亮,前者是用来通知FPGA启动采集发送数据,后者是把上位机接收到的波形数据在波形图控件中显示出来;然后可以在“size_read_U32”控件里面输入一个点数,比如2048000,也就是2M个U8(因为本节实验下位机FPGA里面的数据是8位的AD9280)点;最后,当我们点击一下“Send”按钮之后,上位机PC就会把指令和参数通过PCIe Write通道4下发给FPGA,然后就能看到“内存池”里面的水在不断升高,同时“Buffer_Valid_Length_U8[]”控件里面的数值也在不断变大,当内存池里面的数据量达到2MBytes时(2M1),波形图里面会立刻出现一个长度为2M个点的信号,同时内存池里面的数据清空之后又会从0开始慢慢增加。这个过程可以很好地反应FPGA采集数据发送到PC端的整个动态展示,我们在后续的实验演示环节再给用户讲解。

需要注意的是:如果用户提高了FPGA采样率,比如,把“分频系数(40ns)”这个参数设置为0,也就是40ns,相当于25MS/s,换算成字节单位就是25MB/s,对于B版本的Xillybus IP核来说,可以完全胜任,但是此时,用户最好把前面板上的“显示波形?”熄灭关掉,这样可以节省CPU资源,加快LabVIEW应用程序读取速度,减少上位机的PCIe DMA FIFO溢出造成的数据丢失。

7、实验现象

7.1、准备工作

(1)接好硬件设备。先把台式电脑或者工控机关机,拔掉电源;然后将黑金带PCIe接口的AX7103开发板插到电脑或者工控机主板的PCIe插槽里面,再把Xilinx JTAG下载器(当然,也可以用Digilent JTAG或者其他家的FPGA JTAG下载器),JTAG这头插到黑金AX7103开发板上的JTAG下载口,USB那头接到笔记本电脑上(注意:因为笔记本上面安装了LabVIEW FPGA开发环境和Vivado编译器,台式电脑没有安装Vivado软件,所以LabVIEW FPGA下位机程序我们是在笔记本里面开发的,由于笔记本没有PCIe插槽,所以上位机PCIe通信程序实际是运行在台式电脑或者工控机里面的;此时,笔记本的设备管理器里面识别出来一个Xilinx JTAG下载器设备,如图61-96所示。关于Xilinx下载器驱动安装方法在前面,我们已经给用户介绍过了,这里不再赘述。 在这里插入图片描述
图61-96:识别出来的Xilinx JTAG下载器(DCL9或者DCL10)

工控机主板未上电之前,实际接好的AX7103 PCIe FPGA开发板跟电脑之间的接线实物图,可参考图61-97~61-99所示。 在这里插入图片描述
图61-97:将AN108模块(AD9280)插到黑金AX7103开发板上的J11拓展口上面,然后再插到工控机主板上(注意:电脑千万不要上电、不要开机,PCIe不支持热插拔!!!) 在这里插入图片描述
图61-98:AX7103开发板与Xilinx JTAG下载器接线实物图 在这里插入图片描述
图61-99:AX7103与AD9280模块、信号发生器、JTAG下载器和笔记本之间的接线实物图

接下来,打开台式电脑或者工控机的电源开关,开机(必须),过一会,如果AX7103开发板上的红色电源指示灯点亮,说明主板给PCIe板卡的供电是正常的,如图61-100所示。注意:当主板上电后,主板会通过PCIe插槽自动提供电源给到黑金AX7103 FPGA开发板,不需要外接电源适配器。 在这里插入图片描述
图61-100:开机后,主板会通过PCIe插槽提供电源给到黑金AX7103 FPGA开发板
(不需要接外部电源适配器)

7.2、下载bit文件

(1)找到NIFPGA安装目录下的Vivado.bat批处理文件,如果LabVIEW FPGA工具包安装在C盘,那么默认的路径就是:C:\NIFPGA\programs\Vivado2014_4\bin,如图61-101所示。双击运行这个bat文件,就可以启动Vivado工具,启动成功后的Vivado软件如图61-102所示。 在这里插入图片描述
图61-101:找到启动Vivado工具的批处理文件 在这里插入图片描述
图61-102:启动成功后的Vivado工具界面

(2)由于Vivado下载工具引入了所谓的硬件服务器的概念,具体什么东西,感兴趣的用户可以百度一下。这个服务hw_server经常由于系统防火墙或者杀毒软件或者win10实时防护等原因导致未开启,用户可以打开任务管理器,如果里面没有hw_server这个进程,表明这个服务没有启动。那么我们可以直接使用TCL Console命令手动重启一下这个hw_server服务。在Vivado软件界面最下方Tcl Console里面输入hw_server -s TCP::3121 -d,如图61-103所示。然后按下回车键,就能看到hw_server application启动成功的提示,如图61-104所示。 在这里插入图片描述
图61-103:通过TCL命令手动重启hw_server服务器 在这里插入图片描述
图61-104:hw_server application服务启动成功

(3)接着,单击首页面里面的“Open Hardware Manager”按钮启动下载页面,打开FPGA硬件管理器,然后点击“Open target”,选择下拉列表里面的“Open New Target”,如图61-105所示。在弹出来的硬件选择对话框里面,点击“Next”,如图61-106所示。 在这里插入图片描述
图61-105:启动Vivado FPGA硬件管理器 在这里插入图片描述
图61-106:进入FPGA硬件目标配置页面

(4)进入硬件服务器设置页面之后,可以看到有两个服务器供用户选择,一是Local server本地服务器和Remote server远程服务器。一般情况下,默认选择Local server,然后点击Next,如图61-107所示。此时会弹出一个进度条,提示正在Connect连接服务器,如图61-108所示。如果连接成功则继续,如果出现图61-109所示的结果,说明本地服务器无法连接成功,此时我们需要采取另外一种方式,也就是Remote server。 在这里插入图片描述
图61-107:FPGA硬件服务器选择页面 在这里插入图片描述
图61-108:硬件服务器连接中 在这里插入图片描述
图61-109:本地服务器连接失败

(5)既然本地服务连接不了,那我们就选择Remote server远程服务器,如图61-110所示。其中Host name需要填与FPGA相连的计算ID名称,用户可以单击开始菜单,右击“计算机”选择属性,如图61-111所示,然后在弹出来的属性对话框页面的最下方,找到计算机ID名称,如图61-112所示。将其复制到图61-110里面来,端口号保持默认的3121不变。 在这里插入图片描述
图61-110:填写远程服务器主机名称和端口号 在这里插入图片描述
图61-111:右击计算机选择属性 在这里插入图片描述
图61-112:在属性页面里找到计算机名称

(6)点击Next下一步之后,可以看到立刻发现了Xilinx JTAG下载器和Aritx7主芯片,如图61-113所示。为了提高下载速度,我们可以将JTAG Clock Frequency时钟频率拉到最大值12MHz,然后点击Next下一步,进入汇总页面,如图61-114示。 在这里插入图片描述
图61-113:识别出来的Xilinx JTAG下载器和ARTIX7芯片 在这里插入图片描述
图61-114:FPGA硬件目标汇总页面

(7)接下来,在Hardware窗口里面,右击ARTIX7芯片里面的FPGA部分,也就是XC7A100T,选择“Program Device…”,如图61-115所示。然后在弹出来的位流文件路径选择对话框里面,单击右侧的浏览按钮,如图61-116所示。找到E盘下面编译成功的PCIe总线通信FPGA bit文件(ARTIX7_XC7A100T_PCIe_X4_8Chs_B+AD9280.bit),如图61-117所示。点击OK按钮,然后回到路径选择页面,再点击“Program”按钮即可将这个bit文件烧写到FPGA芯片里面运行,如图61-118所示。烧写过程非常快,原因是因为前面我们把Xilinx JTAG下载器的时钟频率设置成了最大的12MHz,所以一瞬间就下载完成了,如图61-119所示。 在这里插入图片描述
图61-115:右击FPGA芯片选择Program Device 在这里插入图片描述
图61-116:点击浏览按钮选择bit文件 在这里插入图片描述
图61-117:找到先前编译成功的LabVIEW FPGA原始bit文件 在这里插入图片描述
图61-118:单击Program按钮下载bit文件 在这里插入图片描述
图61-119:FPGA bit文件下载过程

(8)当含有PCIe IP核的FPGA bit文件下载到FPGA芯片里面运行之后,AX7103开发板上的4个LED灯全部熄灭了,如图61-120所示。这是因为,虽然FPGA bit下载进去了,但是上位机主机端(台式电脑或者工控机)并没有主动去识别加载PCIe驱动,所以下位机FPGA里面的PCIe底层通信代码并没有进入正常运行模式,故4个LED灯都是熄灭的。 在这里插入图片描述
图61-120:FPGA bit文件下载成功,开发板上的4个LED灯全部熄灭状态

7.3、重启台式电脑或者工控机(热启动,加载PCIe驱动)

当PCIe FPGA bit文件下载到FPGA芯片里面之后,如果开发板掉电又上电的话,那么FPGA芯片里面的代码会自动消失的,所以有时候为了避免反复下载bit文件,我们可以把编译出来的bit文件先转成mcs或者斌文件,然后固化到开发板上的Flash里面,这样即使开发板掉电程序依然存在。但是固化bin文件时间会比较长,为此,这里我们推荐用户采用热启动的方式来重启电脑,FPGA开发板不掉电的情况下,重启电脑自动识别加载PCIe驱动。

(1)当我们把含有PCIe通信IP核的FPGA bit文件下载到FPGA芯片里面,电脑并不会主动去识别这个PCIe设备,我们需要重启电脑才可以让主机去主动识别加载PCIe驱动。由于bit文件运行在FPGA芯片里面的,所以不能直接关机重启,因为电脑关机之后,主板就没有电了;所以我们需要右击电脑开始菜单,选择“重启”,如图61-121所示。这就是所谓的热重启。 在这里插入图片描述
图61-121:点击开始菜单,选择重新启动(热启动)

(2)当电脑重启成功后,打开设备管理器,如果出现未知PCI设备黄色感叹号,我们可以右击更新驱动程序,然后选择加载一下Xillybus官方提供的inf驱动文件即可,如图61-122所示。正常情况下,只要加载一次驱动文件就可以了,以后重启的时候,系统会自动去识别所有底层使用了Xillybus提供的PCIe IP核的FPGA硬件。 在这里插入图片描述
图61-122:第一次识别加载PCIe硬件时,需要选择更新Xillybus提供的PCIe inf驱动文件

(3)PCIe驱动加载成功后,设备管理器里面就会出现名为Xillybus的设备,如图61-123所示。 在这里插入图片描述
图61-123:主机识别成功后的FPGA PCIe硬件设备默认名称

(4)接下来,还需要检验一下上位机主机系统是否正常加载了FPGA下位机里面添加的PCIe DMA通道号。比如我们在下位机FPGA里面封装了8上8下的PCIe DMA CLIP节点,那么上位机PC端必须要识别出来所有的通道号名称和具体路径,这样后续主机端才能跟下位机FPGA进行PCIe通信。

具体怎么操作呢?用户可以到微软官方网站上,下载一个小工具“WinObj.exe”,直接双击运行,它可以将我们电脑上所有正常安装的硬件,统统识别出来,比如我们平时常用的串口设备,如图61-124所示。而我们刚刚加载安装成功的Xillybus PCIe设备端口读写通道号也被枚举出来了,如图61-125所示。 在这里插入图片描述
图61-124:通过WinObj工具可以查看计算机所有的串口设备号
在这里插入图片描述
图61-125:通过WinObj工具查看计算机枚举出来的Xillybus PCIe端口通道号(A版本)

这些枚举出来的端口通道名称就是上位机应用程序需要访问的端口名称,比如A版本里面的“xillybus_write_32_ch0”,意为下行(Host–>FPGA)的写端口,位宽是32位,通道号是0;B版本则是“xillybus_write_64_ch0”,意为下行(Host–>FPGA)的写端口,位宽是64位,通道号是0;本节实验我们使用的就是B版本的读取通道ch0和写入通道ch4。

关于Xillybus驱动加载和通道号查看及变更等更多的信息,不熟悉的用户可以回顾一下本章前面7.3.2节的相关内容,这里不再赘述。

(5)当前面的步骤走完之后,以后重启电脑就不需要了,因为系统已经记住了这些操作和驱动。此时,可以看到插到主板上的FPGA PCIe开发板上的LED1指示灯以1Hz的频率开始闪烁起来了,如图61-126所示。说明FPGA芯片里面的PCIe通信代码正常启动运行了,LED1属于心跳信号,Xillybus提供的PCIe IP核就是通过这个状态来告诉用户,PCIe底层代码是否正常运行。 在这里插入图片描述
图61-126:电脑重启之后,FPGA芯片里的PCIe IP核工作正常后,LED1闪烁(心跳)

到这里,我们的FPGA下位机PCIe通信程序运行起来了,并且上位机的PCIe驱动也已安装成功了!

(6)将外部函数信号发生器的波形设置为Sine正弦,频率设置为:10KHz,峰峰值设置为±2.5V,如图61-127所示。 在这里插入图片描述
图61-127:将外部函数信号发生器设置为正弦波(频率=10KHz,峰峰值=±2.5V)

7.4、观察现象(运行上位机PCIe通信测试程序)

(1)打开运行上位机PCIe通信测试程序,位于项目浏览器“我的电脑”下面的“实验61-PCIe_DMA_8位数据采集卡(AD9280)-PC.vi”,如图61-128所示。 在这里插入图片描述
图61-128:运行上位机PCIe总线通信测试程序

(2)运行上位机程序之后,在输入控件“分频系数(40ns)”里面输入24,相当于等效的采样率是25×40ns=1us<=>1MS/s<=>1MB/s(AD9280位宽是8位U8);按下“波形显示?”按钮,这样后面就能在波形图里面看到每次读取的Sine信号了;再在输入控件“size_read_U32”里面输入每次从PC端的DMA FIFO缓冲区里面需要读取的数据长度,由于前面我们设置的采样率不高,所以这里输入的读取长度相对自由一些,比如我们设置2048000,也就是2M个点,单位是U8,换算成字节的话,就是每次读取2M个Byte;最后再按下点亮“开始/停止采集”这个按钮。

当一切设置就绪后,点击一下前面板上的“Send”发送按钮(这是一个触发型的控件),每点击一次,上位机都会把前面板上设置的这些参数转换成字节数组通过PCIe写通道ch4发送给下位机FPGA,当FPGA接收完成并解析出来指令和参数后,会立刻把采集到的Sine信号通过PCIe DMA上行通道ch0源源不断的发送给上位机PC内存里面。

此时,插到主板上的FPGA PCIe开发板上的LED2和LED4都是熄灭状态,LED3常亮,LED1心跳灯正常闪烁。LED3常亮是因为我们下发的数据量和吞吐率都很低;LED2反应的是下位机FPGA发送数据到上位机PC端的状态,如果数据传输吞吐率很高,且不闪烁的话,说明在Xillybus PCIe IP核传输这一层没有发生数据丢失(因为B版本里面ch0通道我们默认设置的传输带宽是720MB/s);LED4是我们人为加在AD9280采集并转串线程里面的64位发送FIFO端的超时状态指示灯,如果LED4点亮或者闪烁,那就说明AD9280采集线程里面的FIFO数据溢出了,采集的原始Sine信号还没来得及给到Xillybus PCIe IP核之前就丢点了。关于这4个指示灯的实际运行状态,用户必须要自己做实验才能看到。

因此,通过上面LED2和LED4指示灯的状态分析,可以看出:下位机FPGA模拟采集到的所有Sine数据没有发生溢出或者丢失,上位机全部接收到了,如图61-129所示。 在这里插入图片描述
图61-129:开启波形显示功能,禁用波形首尾拼接(采样率:1MS/s<=>1MB/s)

上位机前面板上的“内存池”液罐显示控件里面的高度(程序默认开辟的是500MB),在不断升高,直到涨到2MB时,一下子又清空了,这是因为我们的上位机LabVIEW应用程序把这2MB数据全部读取出来了,经过强制类型转换之后在波形图上显示出来了;所以,实际观察到的情况是:每隔2s,内存池会清空一次,同时,波形图里面的曲线会刷新一次,这是因为下位机FPGA的采样率是1MB/s,上位机LabVIEW程序每次批量读取2MB数据,所以刷新周期就是2s。

我们将波形图里面的曲线进行放大,比如,将横坐标设置为500个点进行显示,可以看到,正好有5个周期的正弦曲线在里面,因为外部Sine信号频率是10KHz,AD9280采样率设置的是1MS/s,所以每个周期的采集量化点数就是100个,因此,500个点就是5个周期信号。再来看看幅度对不对,外部信号峰峰值是±2.5V,而AD9280模块的采集范围是正负5V,采集到上位机的U8数据范围是0~255,其中0对应的是-5V,255对应的是5V,128对应的是0V;所以-2.5V应该就是64,+2.5就是128+64=192;这个分析结果正好与上面图61-129完全匹配上了,说明我们编写的下位机FPGA程序和上位机程序都是完全正确的。

(3)接下来,我们验证一下FPGA发送上来的数据是否是连续的,是否存在丢点。首先,按下点亮前面板上的“拼接?”按钮,其他控件参数保持不变;然后点击一下前面板上的“Send”发送按钮(这是一个触发型的控件),每点击一次,上位机会立刻把前面板上设置的这些参数转换成字节数组通过PCIe写通道ch4发送给下位机FPGA,当FPGA接收完成并解析出来指令和参数后,会立即把采集到的Sine信号通过PCIe DMA上行通道ch0源源不断的发送给上位机PC内存里面。

因为我们没有改变FPGA里面的控制参数,所以插到主板上的FPGA PCIe开发板上的4个LED灯现象应该与上面是一致的:即LED2和LED4都是熄灭状态,LED3常亮,LED1心跳灯正常闪烁。LED3常亮是因为我们下发的数据量和吞吐率都很低;LED2反应的是下位机FPGA发送数据到上位机PC端的状态,如果数据传输吞吐率很高,且不闪烁的话,说明在Xillybus PCIe IP核传输这一层没有发生数据丢失(因为B版本里面ch0通道我们默认设置的传输带宽是720MB/s);LED4是我们人为加在FPGA用户线程里面的写入FIFO端的超时状态指示灯,如果LED4点亮或者闪烁,那就说明AD9280并转串线程里面的FIFO数据溢出了,采集的原始信号还没来得及给到Xillybus PCIe IP核之前就丢点了。关于这4个指示灯的实际运行状态,用户必须要自己做实验才能看到。

因此,通过上面LED2和LED4指示灯的状态分析,可以判断出:下位机FPGA模拟采集到的所有Sine数据没有发生溢出或者丢失,上位机全部接收到了,如图61-130所示。 在这里插入图片描述
图61-130:开启波形显示功能,开启波形首尾拼接(采样率:1MS/s<=>1MB/s)

上位机前面板上的“内存池”液罐显示控件里面的高度(程序默认开辟的是500MB),在不断升高,直到涨到2MB时,一下子又清空了,这是因为我们的上位机LabVIEW应用程序把这2MB数据全部读取出来了,经过强制类型转换之后在波形图上显示出来了;实际观察到的情况是:每隔2s,内存池会清空一次;同时,波形图里面的Sine正弦曲线会以每隔2s的速度增加变长,而且中间都是完全无缝衔接连续的;之所以是2s刷新一次,是因为下位机FPGA的采样率是1MB/s,上位机LabVIEW程序每次批量读取2MB数据,所以刷新周期就是2s。

用户可以将2048000整数倍处进行放大观察,看看采集上来的信号是否是连续的,图61-130里面显示的是连续的,也间接说明采集过程中没有发生数据溢出或丢失。

注意:如果让这个LabVIEW上位机程序一直运行着,过一会会弹出一个错误提示:系统内存不足,请释放内存空间。这是因为我们在程序框图里面开启了波形首尾拼接功能,随着时间的延长,程序框图里面的数组长度和波形里面的斜线越来越大,一旦超越了LabVIEW软件本身的承受能力,就会导致LabVIEW报警甚至卡死崩溃。所以,一般情况下,我们都是人为控制一下程序框图里面的数组长度以及波形控件里的数据点数,不能太大,否则不仅会造成计算机卡顿甚至造成软件崩溃。

(4)下面我们来提高一下FPGA采样率,看看会有什么情况发生:首先,将输入控件“分频系数(40ns)”里面的24改成9,相当于等效的采样率提高了2.5倍:(9+1)×40ns = 400ns <=> 2.5MS/s <=> 2.5MB/s(位宽是8位U8);按下“波形显示?”按钮,这样后面就能在波形图里面看到每次读取的Sine信号了,熄灭“拼接?”按钮;其他参数保存不变,比如,输入控件“size_read_U32”里面还是输入2048000,也就是2M个点,单位是U8,换算成字节的话,就是每次读取2M个Byte;“开始/停止采集”这个按钮要一直点亮。

设置完成后,再点击一下前面板上的“Send”发送按钮(这是一个触发型的控件),每点击一次,上位机都会把前面板上设置的这些参数转换成字节数组通过PCIe写通道ch4发送给下位机FPGA,当FPGA接收完成并解析出来指令和参数后,会立刻把采集到的Sine信号通过PCIe DMA上行通道ch0源源不断的发送给上位机PC内存里面。

此时,插到主板上的FPGA PCIe开发板上的4个LED灯状态跟前面还是一样的:即LED2和LED4都是熄灭状态,LED3常亮,LED1心跳灯正常闪烁。LED3常亮是因为我们下发的数据量和吞吐率都很低;LED2反应的是下位机FPGA发送数据到上位机PC端的状态,如果数据传输吞吐率很高,且不闪烁的话,说明在Xillybus PCIe IP核传输这一层没有发生数据丢失(因为B版本里面ch0通道我们默认设置的传输带宽是720MB/s);LED4是我们人为加在FPGA用户线程里面的发送FIFO端的超时状态指示灯,如果LED4点亮或者闪烁,那就说明并转串线程里面的64位FIFO数据溢出了,采集的原始信号还没来得及给到Xillybus PCIe IP核之前就丢点了。关于这4个指示灯的实际运行状态,用户必须要自己做实验才能看到。

因此,通过上面LED2和LED4指示灯的状态分析,即使此时下位机FPGA里面的采样率提高到了2.5MS/s,下位机FPGA模拟采集到的所有斜坡数据依然没有发生溢出或者丢失,上位机全部接收到了,如图61-131所示。 在这里插入图片描述
图61-131:开启波形显示功能,禁用波形首尾拼接(采样率:2.5MS/s<=>2.5MB/s)

上位机前面板上的“内存池”液罐显示控件里面的高度(程序默认开辟的是500MB),在不断升高,瞬间就涨到了2MB时,不到1秒又清空了,这是因为下位机FPGA的采样率是2.5MB/s,上位机LabVIEW程序每次批量读取2MB数据,所以刷新周期就是0.8s,相当于1.25Hz的刷新率;波形图里面的Sine信号基本上处于不变状态,这是因为AD9280采样率跟外部信号的频率整除关系,用户可以尝试改变一下外部Sine信号的频率或者幅度就能看到上位机Sine曲线明显变化了。

我们将波形图里面的曲线进行放大,比如,将横坐标设置为1000个点进行显示,可以看到,正好有4个周期的正弦曲线在里面,因为外部Sine信号频率是10KHz,AD9280采样率设置的是2.5MS/s,所以每个周期的采集量化点数就是250个,因此,1000个点就是4个周期信号。幅度没有变化,峰峰值依然是64~192,对应的就是±2.5V。

(5)下面我们进一步提高FPGA采样率,看看会有什么情况发生:先将输入控件“分频系数(40ns)”里面的9改成0,相当于等效的采样率提高了10倍:(0+1)×40ns = 40ns <=> 25MS/s <=> 25MB/s(位宽是8位U8);其他保持不变,比如,“波形显示?”按钮处于点亮状态,这样后面就能在波形图里面看到每次读取的正弦信号了;“拼接?”按钮依然设置为熄灭状态,防止内存溢出;输入控件“size_read_U32”里面的2048000,也就是2M个点保持不变,单位是U8,换算成字节的话,还是每次读取2M个Byte;“开始/停止采集”这个按钮要一直点亮。

设置完成后,再点击一下前面板上的“Send”发送按钮(这是一个触发型的控件),每点击一次,上位机都会把前面板上设置的这些参数转换成字节数组通过PCIe写通道ch4发送给下位机FPGA,当FPGA接收完成并解析出来指令和参数后,会立刻把采集到的Sine信号通过PCIe DMA上行通道ch0源源不断的发送给上位机PC内存里面。

此时,插到主板上的FPGA PCIe开发板上的4个LED灯状态跟前面还是保持一样,其中,LED4依然保持常灭状态,所以,通过LED4指示灯的状态分析可知,即使下位机FPGA里面的AD9280采样率提高到了25MS/s,FPGA采集的信号数据没有溢出和丢失,同时Xillybus PCIe DMA传输层也没有出现拥堵现象;上位机读到的Sine信号是连续的,如图61-132所示。这是因为我们选择的是ch0通道进行传输,这个通道的带宽是720MB/s,加上上位机没有什么复杂的解析和算法,能及时读走DMA缓冲区里面的数据,所以就没有产生数据溢出或者丢失。 在这里插入图片描述
图61-132:LED2和LED4间歇性闪烁,正弦信号出现跳变(采样率:25MS/s<=>25MB/s)

我们将波形图里面的曲线进行放大,比如,将横坐标设置为10000个点进行显示,可以看到,正好有4个周期的正弦曲线在里面,因为外部Sine信号频率是10KHz,AD9280采样率设置的是25MS/s,所以每个周期的采集量化点数就是2500个,因此,10000个点就是4个周期信号。幅度没有变化,峰峰值依然是64~192,对应的就是±2.5V。

(6)当然,感兴趣的用户,还可以将下位机FPGA里面的AD9280采集线程的定时循环时钟源改成32MS/s,来测试一下AD9280的极限采样率以及我们设计的PCIe板卡带宽是否能够胜任。(拓展实验,用户需要自己创建32MHz和64MHz两个衍生时钟)

7.5、实验分析(至关重要)

(1)本节实验因为下位机FPGA的采样率不高,远远小于上行通道ch0的极限带宽,加上上位机没有什么复杂的算法,所以即使把采样率提到最大,依然不会溢出或者丢点。

(2)重要结论:只要上位机PCIe DMA读线程足够快,就不会发生下位机FPGA FIFO溢出,LED2和LED4就不会闪烁,数据就不会丢失;所有采集的数据都可以传输到计算机内存里面来。

(3)有些细心的用户发现了,如果上位机每次读取长度设置为2048000或者1024之类的,随着时间的推移,波形里面的曲线会周期性的出现截断现象,这是什么原因呢?其实,早在前面给大家讲解上位机PCIe DMA函数的时候,就重点提到过了,如下:

注意:初始化函数的缓冲区深度设置,跟后面的(PC_FIFO_DMA_Poly_Read_DLW30.vi)读取长度之间需要满足一定的关系,否则会出现读取丢点的情况。原因是我们开辟的缓冲区(假设FIFO深度为M),实际上是一个异步的环形FIFO,FPGA端会把数据源源不断的往这个FIFO里面写,然后应用层,比如LabVIEW会从这个FIFO里面取数据,至于什么时候读,读多少?取决于这个FIFO里面现有的数据长度,一般的上位机读取机制是:当上位机判断到这个FIFO已经有了至少N个点时,我们就调用这个函数(PC_FIFO_DMA_Poly_Read_DLW30.vi)把N个点读取出来,余下的数据还会留在FIFO里面,由于开辟的是环形FIFO,所以读取位置会不断的从开头到末尾扫描,因此,在跨越边界长度的时候,会出现读取截断或者丢点的现象。结论是M要能整除N才行。

举例说明:比如我们开辟了一个500MByte深度的缓冲区,然后每次读取2048个Byte点,实际上500M/2048无法整除,这样就会导致在读取最后一帧的时候,读出来的数据有问题,为了避免这个问题,我们有两种解决方法:如果读取的长度事先确定了,那么我们在申请开辟缓冲区长度的时候,将读取长度乘上一个整数就可以了,比如开辟2048×100000=204.8MByte;如果读取长度是变化的,那么需要满足整除这个条件,比如我们开辟的环形FIFO大小是500M,那么读取长度可以选择5、10、100、1000之类的,不能选择1024或者3之类的。上面讲解的机制和原理,用户一定要记在心里!!!

小心:在后续的PCIe+OV5640摄像头例程里面,可以看到,720p RGB565格式下的每帧图像字节是1280×720×2=1843200Byte,如果用户想要每次读取一幅图像刷新显示,那么在调用初始化函数(PC_FIFO_DMA_Poly_Init_DLW30.vi)开辟缓冲区长度的时候,一定要设置为1843200Byte的整数倍,比如184.32MByte或者368.64MByte。

看到这里,估计用户清楚了,我们可以将读取长度改成200000,而不是204800,效果就完全不一样了!

8、FPGA VI程序固化(Bit文件)

前面通过LabVIEW将编译成功后的FPGA VI下载到FPGA芯片里面运行,一旦掉电或者按下复位键之后,FPGA里面的程序就没有了,无法实现脱机独立运行。因此我们需要将FPGA编译出来的bit文件通过下载器下载到Xilinx官方PROM或者第三方的Flash或者SD卡里面,这个过程我们称之为“固化”。如果是传统的6系列FPGA,我们可以将bit文件转成mcs文件然后再利用IMPACT.exe下载到Flash里面,但是对于7系列的FPGA来说,Vivado编译工具无法直接生成mcs文件,虽然可以勾选生成bin文件,但是如果没有完整的Vivado工程的话,这个bin文件还是生成不了。

我们把问题简化一下,前面已经获得了原始的按键捕捉bit文件,那么我们就需要想办法抛开Vivado,将bit文件直接转化成bin,mcs或者hex,然后下载固化到Flash里面。类似于把bit文件转成edf网表文件,思路是一样的。

(1)好在Xilinx提供了TCL脚本命令,可以使用write_cfgmem这个命令来强制转换。参考前面的步骤打开Vivado软件,然后在最下方的TCL Console控制台里面输入下面的命令,然后回车,如图61-133所示。其中,SPIx1指的是Flash芯片的通信线数,板载的N25Q128支持X1、X2和X4,X4读写速度最快,默认情况下,LabVIEW FPGA编译出来的bit文件格式里面是x1,所以在写转换指令的时候,必须要选择SPIx1;16是指Flash的大小,单位是MByte,因为黑金AX7103开发板上的Flash容量是128Mbit,相当于16MByte;红色部分是原始的bit文件路径,蓝色的是转换出来的bin文件路径。

注意1:命令里面有个-force,这个是为了强制重写,可以直接覆盖已有的bin文件。由于这串命令很长,用户可以直接复制,然后根据实际情况修改紫色、红色和蓝色部分的字符串就可以了。下面的命令就是把E盘下的原始FPGA bit文件,转换成bin文件,还是存放在E盘下面。
write_cfgmem -force -format bin -interface SPIx1 -size 16 -loadbit “up 0x0 E:/ARTIX7_XC7A100T_PCIe_X4_8Chs_B+AD9280.bit” E:/ARTIX7_XC7A100T_PCIe_X4_8Chs_B+AD9280.bin 在这里插入图片描述
图61-133:在Vivado TCL控制台里面输入write_cfgmem强制转换命令

注意2:如果是Digilent的Basys3开发板,假设我们生成了一个流水灯的bit之后,同样可以用下面的指令来生成bin文件,需要注意的是,Basys3上面的Flash型号是s25fl032,容量是32Mbit,也就是4MByte,所以指令如下:
write_cfgmem -force -format bin -interface SPIx1 -size 4 -loadbit “up 0x0 E:/Basy3_LED.bit” E:/ Basy3_LED.bin

(2)按下回车之后,可以看到控制台打印显示窗口里面的输出结果,如图61-134所示。包括从哪里加载bit文件,转换出来的bin文件路径等基本信息。 在这里插入图片描述
图61-134:Vivado执行完TCL命令之后的输出结果

(3)接下来,我们把bin文件通过Vivado工具下载到AX7103开发板上的Flash里面,这样FPGA上电就会自动从Flash里面加载程序了。这颗Flash芯片容量是128Mbit,相当于16MByte,具体型号是Numonyx公司生产的N25Q128。右击XC7A100T主芯片,选择“Add Configuration Memory Device”,如图61-135所示。然后在弹出来的Flash器件选型页面里面,制造商Manufacture选择Micron美光,容量选择128Mbit,宽度选择x1_x2_x4兼容模式。然后在下拉列表里面选择3.3V电平的n25q128芯片就可以了,如图61-136所示。因为黑金AX7103开发板上的n25q128芯片接的是3.3V电压。图61-137显示的是Digilent Basys3开发板上的Flash芯片固化选型。 在这里插入图片描述
图61-135:添加用于存放FPGA配置文件的Memory器件 在这里插入图片描述
图61-136:筛选出指定的Flash芯片(n25q128)-黑金AX7103开发板 在这里插入图片描述
图61-137:筛选出指定的Flash芯片(s25fl032)-Digilent Basys3开发板

(4)点击OK之后,会弹出提示框,提醒用户是否现在就对这个Flash或者PROM进行编程,也就是烧写bin/mcs/hex文件,如图61-138所示。点击OK之后,弹出添加配置文件窗口,点击旁边的浏览按钮,如图61-139所示,然后选择前面转换出来的bin文件,如图61-140所示。最后点击OK之后,开始下载烧写bin文件,经过擦除、编程、验证3个步骤之后,会弹出提示框,提醒用户程序固化成功了,如图61-141所示。此时,AX7103开发板上的PCIe DMA通信程序并没有加载运行,用户需要重启一下电源,然后FPGA会自动从Flash里面读取刚刚下载的bin文件加载到FPGA芯片内部运行。 在这里插入图片描述
图61-138:是否现在对Memory器件进行编程 在这里插入图片描述
图61-139:点击浏览按钮找到前面转换出来的bin文件 在这里插入图片描述
图61-140:选择一个符合n25q128芯片的bin文件 在这里插入图片描述
在这里插入图片描述
图61-141:bin文件烧写过程与成功后的提醒

(5)此时,Vivado里面的FPGA芯片下面多了一个n25q128配置芯片,如果用户想要再次烧写bin文件进去,可以右击这个芯片,然后选择“Program Configuration Memory Device”即可重新下载固化FPGA VI对应的bin文件,如图61-142所示。 在这里插入图片描述
图61-142:右击Vivado里面的Flash器件可以再次烧写程序

结论:因为Vivado软件本身要比ISE操作起来麻烦一些,很多指令都变成了TCL脚本,考虑到开发过程中的稳定性和易用性,我们建议用户对本书里面讲到的Vivado软件基本操作熟悉一下,除了下载bit和bin文件需要用到Vivado外,其余所有的开发都是在LabVIEW里面进行的,相对来说还是比较简单的。

9、总结
本节实验内容写的非常细致,难度也非常大,因为涉及到很多下位机FPGA PCIe和上位机PC端通信的知识点和内容,用户需要细心学习研究。在学习研究本节实验之前,有些用户是跳着看的,建议大家最好先把本章前面的PCIe基础内容看一遍,否则有些概念都不清楚的话,是很难真正掌握LabVIEW FPGA下的PCIe总线开发的。

本节实验重点需要理解和掌握PCIe总线通信过程、原理;学会我们封装的下位机FPGA PCIe Socket CLIP里面的读写通道的调用及其注意事项;复习前面我们学过的8位ADC芯片AD9280采集线程的编写;掌握控制AD9280采样率的编程方法;学会接收并解析上位机下发的指令和参数;熟练使用FIFO四线握手制编程方式;最后就是学习和掌握我们封装的上位机PCIe DMA通信lvlib库里面的所有函数(VI)的功能和注意事项,做到熟练使用。

相关推荐
LabVIEW Vision图像处理开发宝典... 1 版权声明... 7 读者利益... 7 感谢... 7 前言... 8 第一章 概述... 9 第一节 NI LabVIEW & NI Vision简介... 9 第二节 NI各款视觉软件的应用范围... 14 2.1 LabVIEW的应用... 14 2.2 VDM视觉开发模块的应用... 15 2.3 VA视觉助手的应用... 16 2.4 VBAI视觉生成器的应用... 17 2.5 VAS视觉采集软件的应用... 18 第三节 NI软件的兼容性... 19 3.1 LabVIEW与操作系统兼容性... 19 3.2 LabVIEW的生命周期策略... 20 3.2.1 NI LabVIEW 年度发行... 20 3.2.2 NI LabVIEW生命周期策略的各个阶段... 21 3.2.3 NI LabVIEW生命周期策略... 21 3.3 视觉软件的兼容性... 23 第二章 结构框架... 27 第一节 编程风格... 27 第二节 编程框架... 30 第三节 通用图像处理平台项目结构... 39 第四节 通用图像处理平台流程图... 44 第三章 功能函数与主程序... 47 第一节 功能函数与模块... 47 1.1 Camera相机模块... 49 1.1.1 打开相机... 51 1.1.2 设置属性... 53 对照MAX中的属性设置属性... 56 使用.icd文件查看属性... 58 使用枚举属性函数查看属性... 60 读写相机属性文件... 60 1.1.3 读视频模式... 61 1.1.4 写视频模式... 62 1.1.5 配置相机... 63 1.1.6 采集图像... 64 1.1.7 停止相机... 65 1.1.8 关闭相机... 66 1.1.9 大恒相机DLL调用... 67 函数LvGetDeviceTotal 68 函数LvOpenDevice. 69 函数LvAllocateResources. 71 函数LvSetResolution. 73 函数LvSetSnapMode. 75 函数LvSetGain. 78 函数LvGetImageWidth. 81 函数LvGetImageHeight. 83 函数LvSetAOI 83 函数LvSetShutter. 88 函数LvSetADCLevel 91 函数LvSetTimeout. 94 函数LvGrabSingle. 96 函数LvExportImageData. 101 函数LvStartGrab. 102 函数LvStopGrab. 104 函数LvCloseDevice. 106 函数使用简要说明... 108 1.2 Datalog数据记录... 109 1.2.1 Open打开数据库... 110 1.2.2 Write写数据... 111 1.2.3 Search查询数据... 113 1.2.4 Update更新数据... 114 1.2.5 Delete删除数据... 115 1.2.6 Close关闭数据库... 115 1.3 Sub VI子函数... 116 1.3.1 ImageforUse. 117 Create创建图像缓存... 117 Use使用图像... 118 Copy复制图像... 119 Dispose释放图像缓存... 120 1.3.2 帮助... 120 1.3.3 等待(有错误)... 121 1.3.4 读写参数... 122 1.3.5 读写最后机种... 126 1.3.6 仿真采集图像... 129 1.3.7 放大图像算子... 131 1.3.8 缩小图像算子... 132 1.3.9 检查文件夹存在及新建... 133 1.3.10 良率计算... 134 1.3.11 路径(开发与应用环境)... 135 1.3.12 罗列文件-多文件类型... 137 1.3.13 判断数组大小并截取子集... 138 1.3.14 任一范围随机数... 139 1.3.15 首行行号... 140 1.3.16 提示信息... 143 1.3.17 新建文件... 145 第二节 主程序与功能实现... 149 2.1 主程序前面板说明... 151 2.2 主程序初始化... 153 2.2.1 程序执行时的原始初始化... 154 初始化状态提示... 155 初始化主队列... 156 初始化从队列... 156 建立相对路径... 157 前面板全屏运行... 157 访问网页... 159 2.2.2 队列初始化... 160 元素出队列... 161 状态机-初始化... 162 循环控制及状态机
本宝典由浅入深,详细讲解了如何利用 LabVIEW 对 ARM Cortex-M3 嵌入式系统进行程序开发。宝典 一共分为以下 6 篇内容: 1、软件篇:主要介绍了 NI 公司的 LabVIEW Embedded Module for ARM Microcontrollers 工具包和 Keil 公司(已被 ARM 收购)的 RealView MDK 软件,并详细介绍了配置一个完整的 ARM Cortex-M3 嵌入 式系统开发所需的软件环境及其安装过程。 2、硬件篇:主要介绍了本宝典实验所用到的 3 种主流的开发实验平台(STM32F103 学习板,核心板以 及数采板),并详细介绍了 3 种平台的硬件资源。 3、基础模块篇:主要介绍了 STM32 学习板上的基础硬件电路及其功能模块,并详细讲解了每个模块 的工作原理,为后续的编程实验打下理论基础。 4、高级模块篇:主要介绍了 STM32 学习板上的扩展硬件模块,这些模块涉及的知识点较多,具有一 定的难度,但是有很强的应用价值,因此会着重讲解。 5、基础实验篇,设计了大量的针对基础模块所需的实验案例以帮助大家理解和应用这些功能模块, 详细地讲解了每个实验的应用背景、实现过程以及经验总结与编程技巧。 6、高级实验篇,针对高级模块精心设计了丰富的实验经典案例以供大家练习使用,这部分实验内 容比较多,可以很好地锻炼大家的编程能力,侧重讲解程序的优化。 本宝典配套的光盘中包含了 STM32 学习板开发平台所有相关的原理图以及完整的 LabVIEW 程序源代 码,每个 VI 都有详细的帮助文档,所有实验的程序框图都添加了详细的注释。客户只需要点击 VI 运行 按钮就可以直接将程序下载到开发平台上进行图形化在线调试,无须通过串口等调试工具,因此,可以 极大地简化缩短调试过程,帮助用户提高项目的开发效率。
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页