目录
一、前情提要
很久没有给大家更新CSDN了,一方面是由于这一段时间比较忙之外,做东西的方向也是在发生变化,纯的FPGA已经不能够满足项目的需求了。再者是FPGA图形化开发是比较容易上手的,从开发宝典的Pro1到开发宝典的Pro5,思路、代码的结构都比较容易继承,也就是说,用较短的时间去掌握这门图形化语言并对多款FPGA的开发是没有问题的,现在FPGA开发更多关注的是算法移植和数据处理上,往期的文章给大家提供了verliog例化后导入LabVIEW的思路,方便大家做程序的移植,说白了LabVIEW是一个工具,底层的代码都可以用LabVIEW进行顶层调用,相信大家通过之前的文章对FPGA图形化开发都有一个了解。FPGA固然强大,其高速、并行、半定制的特点是其他芯片无法比拟的,在这信息爆炸的时代FPGA不可或缺,但并不是万能的,由于FPGA内部资源有限,对于那些速度要求不高但是消耗FPGA资源多的通信协议、算法,放在ARM里面做再合适不过了,Xilinx 推出了ZYNQ 系列的FPGA,这类FPGA通常是将ARM内核整合在一个芯片中,实现了片上系统(SoC)。Xilinx的ZYNQ7000系列,就是一个芯片内部同时集成了双核Cortex-A8(667MHz)/Cortex-A9(767MHz)的ARM外加一个 ARTIX7 或者 KINTEX7 系列的 FPGA,这样可以非常方便的取代传统的外部DSP+FPGA 或者外部ARM+FPGA的格局,两种芯片优势互补,极大地方便了既需要高速、实时的应用场景,又满足了低速自动化控制、数据处理、传感器数据采集、人机交互的需求,可以为研发人员提供复杂应用场景下的解决方案。本篇文章就以一个实战化的项目为例,给大家讲解一下FPGA图形化开发之ZYNQ篇,希望大家通过本篇文章的学习既加深了对纯FPGA开发的理解又可以学到ZYNQ(PS)端的开发经验,将汝之所学,用到自己的项目和产品中去。
往期系列文章链接:
【LabVIEW FPGA图形化】 ngc、edf网表文件的编写:LED流水灯
【LabVIEW FPGA图形化】 IP集成节点:按键控制LED
【LabVIEW FPGA图形化】IP集成节点:IIC通信实验
二、项目背景
现代战争中,各种武器装备日新月异,战争环境、作战方式和打击手段等也在发生翻天覆地的变化,制导武器凭借其对目标的打击精度高,附带伤害小,弹药消耗少,对目标进行“外科手术”式打击,成为了信息化战争中的主要作战装备。靶场测试可以检验武器装备的各种性能,数据记录模块的主要功能是实时采集、存储炮弹在飞行过程中所产生的数据,比如加速度信号、飞行速度、飞行姿态、弹体转速、抗冲击力以及飞行过程中各项电信号等。数据记录模块相比传统的外弹道测试受到的干扰较小,能够记录到的参数更全面,试验结束后,回收存储装置并将其内部存储的数据传输至电脑进行解析,回放试验飞行过程中的各种参数。数据记录仪具有存储容量大、存储速度快、成本低、集成度高、可靠性强等优点,广泛用于武器试验中。
三、项目方案和实验内容介绍
数据采集和存储模块最核心的两大功能一个是数据采集一个是数据存储,FPGA具有高速、并行的特点,通过外部挂载高速AD实现数据采集、传输。要让数据掉电不丢失,数据需要存储在ROM中,常见的ROM有EEPROM、NOR Flash 、Nand Flash、eMMC等,在高速存储中通常考虑Nand Flash、eMMC,其存储速度快,容量大,但是它的协议较复杂,在FPGA端实现需要消耗大量的逻辑门资源,SoC可以通过系统级调度,硬件接口集成,协议栈实现起来更加的方便,弹载数据采集存储模块系统总体组成框图如下。
弹载传感器输出模拟信号,模块通过AD7606进行数据采集,FPGA作为控制核心负责配置采样通道、采样率等参数。EIO接口作为启动信号的输入(断靶信号或传感器信号)。DMA FIFO是PS和PL之间数据交互的桥梁,FPGA对数据处理并进行格式转换后,使用DMA FIFO通道将数据发送给ARM。ARM有对应的文件管理系统,对数据进行存储,为了保证数据不丢失、同时提高存储速度,数据以二进制的方式进行存储(LabVIEW中体现为TDMS文件,是LabVIEW中速度最快的一种数据存储方式)。
根据系统的总体方案,设计出的弹载传感器数据采集的流程图如下图所示:
由以上流程图可以看到数据采集、存储所需要经历的步骤还是很多的,不仅涉及到驱动文件的加载,还有文件IO的管理,FPGA数据采集和处理的部分。需要说明的是,流程图的左边为PL部分,FPGA在运行各个线程(步骤)之间是并行的逻辑,也就是说在进行数据采集的同时,可以进行数据的采集与发送,各个线程是并行的。流程图右边是Linux RT的运行过程,其他的实验程序框架也类似,首先是等待系统启动,然后动态加载bit文件,动态加载所需的底层驱动文件(KO文件),加载应用层所需的动态链接库文件(SO文件),等到程序运行完成后,逐次关闭、释放资源。ZYNQ的PS端运行的是Linux RT,是一种实时操作系统,在PS开发程序类似于上位机,很多的上位机函数可以直接在ZYNQ的PS端上运行。众所周知,LabVIEW可是嵌入式界的Matlab,功能很齐全,广泛用于数据采集、仪器控制、测试测量和自动化的应用。PS端相当于将工控机小型化和专用化了,程序设计思路很类似。LabVIEW支持多种数据存储格式,如下,TDMS数据完整性和兼容性较好,被广泛应用于波形存储中。
LabVIEW 本地数据文件 (.lvdata): LabVIEW 的原生数据文件格式,可以存储各种数据类型,包括数值、结构、数组等。这种方式适用于 LabVIEW 工程内部的数据传输和存储。
文本文件 (.txt, .csv): LabVIEW 可以将数据存取到文本文件,其中 CSV(逗号分隔值)格式尤其方便数据分析。
数据库 (.mdb, .accdb, .sql): LabVIEW 可以连接和操作多种数据库,例如 MS Access 和 SQL Server,将数据存储和检索。数据库适用于存储大量的数据和进行复杂的数据查询。
图像文件 (.png, .jpg, .gif): LabVIEW 可以将采集到的图像数据保存到多种图像文件格式中。
二进制文件 (.bin): LabVIEW 可以将数据存储到二进制文件,提供一种更高效、更紧凑的数据存储方式。
TDMS文件: TDMS(Time Stamped Data)是 National 为 LabVIEW 设计的一种数据文件格式,集成时间戳信息,适用于存储、分析和处理测量数据。
Excel :可支持生成数据表、报表文件
此外,AXI DMA FIFO是一种高效的数据传输机制,它利用 AXI总线进行异步传输,从而提高数据流效率,减少CPU负荷。AXI DMA FIFO 负责在DMA控制器和外设(FPGA)之间传输数据,能够同时读写数据,极大的方便了PS与PL之间的高速数据交互,本节实验就是基于DMA FIFO的数据交互方式,实现片上数据采集、处理、存储 。
四、硬件电路
弹载数据记录模块通常受限于空间,模块在设计时体积通常是越小越好,并且在设计之初就有尺寸要求,通常数据采集存储模块为圆形,电源板和控制板分开设计,板卡之间通过连接器进行连接,整体的结构可以参考下图:
可以看到,模块的体积相比于下载器还要小一点,数据线缆通过航空插座与外部相连。为了让大家能在通用的开发平台上去验证数据采集和存储实验,我们将项目中数据采集和存储的部分用正点原子领航者的ZYNQ7020开发板去实现,本次实验的硬件平台如下:
正点原子领航者开发板的PS端支持多种启动方式,可通过JTAG、NAND、QSPI和SD Card进行启动,本次实验烧录的镜像就是在SD卡中,因此在上电前需要将拨码开关打到SD卡档位(OFF OFF)。
数据的交互和存储功能都是通过PS和PL端完成的,在ZYNQ芯片的内部,集成度高,也便于模块小型化,外部数据获取无非是外挂一个AD芯片,在实验中也可以用存储器产生波形,作为生产者,模拟读取数据的过程,通过网线可以对PS端进行在线前面板调试,因此大家可以用一块开发板就能完成对本次实验的验证。
五、FPGA图形化程序设计(PL端)
FPGA主要负责数据采集与数据传输,为了简化实验过程,同时测量数据采集存储的极限速度,实验可以采用存储器来模拟ADC采集到的波形,通过设置分频系数,可以调节生产者的带宽,用于模拟ADC不同速率下的数据采集过程。数据的消费者是DMA FIFO通道,在LabVIEW中,工具包将其封装成了子VI的形式,便于调用
DMA FIFO用一个子VI就能够解决PS和PL之间的数据交互问题,在使用DMA的上行或下行通道时,直接在函数选板里面拖出来,极大地方便用户的使用,双击可以进入子VI中,其程序框图如下
可以看到,子VI的程序框图并不复杂,无非是数据的拆分与拼接,其底层主要还是靠CLIP来实现对应的功能。
在帮助界面,可以看到子VI的调用也十分简单,就只有FIFO名称作为入口参数
因此,在实现相应功能时有两种方式,一种是子VI的方式,另一种是展开显示的方式(复制子VI到主函数中),显然,第一种方式比较方便,本文以子VI的方式为例,展开说明PL_DMA函数选板里的函数。首先从函数名可以看出数据传输的方向——PS2PL是下行,PL2PS是上行,字长是32,一包数据的发送长度取决于后面的字节。需要注意的是,下位机发送数据长度要与上位机接收数据长度一致,否则会导致PS端进程崩溃。
在介绍完工具包新增的函数选板后,接下来根据流程图对程序进行编写,PL端的程序框图如下图所示
可以看到,PL端的程序由三个线程组成,分别是数据产生线程、数据转移线程、数据发送线程,数据以数据流的方式转移,在生产者中,模拟AD7606数据采集,其数据位宽为16bit,在DMA FIFO通道中,数据是以32bit发送,在带宽足够的情况下,用户可以强制类型转换,以32bit发送,带来的问题是带宽利用率的降低,本实验通过数据转移线程将两个采样点组合成32bit数据进行发送,可以充分地利用带宽,数据发送线程是将数据通过FIFO转移到Ps_System_PWM Data\Clock lv_FCLK_CLK0_PS2PL
这个由PS端产生的时钟域下,保证数据上行的正常传输。
六、Linux RT图形化程序的编写(PS端)
Linux RT具有实时调度、时延固定、高可靠和高稳定性、开源和可定制的特点,适用于那些对时钟精度、任务调度和响应时间要求极高的应用,可以满足设备实时响应的需求。由于弹载数据采集存储模块工作条件较为恶劣,电磁干扰、电压波动都有可能造成采集过程中模块掉电、数据丢失。因此,采集到的数据需要实时写入Flash中。在PS端运行Linux RT,相当于将LabVIEW移植到了嵌入式的处理平台上,原本在工控机(计算机)上实现的功能都可以在ZYNQ上完成,节省了项目的成本,同时做到了体积小型化,比传统工控机具有更好的实时性和专用性,这些都归功于labVIEW的功劳。通常情况下,上位机通过前面板进行人机交互,控制、数据显示、存储。以往的数据采集案例,如同步数采板卡,通常只用纯FPGA和千兆以太网的方案,将数据上传至工控机,由工控机进行数据的显示、存储、解析。有了ZYNQ芯片,我们可以将上位机的操作移植到PS端,通过LabVIEW中的文件IO(TDMS函数选板)进行存储,因此,其开发与上位机程序开发有异曲同工之妙,根据程序流程图,我们设计得到的PS程序框图如下图所示(矢量图可以放大看)。
PS端程序可以用平铺式顺序结构来保证程序的逐步执行,首先是动态加载FPGA bit文件,只要文件在Alway include列表中,程序运行时会从库中找到对应bit文件并下载至PL里,接着是一系列的程序初始化过程,程序初始化完成后,进入while循环,持续不断的查询DMA缓冲区中元素的数量,大于一包数据后,调用读取的子VI,读取数据,上位机拆分数据可以直接使用类型转换(Type cast)函数转成U16,进行波形显示、存储。
七、实验测试与验证
找到对应的IP地址 连接ZYNQ ,连接成功后工程上的ZYNQ终端的小灯泡会点亮,运行程序,前面板就可以观察到实验的现象,Running表示程序一直在运行,点击数据发送前面板开关,PS端就能成功接收到数据并在波形图标上显示出来。PS和PL端的数据打通了。
为了观察TDMS文件是否能写入到指定路径下(/home/lvuser/natinst/bin/data),可以利用调试软件MobaXterm去观察文件夹里面的文件及其大小。用SSH连接ZYNQ(一个系统可以支持多个连接请求),登录后可以看到文件夹中多出本次新建TDMS文件,文件大小会随着数据存储量的增大而变大。
为了验证数据写入的有效性,我们需要对TDMS文件的数据进行读取,可以直接在ZYNQ中调用TDMS读函数进行数据读取,也可以拖到电脑中(上位机)读取,MobaXterm可以直接拷贝拖拽文件,方便起见,直接将其拖到桌面上在上位机打开TDMS文件,查看波形。读取到的波形数据如下图所示
由上图可以看到,数据是一个很有规律的斜坡信号说明数据能够完整无误地从PL端传输并写入到PS端。为了验证系统的带宽,我们可以在PS端加入一些与测速有关的函数,计算单位时间内传输的数据量。
在前面板上观察测速结果,可以看到系统的写入速度在40,000,000bit/s,换算成字节约为4.7MB/s,这个速度对于AD7606来说,已经是绰绰有余,AD7606的采样率最高为200k,8通道同时采样则所需的带宽为 200k x 16bit x 8 = 3.2MB/s ,AD所需的带宽小于系统的写入带宽 , 完全能够胜任数据采集和存储的任务。
八、AD7606数据采集 FPGA图形化程序设计
上面的实验采用了存储器来模拟AD采集到的数据,如果用户是自己设计板子,或者有AD7606数据采集模块的,也可以用AD7606模块作为生产者线程去传输数据,其原理都是一样的。只不过该AD模块读取数据需要按照一定的时序进行,FPGA图形化的程序框图可参考下图
AD7606采集到的数据依次转移到相应的FIFO中,后续通过数据转移线程很轻松地就能实现数据的发送。黑金也提供了AD7606的verliog源代码,综合后生成的网表文件可以直接用于LabVIEW中。
`timescale 1ns / 1ps
//
// Module Name: ad7606
//
module ad7606(
input clk, //50mhz
input rst_n,
input [15:0] ad_data, //ad7606 采样数据
input ad_busy, //ad7606 忙标志位
input first_data, //ad7606 第一个数据标志位
output [2:0] ad_os, //ad7606 过采样倍率选择
output reg ad_cs, //ad7606 AD cs
output reg ad_rd, //ad7606 AD data read
output reg ad_reset, //ad7606 AD reset
output reg ad_convstab, //ad7606 AD convert start
output reg [15:0] ad_ch1, //AD第1通道的数据
output reg [15:0] ad_ch2, //AD第2通道的数据
output reg [15:0] ad_ch3, //AD第3通道的数据
output reg [15:0] ad_ch4, //AD第4通道的数据
output reg [15:0] ad_ch5, //AD第5通道的数据
output reg [15:0] ad_ch6, //AD第6通道的数据
output reg [15:0] ad_ch7, //AD第7通道的数据
output reg [15:0] ad_ch8 //AD第8通道的数据
);
reg [15:0] cnt;
reg [5:0] i;
reg [3:0] state;
parameter IDLE=4'd0;
parameter AD_CONV=4'd1;
parameter Wait_1=4'd2;
parameter Wait_busy=4'd3;
parameter READ_CH1=4'd4;
parameter READ_CH2=4'd5;
parameter READ_CH3=4'd6;
parameter READ_CH4=4'd7;
parameter READ_CH5=4'd8;
parameter READ_CH6=4'd9;
parameter READ_CH7=4'd10;
parameter READ_CH8=4'd11;
parameter READ_DONE=4'd12;
assign ad_os=3'b000;
//AD 复位电路
always@(posedge clk)
begin
if(cnt<16'hffff) begin
cnt<=cnt+1;
ad_reset<=1'b1;
end
else
ad_reset<=1'b0;
end
always @(posedge clk)
begin
if (ad_reset==1'b1) begin
state<=IDLE;
ad_ch1<=0;
ad_ch2<=0;
ad_ch3<=0;
ad_ch4<=0;
ad_ch5<=0;
ad_ch6<=0;
ad_ch7<=0;
ad_ch8<=0;
ad_cs<=1'b1;
ad_rd<=1'b1;
ad_convstab<=1'b1;
i<=0;
end
else begin
case(state)
IDLE: begin
ad_cs<=1'b1;
ad_rd<=1'b1;
ad_convstab<=1'b1;
if(i==20) begin
i<=0;
state<=AD_CONV;
end
else
i<=i+1'b1;
end
AD_CONV: begin
if(i==2) begin //等待2个clock
i<=0;
state<=Wait_1;
ad_convstab<=1'b1;
end
else begin
i<=i+1'b1;
ad_convstab<=1'b0; //启动AD转换
end
end
Wait_1: begin
if(i==5) begin //等待5个clock, 等待busy信号为高
i<=0;
state<=Wait_busy;
end
else
i<=i+1'b1;
end
Wait_busy: begin
if(ad_busy==1'b0) begin //等待busy信号为低
i<=0;
state<=READ_CH1;
end
end
READ_CH1: begin
ad_cs<=1'b0; //cs信号有效
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch1<=ad_data; //读CH1
state<=READ_CH2;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH2: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch2<=ad_data; //读CH2
state<=READ_CH3;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH3: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch3<=ad_data; //读CH3
state<=READ_CH4;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH4: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch4<=ad_data; //读CH4
state<=READ_CH5;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH5: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch5<=ad_data; //读CH5
state<=READ_CH6;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH6: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch6<=ad_data; //读CH6
state<=READ_CH7;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH7: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch7<=ad_data; //读CH7
state<=READ_CH8;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_CH8: begin
if(i==3) begin
ad_rd<=1'b1;
i<=0;
ad_ch8<=ad_data; //读CH8
state<=READ_DONE;
end
else begin
ad_rd<=1'b0;
i<=i+1'b1;
end
end
READ_DONE:begin
ad_rd<=1'b1;
ad_cs<=1'b1;
state<=IDLE;
end
default: state<=IDLE;
endcase
end
end
endmodule
AD7606线程只需要替换之前的生产者线程就可以,本实验不再赘述。
200kSPS的AD7606所需的带宽并不大,DMA FIFO以及PS端的速率能够满足数据的存储需求,如果采用AD7606C以更高速率进行采样,只能实现单通道的数据无损传输。
九、总结
ZYNQ芯片集成度高,其SoC功能强大,结合了FPGA和ARM的优点,只需要1个芯片就可以实现数据的采集与存储,性价比高。此外,数据采集存储模块还可以拓展到其他的应用场合,如工业控制和故障检测等应用场景。
FPGA图形化编程其优势在于逻辑图形化,一个软件就可以做到上下位机的统一。
通过本次实验,可以看到PS与PL端的数据交互速度在5MB/s左右,如果是对AD7606C来说,写入速度还是慢了一点,而在一些瞬态信号测量的应用场合,又不得不用到高速AD,因此在更高速度下的数据采集和存储是很有必要的。
究其原因,本次测试是将Linux RT烧录在SD卡中,通过SD卡启动,TDMS文件也是写入SD卡中,虽说ARM端能以较快的时钟频率,通过SDIO对SD卡进行读写,非常容易出现速度瓶颈,因此,如果用户需要以更高的速率进行采集存储,建议将Linux RT镜像烧录在NAND Flash中,系统从Nand启动,可获得更快的传输速率,感兴趣的读者可以试一试。本节实验的Verliog网表文件和LabVIEW的vi可以联系作者(QQ:395494645,请注明来源)获取。