ZYNQ--PL与PS端的数据交互(PS端编程实现)

        文章ZYNQ从入门到不放弃讲了ZYNQ的PL和PS端是怎么联系起来的。本文将通过数据传输的例子具体地说明这种联系。

        这篇文章的侧重点是讲思路的,讲为什么需要某一个步骤或者某一个东西,以及它和其他步骤的联系是什么,看完以后相信会让你对ZYNQ有更清晰的认识。

总体思路

        首先讲讲这个实验的数据流向:PS端产生数据,存储到内存DDR中,再传输到PL端,在PL端用FIFO缓存一下,再回到PS端的内存,对比两次数据来验证传输过程的正确性。在这个过程中,可以在数据经过PL端的时候进行你想要的处理。

        既然是用PS端编程的方法来实现数据传输,那PL端是不需要写代码的,但需要搭建合适的电路给PS端使用。

       到PS端,我们去控制ARM对内存进行操作,把内存的数据输出去,然后再接收回来。这里除了写C语言代码直接对内存进行操作,也可以使用DMA。DMA是一个对内存进行操作的IP核,你只需要告诉它是哪块内存(地址),数据量,传输方向等信息,它就会自动完成。

        那这个事情就变成了我们告诉DMA要把哪些数据传出去,然后再把外面的数据放回到哪里,最后再把两块内存中的数据进行比较。

PL端是怎么做的

       对于硬件部分要有个这样的概念,ZYNQ中的外设和ARM核通过AXI连接起来,硬件设计完成输出给PS端后。我们不需要去管数据在硬件中是如何流动的,VIVADO会把我们的硬件映射为一个个地址,还会给我们提供很多API去操作它们,就和单片机的使用方法类似。

       根据我们想要实现的逻辑,PL端需要提供给PS端的硬件主要有两个IP核,DMA和FIFO部分,总体的电路如下

     DMA:

       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。

        

     FIFO:

       DMA本身有一定的缓存能力,它把数据从内存读过来,如果直接连接自己输出端,如果处理不及时,超过了自己的缓存能力,可能就会发生错误。FIFO就提供这么一个缓存的功能。

        1:DMA读过来的数据传输给FIFO

        2:FIFO把数据流的数据传回给DMA。我之前疑惑的什么时候FIFO知道把数据还给DMA呢?其实这里的DMA主要起一个控制的作用,它不怎么存数据,接收到数据流后如果内存准备好接收数据了它就传过去。    

        把这两个IP核配置好后,我们点击自动布线,VIVADO就会帮我们添加一些合适的连接性的IP核,比如AXI Intwrconnect ,AXI SmartConnect。这些是不用管的,它负责帮我们配置好每一个总线的地址。这样PS端进行调用的时候就不会出错。

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端的电路,地址的配置,得到的库函数等可能都不一样,所以就把我对于工程中的一些理解讲了讲,希望可以对大家有所帮助。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值