别光看理论了!手把手在Zynq-7000上跑通AXI DMA数据回环(Vivado 2023.1 + SDK)
从零构建Zynq-7000的AXI DMA数据回环Vivado 2023.1实战指南第一次在Zynq-7000上搭建AXI DMA数据通道时我盯着Vivado里那些密密麻麻的总线接口发愣——MM2S、S2MM、AXI Stream、SmartConnect每个名词都像一堵高墙。直到亲手完成这个PS-PL-PS数据回环实验看到示波器上跳动的波形和终端打印的校验数据才真正理解AXI DMA如何架起处理器与可编程逻辑之间的高速桥梁。本文将用最直白的语言和截图带你一步步避开所有坑点在Zynq-7000开发板上实现这个经典的数据传输实验。1. 工程创建与IP核配置打开Vivado 2023.1新建一个RTL工程并选择对应的Zynq器件型号。在Block Design中首先需要添加Zynq Processing System IP这是整个设计的核心。双击打开配置界面在PS-PL Configuration选项卡中做以下关键设置启用GP0 AXI Master接口用于控制DMA启用HP0 AXI Slave接口用于高速数据传输在Clock Configuration中确保FCLK_CLK0频率设置为100MHz与DMA时钟同步接着添加AXI Direct Memory Access IP核这是实现高速数据传输的关键。建议直接使用最新版本的7.1版本DMA其配置参数如下表所示参数项推荐值说明Enable Scatter Gather取消勾选简化设计使用Simple模式Width of Buffer Length23最大传输长度8MB(2^23)Enable Micro DMA取消勾选标准DMA模式更稳定Memory Map Data Width64匹配Zynq HP接口位宽Stream Data Width32常见数据位宽节省资源注意务必勾选Enable MM2S和Enable S2MM两个方向因为我们既要实现PS到PL的发送也要完成PL到PS的接收。2. 构建完整的Block Design现在需要将各个IP核通过AXI总线正确连接。推荐使用AXI SmartConnect作为总线互联IP它能自动处理地址映射和协议转换。具体连接步骤如下控制通路连接Zynq的M_AXI_GP0 → SmartConnect的S00_AXISmartConnect的M00_AXI → DMA的S_AXI_LITE数据通路连接DMA的M_AXI_MM2S → Zynq的S_AXI_HP0DMA的M_AXI_S2MM → Zynq的S_AXI_HP0DMA的M_AXIS_MM2S → AXI Data FIFO的S_AXISAXI Data FIFO的M_AXIS → DMA的S_AXIS_S2MM中断连接DMA的mm2s_introut → Zynq的IRQ_F2P[0]DMA的s2mm_introut → Zynq的IRQ_F2P[1]完成后的Block Design应该如下图所示实际需根据开发板型号调整// 关键信号连接示例Vivado自动生成 assign axi_smartconnect_0_M00_AXI_ARADDR axi_dma_0_S_AXI_LITE_araddr[31:0]; assign axi_dma_0_S_AXI_LITE_arready axi_smartconnect_0_M00_AXI_arready; assign axi_dma_0_S_AXI_LITE_awready axi_smartconnect_0_M00_AXI_awready; assign axi_dma_0_S_AXI_LITE_bresp axi_smartconnect_0_M00_AXI_bresp[1:0];常见错误忘记连接AXI Data FIFO的axis_rd_data_count和axis_wr_data_count信号这会导致无法监控FIFO状态。建议将这些信号引出到顶层模块方便调试。3. SDK中的软件编程实战生成Bitstream并导出到Vivado SDK后新建一个空白应用工程。关键的软件实现分为三部分DMA初始化、中断服务程序和主数据传输逻辑。3.1 DMA初始化配置#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID #define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_INTR #define TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_INTR XAxiDma_Config *DmaConfig; XAxiDma AxiDma; int InitDMA() { // 查找DMA配置 DmaConfig XAxiDma_LookupConfig(DMA_DEV_ID); if (!DmaConfig) { xil_printf(No DMA config found\r\n); return XST_FAILURE; } // 初始化DMA实例 int Status XAxiDma_CfgInitialize(AxiDma, DmaConfig); if (Status ! XST_SUCCESS) { xil_printf(DMA init failed: %d\r\n, Status); return XST_FAILURE; } // 检查Scatter Gather模式是否意外启用 if(XAxiDma_HasSg(AxiDma)){ xil_printf(Error: DMA configured in SG mode\r\n); return XST_FAILURE; } return XST_SUCCESS; }3.2 中断服务程序实现volatile int TxDone 0; volatile int RxDone 0; void TxIntrHandler(void *Callback) { TxDone 1; XAxiDma *AxiDmaInst (XAxiDma *)Callback; XAxiDma_IntrAckIrq(AxiDmaInst, XAXIDMA_IRQ_ALL_MASK); } void RxIntrHandler(void *Callback) { RxDone 1; XAxiDma *AxiDmaInst (XAxiDma *)Callback; XAxiDma_IntrAckIrq(AxiDmaInst, XAXIDMA_IRQ_ALL_MASK); } int SetupIntrSystem(XScuGic *IntcInstancePtr, XAxiDma *AxiDmaPtr) { XScuGic_Config *IntcConfig; // 初始化中断控制器 IntcConfig XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID); XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig-CpuBaseAddress); // 注册中断处理函数 XScuGic_Connect(IntcInstancePtr, TX_INTR_ID, (Xil_ExceptionHandler)TxIntrHandler, AxiDmaPtr); XScuGic_Connect(IntcInstancePtr, RX_INTR_ID, (Xil_ExceptionHandler)RxIntrHandler, AxiDmaPtr); // 启用中断 XScuGic_Enable(IntcInstancePtr, TX_INTR_ID); XScuGic_Enable(IntcInstancePtr, RX_INTR_ID); // 设置DMA中断模式 XAxiDma_IntrEnable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA); XAxiDma_IntrEnable(AxiDmaPtr, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE); return XST_SUCCESS; }4. 数据传输测试与调试技巧完成基础代码编写后我们需要验证数据传输的正确性。以下是一个完整的测试流程内存准备#define MAX_PKT_LEN 1024 u8 *TxBufferPtr (u8 *)Xil_DCacheInvalidateRange(TX_BUFFER_BASE, MAX_PKT_LEN); u8 *RxBufferPtr (u8 *)Xil_DCacheInvalidateRange(RX_BUFFER_BASE, MAX_PKT_LEN); // 填充测试数据 for(int i0; iMAX_PKT_LEN; i) { TxBufferPtr[i] i % 256; }启动传输// 启动接收通道PL→PS Status XAxiDma_SimpleTransfer(AxiDma, (UINTPTR)RxBufferPtr, MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA); // 启动发送通道PS→PL Status XAxiDma_SimpleTransfer(AxiDma, (UINTPTR)TxBufferPtr, MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);等待完成并校验while(!TxDone || !RxDone); // 等待中断标志 // 数据校验 int error_count 0; for(int i0; iMAX_PKT_LEN; i) { if(RxBufferPtr[i] ! TxBufferPtr[i]) { error_count; xil_printf(Mismatch at %d: Tx0x%02x Rx0x%02x\r\n, i, TxBufferPtr[i], RxBufferPtr[i]); } }当遇到问题时可以按照以下顺序排查DMA初始化失败检查vivado中DMA IP的基地址是否与xparameters.h中一致数据传输卡死确认HP0接口已在Zynq配置中启用且DDR控制器配置正确中断不触发在Vivado中确认中断信号已正确连接检查GIC中断号定义数据校验错误检查AXI Data FIFO的位宽设置确保没有数据截断5. 性能优化与高级应用成功实现基础功能后可以通过以下方式进一步提升DMA传输效率双缓冲技术// 定义两个接收缓冲区 u8 *RxBufferPtr[2]; RxBufferPtr[0] (u8 *)RX_BUFFER_BASE; RxBufferPtr[1] (u8 *)(RX_BUFFER_BASE MAX_PKT_LEN); // 交替使用缓冲区 int current_buf 0; while(1) { XAxiDma_SimpleTransfer(AxiDma, (UINTPTR)RxBufferPtr[current_buf], MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA); current_buf ^ 1; // 切换缓冲区 ProcessData(RxBufferPtr[current_buf^1]); // 处理另一个缓冲区的数据 }使用VDMA实现视频流处理 对于图像处理应用可以将AXI DMA替换为AXI VDMA支持帧缓冲和2D数据传输特性AXI DMAAXI VDMA数据传输维度1D线性2D帧缓冲典型应用普通数据流视频图像处理内存管理简单缓冲区多帧缓冲管理同步方式中断/轮询帧同步信号结合FreeRTOS实现任务调度 在更复杂的系统中可以将DMA操作封装为FreeRTOS任务void vDMATask(void *pvParameters) { while(1) { xSemaphoreTake(xDMASemaphore, portMAX_DELAY); XAxiDma_SimpleTransfer(AxiDma, (UINTPTR)RxBufferPtr, MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA); xTaskNotifyGive(xProcessingTask); } }在实际项目中我发现最影响稳定性的往往是细节DDR内存地址必须64字节对齐使用Xil_DCacheInvalidateRange确保、中断优先级需要合理设置、Vivado生成的时钟约束必须严格检查。有一次调试三天才发现问题出在AXI SmartConnect的地址映射范围设置错误导致DMA无法访问指定内存区域。