赞
踩
文章ZYNQ从入门到不放弃讲了ZYNQ的PL和PS端是怎么联系起来的。本文将通过数据传输的例子具体地说明这种联系。
这篇文章的侧重点是讲思路的,讲为什么需要某一个步骤或者某一个东西,以及它和其他步骤的联系是什么,看完以后相信会让你对ZYNQ有更清晰的认识。
首先讲讲这个实验的数据流向:PS端产生数据,存储到内存DDR中,再传输到PL端,在PL端用FIFO缓存一下,再回到PS端的内存,对比两次数据来验证传输过程的正确性。在这个过程中,可以在数据经过PL端的时候进行你想要的处理。
既然是用PS端编程的方法来实现数据传输,那PL端是不需要写代码的,但需要搭建合适的电路给PS端使用。
到PS端,我们去控制ARM对内存进行操作,把内存的数据输出去,然后再接收回来。这里除了写C语言代码直接对内存进行操作,也可以使用DMA。DMA是一个对内存进行操作的IP核,你只需要告诉它是哪块内存(地址),数据量,传输方向等信息,它就会自动完成。
那这个事情就变成了我们告诉DMA要把哪些数据传出去,然后再把外面的数据放回到哪里,最后再把两块内存中的数据进行比较。
对于硬件部分要有个这样的概念,ZYNQ中的外设和ARM核通过AXI连接起来,硬件设计完成输出给PS端后。我们不需要去管数据在硬件中是如何流动的,VIVADO会把我们的硬件映射为一个个地址,还会给我们提供很多API去操作它们,就和单片机的使用方法类似。
根据我们想要实现的逻辑,PL端需要提供给PS端的硬件主要有两个IP核,DMA和FIFO部分,总体的电路如下
DMA(Direct memory Access) 是一种允许某些硬件子系统独立于主CPU向系统内存读写数据的技术,它可以减轻CPU的负担,提高数据的传输效率,广泛应用于高速数据传输任务中,如网络通信、图形渲染、外设与内存之间的数据传输等。来看看它的主要信号
1:主要用于配置、管理和监控DMA。
2:数据通路,后缀是MM2S是指内存传输到数据流(可以简单理解为PL端),S2MM是数据流传输到内存。注意这里和PS连接的是HP口。(一般配置总线用GP,数据总线用HP,因为数据量较大)。关于这里,我之前有个疑惑PS端用了一个端口和DMA两个端口连接,查资料的时候说系统会给DMA端的两条总线分配不同的地址范围,要使用的时候可以因此进行区分。
3:中断信号:这两个线通过IP核Concat和PS端的中断信号连着了一起,当DMA完成了相应的传输任务,或者没有完成(传输失败等),都会产生中断信号告知PS。
DMA本身有一定的缓存能力,它把数据从内存读过来,如果直接连接自己输出端,如果处理不及时,超过了自己的缓存能力,可能就会发生错误。FIFO就提供这么一个缓存的功能。
1:DMA读过来的数据传输给FIFO
2:FIFO把数据流的数据传回给DMA。我之前疑惑的什么时候FIFO知道把数据还给DMA呢?其实这里的DMA主要起一个控制的作用,它不怎么存数据,接收到数据流后如果内存准备好接收数据了它就传过去。
把这两个IP核配置好后,我们点击自动布线,VIVADO就会帮我们添加一些合适的连接性的IP核,比如AXI Intwrconnect ,AXI SmartConnect。这些是不用管的,它负责帮我们配置好每一个总线的地址。这样PS端进行调用的时候就不会出错。
PS端最主要的工作就是对DMA的控制,让它来帮我们完成内存到外部的数据传输。那么在使用它之前,肯定得进行初始化,配置等操作。当DMA完成任务后它会发送中断给CPU,所以我们还需要使用中断系统,所以也需要对中断系统进行初始化,配置等工作。这就是整体代码的逻辑。这里没有涉及到FIFO这个外设,因为FIFO和PS端不是直接接触的,我们只需要和DMA接触就好啦。
依据这个逻辑,我们的main函数的代码如下,可以直接看最后的部分:
- #include "dma_intr.h"
- #include "sys_intr.h"
-
-
- static XScuGic Intc; //GIC
- static XAxiDma AxiDma;
-
- volatile u32 success;
-
-
- int Tries = NUMBER_OF_TRANSFERS;
- int i;
- int Index;
- u8 *TxBufferPtr= (u8 *)TX_BUFFER_BASE;
- u8 *RxBufferPtr=(u8 *)RX_BUFFER_BASE;
- u8 Value;
-
-
- int axi_dma_test()
- {
- int Status;
- TxDone = 0;
- RxDone = 0;
- Error = 0;
- //给内存中写一些数据进去
- for(i = 0; i < Tries; i ++)
- {
- Value = TEST_START_VALUE + (i & 0xFF);
- for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
- TxBufferPtr[Index] = Value;
-
- Value = (Value + 1) & 0xFF;
- }
-
- /* Flush the SrcBuffer before the DMA transfer, in case the Data Cache
- * is enabled
- */
- Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN);
-
- //这里开始使用DMA的API了,给了它地址,数据大小,传输方向它就开始工作了
- Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) RxBufferPtr,
- MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
-
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
-
- Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr,
- MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
-
- if (Status != XST_SUCCESS) {
- return XST_FAILURE;
- }
-
- /*
- * Wait TX done and RX done
- */
- while (!TxDone || !RxDone) {
- /* NOP */
- }
-
- success++;
- TxDone = 0;
- RxDone = 0;
-
- if (Error) {
- xil_printf("Failed test transmit%s done, "
- "receive%s done\r\n", TxDone? "":" not",
- RxDone? "":" not");
- goto Done;
- }
- /*
- * 数据传输完成后,将两次数据进行对比
- */
- Status = DMA_CheckData(MAX_PKT_LEN, (TEST_START_VALUE + (i & 0xFF)));
- if (Status != XST_SUCCESS) {
- xil_printf("Data check failed\r\n");
- goto Done;
- }
-
- }
- xil_printf("AXI DMA interrupt example test passed\r\n");
- xil_printf("success=%d\r\n",success);
- /* Disable TX and RX Ring interrupts and return success */
- DMA_DisableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID);
- Done:
- xil_printf("--- Exiting Test --- \r\n");
- return XST_SUCCESS;
-
- }
-
- int init_intr_sys(void)
- {
- DMA_Intr_Init(&AxiDma,0);//DMA的初始化
- Init_Intr_System(&Intc); // 中断系统的初始化话
- Setup_Intr_Exception(&Intc); //异常的初始化设置
- DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//把DMA和中断连接起来
- DMA_Intr_Enable(&Intc,&AxiDma);//使能DMA的中断
- }
-
- int main(void)
- {
-
- init_intr_sys(); //DMA和中断的初始化
- axi_dma_test(); //使用DMA进行数据传输工作
-
- }
主函数只说明了总体的逻辑,这里再讲一讲DMA初始化的细节。可以看到这里调用的是DMA_Intr_Init(&AxiDma,0)这个函数去对DMA进行初始化,打开这个函数
- int DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId)
- {
- int Status;
- XAxiDma_Config *Config=NULL;
-
- Config = XAxiDma_LookupConfig(DeviceId);
- if (!Config) {
- xil_printf("No config found for %d\r\n", DeviceId);
- return XST_FAILURE;
- }
-
- /* Initialize DMA engine */
- Status = XAxiDma_CfgInitialize(DMAPtr, Config);
-
- if (Status != XST_SUCCESS) {
- xil_printf("Initialization failed %d\r\n", Status);
- return XST_FAILURE;
- }
-
- if(XAxiDma_HasSg(DMAPtr)){
- xil_printf("Device configured as SG mode \r\n");
- return XST_FAILURE;
- }
- return XST_SUCCESS;
-
- }
开头引用的那篇文章说到过,PS端对外设调用的一般套路,我们看这里也是一样的。
函数的两个参数,一个是DMA的结构体实例的指针,还有一个是设备的ID。首先调用了Config = XAxiDma_LookupConfig(DeviceId);通过这个函数找找我们生成的硬件里面有没有DMA这个外设,有的话把它的地址搞出来,XAxiDma_CfgInitialize(DMAPtr, Config);这个函数可以理解为把我们要用的实例和设备的DMA硬件连接起来了。后面再调用DMA的API的时候,带着这个实例作为输入参数去进行操作就行了。可以看主函数里面的 XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr, MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);就是这么做的。
后面的中断也是一个套路,这里就不再细讲了。
在PL端(硬件电路端)搭建好合适的电路,在PS端(软件端)可以向操控单片机一样来进行想要的处理。
本来想把复现也写一下,但每个人的板子,映射到PS端的电路,地址的配置,得到的库函数等可能都不一样,所以就把我对于工程中的一些理解讲了讲,希望可以对大家有所帮助。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。