news 2026/4/15 19:41:55

一文说清Zynq中AXI DMA的工作模式与配置流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文说清Zynq中AXI DMA的工作模式与配置流程

深入Zynq中的AXI DMA:从寄存器模式到描述符链的实战解析

在工业视觉、雷达信号处理和边缘计算等高性能嵌入式系统中,数据流动的效率往往决定了整个系统的上限。Xilinx Zynq平台凭借“ARM + FPGA”的异构架构脱颖而出——PS端运行操作系统与算法逻辑,PL端实现高速并行处理。但连接这两者的“高速公路”是否畅通无阻?关键就在于AXI DMA

它不是简单的搬运工,而是智能调度的数据管家。尤其在1080p视频流、多通道ADC采样或网络包转发这类持续高吞吐场景下,若使用传统CPU轮询方式传输数据,不仅带宽吃紧,处理器也很快被拖垮。而合理配置的AXI DMA可以做到:数据自己搬,CPU只管结果

然而,面对复杂的寄存器结构、AXI协议细节以及Cache一致性问题,许多开发者在实际项目中常遇到“明明连上了却收不到完整帧”、“中断频繁卡死系统”等问题。本文将带你穿透这些迷雾,以工程实践为视角,彻底讲清AXI DMA的两种核心工作模式及其配置精髓。


为什么需要DMA?从一个真实痛点说起

想象这样一个场景:你正在开发一款基于Zynq的摄像头采集板卡,传感器输出的是1080p@60fps的原始图像流(RGB888),每秒产生约1.5 Gbps的数据量。

如果用CPU通过PIO(Programmed I/O)逐字节读取:

  • 每帧大小 ≈ 2 MB
  • 每秒60帧 → 需要每秒完成60次内存拷贝
  • 每次拷贝前还得同步状态、检查空闲缓冲区……

即使Cortex-A9主频高达667MHz,这种高频小批量操作也会迅速耗尽CPU资源,导致应用层来不及做任何图像处理。

解决方案是什么?

让硬件自动完成这件事——这就是DMA存在的意义

AXI DMA作为连接PL侧AXI Stream数据流与PS侧DDR内存之间的桥梁,允许FPGA逻辑直接将数据写入指定内存地址,全程无需CPU干预。真正的“解放双手”。


两种模式的本质区别:谁来指挥每一次传输?

AXI DMA支持两种主要工作模式:直接寄存器模式散列/描述符模式(Scatter-Gather, SG)。它们的根本差异在于:控制权交给谁?

直接寄存器模式:每次都要“发号施令”

这是最基础的工作方式,适合简单、周期明确的小规模传输任务。

它是怎么工作的?
  1. CPU告诉DMA:“我要从PL接收一段长度为len的数据,放到地址buf_addr。”
  2. 写入目标地址、传输长度、启动位。
  3. DMA发起AXI读事务,把数据从PL搬到DDR。
  4. 传输完成,置标志位,可选触发中断。
  5. CPU收到通知后,再手动发起下一次传输。

整个过程就像点外卖:

“我饿了” → 下单 → 等送达 → 吃完 → 再下单

每一步都得你自己动手。

关键特点一览
特性说明
单次传输每次只能提交一个连续块
无描述符表不依赖外部BD链
软件全程控制必须由CPU重新配置和启动
低延迟启动初始化快,适合短时突发
高CPU开销频繁中断或轮询影响性能
典型应用场景
  • 传感器轮询采集(如温度、加速度计)
  • 小包通信协议解析(Modbus、CAN over FPGA)
  • 原型验证阶段快速验证通路连通性
实现代码示例(裸机环境)
#include "xaxidma.h" XAxiDma AxiDma; int simple_dma_transfer(u32 src_addr, u32 dst_addr, u32 length) { XAxiDma_Config *Config; int Status; // 查找设备配置 Config = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID); if (!Config) return XST_FAILURE; // 初始化DMA实例 Status = XAxiDma_CfgInitialize(&AxiDma, Config); if (Status != XST_SUCCESS) return XST_FAILURE; // 确保SG模式未启用 if (XAxiDma_HasSg(&AxiDma)) { xdbg_printf(XDBG_DEBUG_ERROR, "Warning: SG mode enabled but not used.\r\n"); } // 发起发送方向传输(PL → DDR) Status = XAxiDma_SimpleTransfer(&AxiDma, src_addr, length, XAXIDMA_DMA_TO_DEVICE); if (Status != XST_SUCCESS) return XST_FAILURE; // 发起接收方向传输(DDR ← PL) Status = XAxiDma_SimpleTransfer(&AxiDma, dst_addr, length, XAXIDMA_DEVICE_TO_DMA); if (Status != XST_SUCCESS) return XST_FAILURE; return XST_SUCCESS; }

