我们上一节谈到使用 DMA(直接内存访问)的好处已经变得显而易见。到了这一步,我们留下了人类长期以来一直在思考的问题:DMA到底是什么?
在最基本的层面上,一旦处理器设置了传输,DMA 将数据传入或传出内存而无需CPU干预。DMA 可以显着提高系统性能,具体取决于所采用的方法。在我们更详细地了解 Zynq DMA 之前,我想先解释一些 DMA 控制器的通用原理。通常 DMA 控制器以以下三种模式之一运行:
DMA 控制器的一项非常有用的功能是支持分散/收集(scatter/gather)操作的能力。此功能允许将多个数据源传输到单个目标地址或允许单个源地址提供多个输出目标(也称为“缓冲区”)。Zynq SoC 的基于 ARM 的处理系统 (PS) 有一个 DMA 控制器 (DMAC),它连接到 Zynq 的 AXI4 互连并使用 AXI 总线执行传输。DMAC 在系统存储器和 Zynq 的可编程逻辑 (PL) 之间采用 64 位 AXI 传输。如下所示,Zynq DMAC 有 8 个通道,允许 DMAC 同时执行 8 个 DMA 线程,并通过 AXI 互连实现流控制。
虽然 Zynq DMAC 允许在系统存储器和 PL(包括 PL 中的 Zynq 外设)之间进行双向传输,但它不支持 Zynq PS 中的外设的 DMA,因为这些外设没有流控制信号来支持 DMA 操作。然而,Zynq SoC 中的一些 IO 外设具有自己的 DMA 控制器,以支持进出 IOP 和系统内存的高数据速率传输。这些外围设备是:
如果设备使用 ARM TrustZone,Zynq SoC 还支持安全寄存器访问。Xilinx 同时也提供了一个简单的驱动程序文件 (xdmaps.h),我们可以在独立 BSP 中使用它来配置和启动 DMA 传输。在下一节中,我们将了解如何使用此文件创建简单的 DMA 传输。
本节创建一个非常简单的示例来演示如何设置和使用 DMA。
为了演示这示例,将使用一个 DMA 控制器通道将一个内存位置传输到另一个内存位置。
首先需要在BSP中包含一部分Vivado中生成的头文件。这些头文件提供了我们可以用来驱动 DMA 的宏和函数。对于这个例子,我们需要包括:
#include "xscugic.h"
#include "xdmaps.h"
#include "xil_exception.h"
Xscugic.h 和 xil_exceptions.h 允许使用中断控制器,而 xdmaps.h 允许配置并使用DMA。
使用 xparamters.h 提供的参数,我们可以定义 DMA 和中断控制器的设备标识、将使用的中断以及我们将传输的数据长度:
#define DMA_DEVICE_ID XPAR_XDMAPS_1_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define DMA_FAULT_INTR XPAR_XDMAPS_0_FAULT_INTR
#define DMA_DONE_INTR_0 XPAR_XDMAPS_0_DONE_INTR_0
#define DMA_LENGTH 1024
开发的下一阶段是编写三个函数来配置DMA,配置中断控制器,并在DMA传输完成时充当中断服务程序。
在 DMA 配置函数中,我们首先使用 xdmaps.h 提供的命令结构创建一个 DMA 命令。DMA 命令由通道控制、块描述符、用户定义的程序、指向生成的程序的指针和传输结果组成。由于这是一个简单的示例,我们不需要所有这些组件,但是我们将配置 DMA 控制器,如下所示:
DmaCmd.ChanCtrl.SrcBurstSize = 4;
DmaCmd.ChanCtrl.SrcBurstLen = 4;
DmaCmd.ChanCtrl.SrcInc = 1;
DmaCmd.ChanCtrl.DstBurstSize = 4;
DmaCmd.ChanCtrl.DstBurstLen = 4;
DmaCmd.ChanCtrl.DstInc = 1;
DmaCmd.BD.SrcAddr = (u32) Src;
DmaCmd.BD.DstAddr = (u32) Dst;
DmaCmd.BD.Length = DMA_LENGTH * sizeof(int);
下一步设置运行中断函数以将 DMA 中断连接到中断控制器之前初始化和配置 DMA 控制器:
DmaCfg = XDmaPs_LookupConfig(DeviceId);
XDmaPs_CfgInitialize(DmaInst,DmaCfg,DmaCfg->BaseAddress);
SetupInterrupt(&GicInstance, DmaInst);
在此之后,在我们连接完成处理程序并开始传输之前,源内存位置被设置并清除目标位置,为了跟踪进度,我们还调用了 DMA 进度函数:
DmaCfg = XDmaPs_LookupConfig(DeviceId);
XDmaPs_CfgInitialize(DmaInst,DmaCfg,DmaCfg->BaseAddress);
SetupInterrupt(&GicInstance, DmaInst);
XDmaPs_Print_DmaProg(&DmaCmd);
当附加的源代码文件在ZYNQ上运行时,以下结果显示串口输出上。
源码:
https://gitee.com/openfpga/zynq-chronicles/blob/master/part_29.c