13 - AXI DMA环路实验
文章目录
- 1 实验任务
- 2 系统框图
- 3 硬件设计
- 4 软件设计
1 实验任务
本实验任务是使用PL端的AXI DMA IP核从从PS端的DDR3中读取数据,并将数
据写回到PS端的DDR3中。
2 系统框图
3 硬件设计
- 添加ZYNQ7 Processing System IP核
- 在PS-PL Configuration页面
- 勾选FCLK_RESET0_N
- 勾选M AXI GP0 interface
- 勾选S AXI HP0 interface
- 在Peripheral I/O页面
- 勾选UART
- 在Clock Configuration页面
- 勾选FCLK_CLK0
- 勾选FCLK_CLK1
- 在Interrupts页面
- 勾选IRQ_F2P[15:0]
- 在PS-PL Configuration页面
- 添加AXI Direct Memory Access IP核
- 添加AXI4-Stream Data FIFO IP核
- 添加Clocking Wizard IP核
- 添加Concat IP核
- 添加System ILA IP核
- 自动连线
注意:
- 本设计共有4个时钟,PS和外部晶振各提供两个时钟
- PS FCLK_CLK0:50MHz,为配置接口M_AXI_GP0提供时钟
- PS FCLK_CLK1:100MHz,为数据接口S_AXI_GP0提供时钟
- 晶振clk_out1:120MHz,为MM2S数据流提供时钟(AXI DMA读-下行)
- 晶振clk_out2:150MHz,为S2MM数据流提供时钟(AXI DMA写-上行)
- 在使用自动连线时,一定要头脑清楚,正确设置Master Interface、Slave Interface和AXI Connect的时钟。
4 软件设计
注意事项:
- 缓存一致性问题:在进行PS-PL之间的DMA传输时,不可避免会遇到Cache问题
- 产生原因:
- PS访问DDR时,为了提高访问速度,通常会将数据缓存Data Cache中
- 好处:加快下一次访问速度
- 坏处:Data Cache中的数据与DDR中的数据不一致
- 解决办法
- 方法1:使用Xil_DCacheDisable函数禁用Cache(简单粗暴)
- 方法2:使用Xil_DCacheFlushRange函数和Xil_DCacheInvalidateRange函数
- 在AXIDMA发送前,使用Xil_DCacheFlushRange函数,将Data Cache中缓存的数据强制写回到DDR中去
- 在AXIDMA接收后,使用Xil_DCacheInvalidateRange,使Data Cache中缓存的数据无效,直接从DDR中读取数据
- 产生原因:
/***************************** Include Files *********************************/
#include "stdio.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"
#include "xstatus.h"
#include "xaxidma.h"
/************************** Constant Definitions *****************************/
#define AXIDMA_DEVICE_ID XPAR_AXIDMA_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define AXIDMA_RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define AXIDMA_TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x01000000) //0x01100000
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //0x01200000
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000
#define RESET_TIMEOUT_COUNTER 10000 //复位时间
#define TEST_START_VALUE 0x0 //测试起始值
#define AXIDMA_LENGTH 0x2000 //DMA长度 = 8192Byte
/************************** Function Prototypes ******************************/
int AxiDmaSetup(XAxiDma* AxiDmaInstPtr);
int SetupIntrSystem(XScuGic* IntcInstPtr, XAxiDma* AxiDmaInstPtr, u16 AxiDmaTxIntrId, u16 AxiDmaRxIntrId);
void AxiDmaTxIntrHandler(void* Callback);
void AxiDmaRxIntrHandler(void* Callback);
int CheckData(u32* RxBuffer);
/************************** Variable Definitions *****************************/
XAxiDma AxiDmaInst; //XAxiDma实例
XScuGic IntcInst; //中断控制器的实例
int AxiDmaTxDone; //发送完成标志
int AxiDmaRxDone; //接收完成标志
int AxiDmaTxError; //发送出错标志
int AxiDmaRxError; //接收出错标志
/************************** Function Definitions *****************************/
int main(void)
{
//
int Status;
u32 TestValue;
u32* AxiDmaTxBufferPtr;
u32* AxiDmaRxBufferPtr;
//
AxiDmaTxBufferPtr = (u32 *) TX_BUFFER_BASE;
AxiDmaRxBufferPtr = (u32 *) RX_BUFFER_BASE;
//
printf("AXIDMA Read and Write Loop Test.\n");
// 设置AXIDMA
Status = AxiDmaSetup(&AxiDmaInst);
if (Status != XST_SUCCESS) {
printf("AXIDMA Setup Failed\n");
return XST_FAILURE;
}
// 设置中断系统
Status = SetupIntrSystem(&IntcInst, &AxiDmaInst, AXIDMA_TX_INTR_ID, AXIDMA_RX_INTR_ID);
if (Status != XST_SUCCESS) {
printf("AXIDMA Setup Interrupt System Failed");
return XST_FAILURE;
}
// 初始化标志信号
AxiDmaTxDone = 0;
AxiDmaRxDone = 0;
AxiDmaTxError = 0;
AxiDmaRxError = 0;
// 注意:在嵌入式系统中,程序直接运行在硬件上,内存地址由硬件设计确定,开发者可以直接访问这些地址,而不需要动态申请内存
TestValue = TEST_START_VALUE;
for (int i = 0; i < AXIDMA_LENGTH / sizeof(u32); i++) {
AxiDmaTxBufferPtr[i] = TestValue;
TestValue++;
}
// 在启动AXIDMA发送之前,将Data Cache中缓存的数据强制写回到DDR中去
Xil_DCacheFlushRange((INTPTR)AxiDmaTxBufferPtr, AXIDMA_LENGTH);
//
char ch;
while(1)
{
//
printf("Enter 'T' to Start AXIDMA TX :>\n");
scanf(" %c", &ch);
if (ch == 'T') {
// AXIDMA读操作:DDR -> FPGA
Status = XAxiDma_SimpleTransfer(&AxiDmaInst, (UINTPTR)AxiDmaTxBufferPtr, AXIDMA_LENGTH, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
printf("AXIDMA SimpleTransfer TX Failed\n");
return XST_FAILURE;
}
//
while(!AxiDmaTxDone && !AxiDmaTxError);
if (AxiDmaTxDone == 1) {
printf("AXIDMA TX Done\n");
AxiDmaTxDone = 0;
}
else if (AxiDmaTxError == 1) {
printf("AXIDMA TX Error\n");
AxiDmaTxError = 0;
}
}
else {
break;
}
//
printf("Enter 'R' to Start AXIDMA RX :>\n");
scanf(" %c", &ch);
if (ch == 'R') {
//
Status = XAxiDma_SimpleTransfer(&AxiDmaInst, (UINTPTR)AxiDmaRxBufferPtr, AXIDMA_LENGTH, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
printf("AXIDMA SimpleTransfer RX Failed\n");
return XST_FAILURE;
}
//
while(!AxiDmaRxDone && !AxiDmaRxError);
if (AxiDmaRxDone == 1) {
printf("AXIDMA RX Done\n");
AxiDmaRxDone = 0;
}
else if (AxiDmaRxError == 1) {
printf("AXIDMA RX Error\n");
AxiDmaRxError = 0;
}
// 在启动AXIDMA接收之后,使Data Cache中缓存的数据无效,直接从DDR中读取数据
Xil_DCacheInvalidateRange((INTPTR)AxiDmaRxBufferPtr, AXIDMA_LENGTH);
// 检查数据是否正确
Status = CheckData(AxiDmaRxBufferPtr);
if (Status != XST_SUCCESS) {
printf("AXIDMA RX Data Check Failed\n");
return XST_FAILURE;
}
}
else {
break;
}
}
//
return XST_SUCCESS;
}
// 初始化AXIDMA
int AxiDmaSetup(XAxiDma* AxiDmaInstPtr)
{
//
int Status;
XAxiDma_Config* AxiDmaConfigPtr;
// 初始化AXIDMA
AxiDmaConfigPtr = XAxiDma_LookupConfig(AXIDMA_DEVICE_ID);
if (AxiDmaConfigPtr == NULL) {
printf("AXIDMA LookupConfig Failed\n");
return XST_FAILURE;
}
Status = XAxiDma_CfgInitialize(AxiDmaInstPtr, AxiDmaConfigPtr);
if (Status != XST_SUCCESS) {
printf("AXIDMA CfgInitialize Failed\n");
return XST_FAILURE;
}
// 判断AXIDMA模式:S/G或Simple DMA
if (XAxiDma_HasSg(AxiDmaInstPtr)) {
printf("AXIDMA Configured as SG Mode\n");
return XST_FAILURE;
}
// 使能AXIDMA中断
XAxiDma_IntrEnable(AxiDmaInstPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(AxiDmaInstPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
//
return XST_SUCCESS;
}
// 设置AXIDMA中断系统
int SetupIntrSystem(XScuGic* IntcInstPtr, XAxiDma* AxiDmaInstPtr, u16 AxiDmaTxIntrId, u16 AxiDmaRxIntrId)
{
//
int Status;
XScuGic_Config* IntcConfigPtr;
// step1. 初始化中断控制器GIC
IntcConfigPtr = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (IntcConfigPtr == NULL) {
printf("INTC LookupConfig Failed\n");
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(IntcInstPtr, IntcConfigPtr, IntcConfigPtr->CpuBaseAddress);
if (Status != XST_SUCCESS) {
printf("INTC CfgInitialize Failed\n");
return XST_FAILURE;
}
// step2. 在处理器中初始化异常处理功能
Xil_ExceptionInit();
// step3. 在处理器中为IRQ中断异常注册处理程序
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler,(void *) IntcInstPtr);
// step4. 在处理器中使能IRQ中断异常
Xil_ExceptionEnable();
// step5. 在GIC中为外设注册中断处理程序
Status = XScuGic_Connect(IntcInstPtr, AxiDmaTxIntrId, (Xil_InterruptHandler) AxiDmaTxIntrHandler, AxiDmaInstPtr);
if (Status != XST_SUCCESS) {
printf("AXIDMA TX Interrupt Connect Failed\n");
return XST_FAILURE;
}
// step5. 在GIC中为外设注册中断处理程序
Status = XScuGic_Connect(IntcInstPtr, AxiDmaRxIntrId, (Xil_InterruptHandler) AxiDmaRxIntrHandler, AxiDmaInstPtr);
if (Status != XST_SUCCESS) {
printf("AXIDMA RX Interrupt Connect Failed\n");
return XST_FAILURE;
}
// step6. 在GIC中设置外设中断的优先级和触发类型
XScuGic_SetPriorityTriggerType(IntcInstPtr, AxiDmaTxIntrId, 0xA0, 0x3); // Rising Edge
XScuGic_SetPriorityTriggerType(IntcInstPtr, AxiDmaRxIntrId, 0xA0, 0x3); // Rising Edge
// step7. 在GIC中使能外设中断
XScuGic_Enable(IntcInstPtr, AxiDmaTxIntrId);
XScuGic_Enable(IntcInstPtr, AxiDmaRxIntrId);
//
return XST_SUCCESS;
}
// AXIDMA发送中断处理函数
void AxiDmaTxIntrHandler(void *Callback)
{
//
XAxiDma* AxiDmaInstPtr = (XAxiDma*)Callback;
u32 IsrStatus ;
int AxiDmaResetTimeOut;
// 获取中断状态
IsrStatus = XAxiDma_IntrGetIrq(AxiDmaInstPtr, XAXIDMA_DMA_TO_DEVICE);
// 清除中断状态
XAxiDma_IntrAckIrq(AxiDmaInstPtr, IsrStatus, XAXIDMA_DMA_TO_DEVICE);
// AXIDMA发送出错
if ((IsrStatus & XAXIDMA_IRQ_ERROR_MASK)) {
AxiDmaTxError = 1;
XAxiDma_Reset(AxiDmaInstPtr);
AxiDmaResetTimeOut = RESET_TIMEOUT_COUNTER;
while (AxiDmaResetTimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInstPtr)) {
break;
}
AxiDmaResetTimeOut -= 1;
}
return;
}
// AXIDMA发送完成
if ((IsrStatus & XAXIDMA_IRQ_IOC_MASK)) {
AxiDmaTxDone = 1;
}
//
return;
}
// AXIDMA接收中断处理函数
void AxiDmaRxIntrHandler(void *Callback)
{
//
XAxiDma* AxiDmaInstPtr = (XAxiDma*)Callback;
u32 IsrStatus ;
int AxiDmaResetTimeOut;
// 获取中断状态
IsrStatus = XAxiDma_IntrGetIrq(AxiDmaInstPtr, XAXIDMA_DEVICE_TO_DMA);
// 清除中断状态
XAxiDma_IntrAckIrq(AxiDmaInstPtr, IsrStatus, XAXIDMA_DEVICE_TO_DMA);
// AXIDMA接收出错
if ((IsrStatus & XAXIDMA_IRQ_ERROR_MASK)) {
AxiDmaRxError = 1;
XAxiDma_Reset(AxiDmaInstPtr);
AxiDmaResetTimeOut = RESET_TIMEOUT_COUNTER;
while (AxiDmaResetTimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInstPtr)) {
break;
}
AxiDmaResetTimeOut -= 1;
}
return;
}
// AXIDMA接收完成
if ((IsrStatus & XAXIDMA_IRQ_IOC_MASK)) {
AxiDmaRxDone = 1;
}
return;
}
// 检查数据缓冲区
int CheckData(u32* RxBuffer)
{
//
u32 TestValue;
//
TestValue = TEST_START_VALUE;
//
for (int i = 0; i < AXIDMA_LENGTH / sizeof(u32); i++) {
if (RxBuffer[i] != TestValue) {
printf("The %d Data Error : Received Data is %lx, Expected Data is %lx\n", i, RxBuffer[i], TestValue);
return XST_FAILURE;
}
TestValue++;
}
//
return XST_SUCCESS;
}