⚠️ 注意:XAxiDma_SimpleTransfer是非阻塞接口。调用后需等待完成标志或使用中断机制判断传输结束。

这种方式虽然简单,但在高频率场景下会成为系统瓶颈。有没有更高效的方法?

有,那就是——让DMA自己动起来


散列/描述符模式:给DMA一本“任务手册”

如果说直接模式是“命令驱动”,那描述符模式就是“剧本驱动”。我们提前准备好一张张“任务卡”(Buffer Descriptor, BD),组成一个队列,然后对DMA说:“照着这张表一直干,直到没活为止。”

这正是Scatter-Gather(SG)模式的核心思想。

它解决了什么问题?

问题解法
多段不连续内存传输困难BD可指向任意物理地址
CPU频繁参与调度自动加载下一个任务
中断风暴支持中断合并(每N个BD触发一次)
实时性差支持环形缓冲,实现零延迟切换

特别适用于以下场景:
- 视频帧循环采集(V4L2-like行为)
- 雷达回波数据流记录
- 音频流播放/录制
- 高速ADC长时间采样

工作原理拆解

SG模式依赖于独立的BD Ring(描述符环),其基本流程如下:

  1. 构建描述符环
    在内存中分配一块连续空间,存放多个BD结构体,形成环形队列。

  2. 预填充缓冲信息
    每个BD包含:目标地址、传输长度、控制字段、状态位、用户ID等。

  3. 提交至硬件队列
    调用API将部分或全部BD放入“待处理队列”,DMA开始执行。

  4. 自动调度执行
    每当一帧数据到达,DMA自动选择下一个可用BD,写入数据,并更新状态。

  5. 中断回调回收
    当若干BD完成时,触发中断,CPU批量回收已完成的任务,重新填入新缓冲或复用旧缓冲。

整个过程如同流水线作业,极大降低CPU介入频率。

核心参数设计要点

参数推荐设置说明
BD数量8~32太少易溢出,太多占内存
缓冲大小≥单帧数据建议整数倍于最大数据单元
地址对齐4字节对齐推荐使用Xil_MemAlign分配
中断阈值4~8平衡实时性与中断开销
Cache策略显式刷新/无效化否则可能读到脏数据

SG模式完整初始化流程(接收通道为例)

#define NUM_BDS 16 #define BUFFER_LENGTH 2048 #define TOTAL_SIZE (NUM_BDS * BUFFER_LENGTH) u8 *RecvBuffers[NUM_BDS]; u8 *BdSpace; // 存放BD的内存空间 XAxiDma AxiDma; XAxidma_BdRing *RxRing; int setup_sg_receive_channel() { XAxiDma_Config *Config; XAxidma_Bd BdTemplate; u32 BufferAddr; int i, Status; // 获取配置并初始化DMA Config = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID); Status = XAxiDma_CfgInitialize(&AxiDma, Config); if (Status != XST_SUCCESS) return XST_FAILURE; RxRing = XAxiDma_GetRxRing(&AxiDma); // 停止当前运行以便重新配置 XAxiDma_BdRingHalt(RxRing); // 分配BD存储空间(需对齐) BdSpace = (u8 *)Xil_MemAlign(sizeof(XAxidma_Bd) * NUM_BDS, 64); if (!BdSpace) return XST_FAILURE; memset(BdSpace, 0, sizeof(XAxidma_Bd) * NUM_BDS); // 创建BD环形队列 Status = XAxiDma_BdRingCreate(RxRing, (UINTPTR)BdSpace, (UINTPTR)BdSpace, sizeof(XAxidma_Bd), NUM_BDS); if (Status != XST_SUCCESS) return XST_FAILURE; // 初始化模板(清除默认值) XAxiDma_BdClear(&BdTemplate); XAxiDma_BdRingClone(RxRing, &BdTemplate); // 应用硬件限制 // 分配接收缓冲区(建议使用大页内存) BufferAddr = (u32)memalign(4096, TOTAL_SIZE); if (!BufferAddr) return XST_FAILURE; memset((void*)BufferAddr, 0, TOTAL_SIZE); // 填充每个BD并提交到硬件队列 for (i = 0; i < NUM_BDS; i++) { RecvBuffers[i] = (u8 *)(BufferAddr + i * BUFFER_LENGTH); XAxidma_Bd *BdPtr = XAxiDma_BdRingAlloc(RxRing, 1); if (!BdPtr) return XST_FAILURE; // 设置缓冲地址和长度 XAxiDma_BdSetBufAddr(BdPtr, (UINTPTR)RecvBuffers[i]); XAxiDma_BdSetLength(BdPtr, BUFFER_LENGTH, RxRing->MaxTransferLen); // 清除控制位(例如SOF/EOF等由硬件管理) XAxiDma_BdSetCtrl(BdPtr, 0); // 可绑定用户上下文(便于后续识别) XAxiDma_BdSetId(BdPtr, (void *)RecvBuffers[i]); // 提交至硬件 Status = XAxiDma_BdRingToHw(RxRing, 1, BdPtr); if (Status != XST_SUCCESS) return XST_FAILURE; } // 启动接收引擎 XAxiDma_BdRingStartRx(RxRing); // 使能中断(例如每完成一批触发一次) XAxiDma_BdRingEnableIrq(RxRing, XAXIDMA_IRQ_IOC_MASK); return XST_SUCCESS; }

这段代码完成了SG模式的核心初始化流程。值得注意的是:

  • Xil_MemAlignmemalign确保内存对齐,避免总线异常;
  • BdRingCreate构建逻辑环,支持自动回绕;
  • BdRingToHw将描述符交付给DMA控制器;
  • 最后调用StartRx才真正开启监听。

中断服务程序:如何高效处理完成事件?

void dma_rx_isr(void *Callback) { XAxidma_Bd *BdPtr; int BdCount; u32 IrqStatus; // 读取中断状态并清除 IrqStatus = XAxiDma_BdRingGetIrq(RxRing); XAxiDma_BdRingAckIrq(RxRing, IrqStatus); // 必须先ACK if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) return; // 从硬件队列中取出所有已完成的BD BdCount = XAxiDma_BdRingFromHw(RxRing, XAXIDMA_ALL_BDS, &BdPtr); if (BdCount <= 0) return; // 遍历处理每一个完成的缓冲区 while (BdCount--) { u8 *buffer = (u8 *)XAxiDma_BdGetBufAddr(BdPtr); // 【重要】在启用Cache的系统中必须失效该区域 Xil_DCacheInvalidateRange((UINTPTR)buffer, BUFFER_LENGTH); // 用户自定义处理函数(如送入OpenCV处理队列) process_received_frame(buffer); // 处理完成后重置BD并返还给硬件 XAxiDma_BdSetCtrl(BdPtr, 0); // 清除状态 XAxiDma_BdRingToHw(RxRing, 1, BdPtr); // 放回待用队列 BdPtr = (XAxidma_Bd *)XAxiDma_BdRingNext(RxRing, BdPtr); } // 重新使能中断 XAxiDma_BdRingEnableIrq(RxRing, XAXIDMA_IRQ_IOC_MASK); }

✅ 提示:若使用Linux系统,可通过UIO或Xilinx提供的dmaengine驱动实现用户空间mmap,配合poll机制实现零拷贝。


实战经验:那些手册不会告诉你的坑

坑点1:Cache没处理好,永远看不到最新数据!

这是最常见的错误之一。ARM核有L1 Cache,当你从DDR读取DMA写入的数据时,很可能读到的是Cache中的旧副本。

✅ 正确做法:

// 在访问之前失效Cache Xil_DCacheInvalidateRange((UINTPTR)buf, len); // 或者在DMA写入前清空(用于发送方向) Xil_DCacheFlushRange((UINTPTR)tx_buf, len);

否则你会看到这样的现象:“明明DMA报告完成了,但我读出来的全是0或者上一帧的内容。”


坑点2:地址不对齐导致传输失败或降速

AXI协议要求地址和长度满足一定对齐条件(通常是4字节)。如果你用普通malloc分配内存,不能保证物理对齐。

✅ 解决方案:

u8 *aligned_buf = (u8 *)memalign(64, size); // 至少4字节,推荐64字节对齐

或使用SDK提供的:

u8 *aligned = (u8 *)Xil_MemAlign(size, 64);

坑点3:中断太频繁,系统卡顿甚至死机

SG模式虽支持中断合并,但默认可能是“每完成一个BD就中断”。对于1080p@60fps系统,意味着每秒产生数千次中断!

✅ 正确配置:

// 设置中断合并阈值(例如每4个完成才上报) XAxiDma_BdRingSetCoalesce(RxRing, 4, 0);

这样可以把中断频率降低75%以上,显著提升系统稳定性。


坑点4:HP端口带宽不够,数据堆积

Zynq的S_AXI_HP接口理论带宽可达2.4 GB/s(64位宽 @ 100MHz),但如果数据源速率超过此值,或者多个外设共用同一HP通道,就会出现拥塞。

✅ 设计建议:
- 使用单独HP端口专供DMA;
- 数据源时钟尽量低于HP时钟;
- 添加FIFO缓冲平滑突发流量;
- 利用ILA抓AXI信号确认握手机制是否正常。


结语:理解DMA,才是真正理解Zynq的开始

AXI DMA远不止是一个IP核那么简单。它是打通PS与PL之间数据动脉的关键节点。掌握它的两种工作模式,不仅仅是学会怎么传数据,更是建立起一种“软硬协同”的系统级思维。

当你能从容地在直接模式与SG模式间做出选择,知道何时该牺牲一点灵活性换取极致性能,何时又该简化设计加速迭代,你就已经超越了大多数初学者。

无论是打造一台实时图像分析仪,还是构建一个低延迟工业控制器,让数据自由流动的能力,始终是高性能嵌入式系统的灵魂所在

如果你正在调试DMA却卡在某个环节,欢迎留言交流——毕竟,每一个成功的DMA背后,都曾经历过无数次“收不到第一帧”的夜晚。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 10:29:13

QMCDecode终极指南:快速解锁QQ音乐加密格式的完整解决方案

QMCDecode终极指南&#xff1a;快速解锁QQ音乐加密格式的完整解决方案 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac&#xff0c;qmc0,qmc3转mp3, mflac,mflac0等转flac)&#xff0c;仅支持macOS&#xff0c;可自动识别到QQ音乐下载目录&#xff0c;默…

作者头像 李华
网站建设 2026/4/3 20:30:51

3分钟掌握DOL-CHS-MODS:新手必看的终极整合指南

3分钟掌握DOL-CHS-MODS&#xff1a;新手必看的终极整合指南 【免费下载链接】DOL-CHS-MODS Degrees of Lewdity 整合 项目地址: https://gitcode.com/gh_mirrors/do/DOL-CHS-MODS 想要快速体验Degrees of Lewdity完整汉化版却苦于复杂配置&#xff1f;DOL-CHS-MODS整合包…

作者头像 李华
网站建设 2026/4/14 5:21:34

Degrees of Lewdity中文汉化完全指南:从零开始打造沉浸式游戏体验

Degrees of Lewdity中文汉化完全指南&#xff1a;从零开始打造沉浸式游戏体验 【免费下载链接】Degrees-of-Lewdity-Chinese-Localization Degrees of Lewdity 游戏的授权中文社区本地化版本 项目地址: https://gitcode.com/gh_mirrors/de/Degrees-of-Lewdity-Chinese-Locali…

作者头像 李华
网站建设 2026/4/11 5:45:13

NS-USBLoader完整使用指南:5大核心功能深度解析

NS-USBLoader完整使用指南&#xff1a;5大核心功能深度解析 【免费下载链接】ns-usbloader Awoo Installer and GoldLeaf uploader of the NSPs (and other files), RCM payload injector, application for split/merge files. 项目地址: https://gitcode.com/gh_mirrors/ns/…

作者头像 李华
网站建设 2026/4/11 20:48:44

FluentUI框架实战指南:打造现代化跨平台应用界面的终极利器

FluentUI框架实战指南&#xff1a;打造现代化跨平台应用界面的终极利器 【免费下载链接】OnmyojiAutoScript Onmyoji Auto Script | 阴阳师脚本 项目地址: https://gitcode.com/gh_mirrors/on/OnmyojiAutoScript FluentUI是一个基于Qt Quick技术栈构建的现代化跨平台UI框…

作者头像 李华
网站建设 2026/4/10 10:48:55

QMCDecode使用指南:3步实现QQ音乐格式转换

QMCDecode使用指南&#xff1a;3步实现QQ音乐格式转换 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac&#xff0c;qmc0,qmc3转mp3, mflac,mflac0等转flac)&#xff0c;仅支持macOS&#xff0c;可自动识别到QQ音乐下载目录&#xff0c;默认转换结果存储…

作者头像 李华