
1 /***************************** Include Files *********************************/
2
3 #include "xaxidma.h"
4 #include "xparameters.h"
5 #include "xil_exception.h"
6 #include "xscugic.h"
7
8 /************************** Constant Definitions *****************************/
9
10 #define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
11 #define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
12 #define TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
13 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
14 #define DDR_BASE_ADDR XPAR_PSU_DDR_0_S_AXI_BASEADDR //0x00000000
15 #define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1100000) //0x01100000
16 #define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //0x01200000
17 #define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000
18 #define RESET_TIMEOUT_COUNTER 10000 //复位时间
19 #define TEST_START_VALUE 0x0 //测试起始值
20 #define MAX_PKT_LEN 0x100 //发送包长度
21
22 /************************** Function Prototypes ******************************/
23
24 static int check_data(int length, u8 start_value);
25 static void tx_intr_handler(void *callback);
26 static void rx_intr_handler(void *callback);
27 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
28 u16 tx_intr_id, u16 rx_intr_id);
29 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
30 u16 rx_intr_id);
31
32 /************************** Variable Definitions *****************************/
33
34 static XAxiDma axidma; //XAxiDma实例
35 static XScuGic intc; //中断控制器的实例
36 volatile int tx_done; //发送完成标志
37 volatile int rx_done; //接收完成标志
38 volatile int error; //传输出错标志
39
40 /************************** Function Definitions *****************************/
41
42 int main(void)
43 {
44 int i;
45 int status;
46 u8 value;
47 u8 *tx_buffer_ptr;
48 u8 *rx_buffer_ptr;
49 XAxiDma_Config *config;
50
51 tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
52 rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;
53
54 xil_printf("\r\n--- Entering main() --- \r\n");
55
56 config = XAxiDma_LookupConfig(DMA_DEV_ID);
57 if (!config) {
58 xil_printf("No config found for %d\r\n", DMA_DEV_ID);
59 return XST_FAILURE;
60 }
61
62 //初始化DMA引擎
63 status = XAxiDma_CfgInitialize(&axidma, config);
64 if (status != XST_SUCCESS) {
65 xil_printf("Initialization failed %d\r\n", status);
66 return XST_FAILURE;
67 }
68
69 if (XAxiDma_HasSg(&axidma)) {
70 xil_printf("Device configured as SG mode \r\n");
71 return XST_FAILURE;
72 }
73
74 //建立中断系统
75 status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
76 if (status != XST_SUCCESS) {
77 xil_printf("Failed intr setup\r\n");
78 return XST_FAILURE;
79 }
80
81 //初始化标志信号
82 tx_done = 0;
83 rx_done = 0;
84 error = 0;
85
86 value = TEST_START_VALUE;
87 for (i = 0; i < MAX_PKT_LEN; i++) {
88 tx_buffer_ptr[i] = value;
89 value = (value + 1) & 0xFF;
90 }
91
92 Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
93
94 status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
95 MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
96 if (status != XST_SUCCESS) {
97 return XST_FAILURE;
98 }
99
100 status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
101 MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
102 if (status != XST_SUCCESS) {
103 return XST_FAILURE;
104 }
105
106 Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
107 while (!tx_done && !rx_done && !error)
108 ;
109 //传输出错
110 if (error) {
111 xil_printf("Failed test transmit%s done, "
112 "receive%s done\r\n", tx_done ? "" : " not",
113 rx_done ? "" : " not");
114 goto Done;
115 }
116
117 //传输完成,检查数据是否正确
118 status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
119 if (status != XST_SUCCESS) {
120 xil_printf("Data check failed\r\n");
121 goto Done;
122 }
123
124 xil_printf("Successfully ran AXI DMA Loop\r\n");
125 disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);
126
127 Done: xil_printf("--- Exiting main() --- \r\n");
128 return XST_SUCCESS;
129 }
老师,现在是使用vivado2022.1,vitis,,我从(正点原子FPGA连载】第十六章AXI DMA环路测试交互--领航者 ZYNQ 之嵌入式开发指南(他们使用的sdk)中复制了一份代码,我也是按照他们视频来操作的)帮我分析解释一下这份代码,每一行是什么意思,为什么,我是小白,谢谢啦,越具体越好,我还是想知道代码表示的什么含义,这个是核心,谢谢啦
同学,坐。看到你终于把 Vivado 的连线图、Vitis 的代码工程,还有正点原子的源码都凑齐了,老师打心底里为你高兴!
你现在面对的,就是工业界最标准的"基于中断的 DMA 回环测试"代码。你截图里的 Vivado 连线非常标准:DMA 发出去的数据进入 FIFO,FIFO 吐出来的数据又回到 DMA(形成了自环)。
咱们今天不看那些晦涩难懂的宏定义(那些都是 BSP 自动生成的),老师直接带你一段一段地"啃"这段 main.c,让你彻底明白为什么要在 C 语言里这么写。
🎯 第一幕:排兵布阵(宏定义与全局变量)
看代码的 第 10 行到 38 行。
c
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //0x01200000
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000
#define MAX_PKT_LEN 0x100 //发送包长度 256 字节
static XAxiDma axidma; // DMA 实例
volatile int tx_done; // 发送完成标志
volatile int rx_done; // 接收完成标志
-
本质含义:老板(CPU)要给搬运工(DMA)下达任务,得先准备好"仓库"。
-
TX_BUFFER_BASE和RX_BUFFER_BASE:这就是咱们之前说的"地址"。咱们在内存(DDR)里划出了一块地作为发送仓库,另一块地作为接收仓库。 -
tx_done和rx_done:这两个就是**"警报器"**。一开始是 0,等 DMA 搬完货发了中断,中断服务函数会把它改成 1。
-
-
❓ 为什么要这样写?
- 用
volatile修饰,是为了告诉编译器:"这俩变量随时会被硬件中断改变,你别瞎优化它"。这就是咱们说的中断标志位。
- 用
🎯 第二幕:开机自检与召唤搬运工(初始化)
看代码的 第 56 行到 79 行。
c
config = XAxiDma_LookupConfig(DMA_DEV_ID);
status = XAxiDma_CfgInitialize(&axidma, config);
if (XAxiDma_HasSg(&axidma)) {
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
-
本质含义:
-
LookupConfig和CfgInitialize:查字典,找到 DMA 的硬件基地址,把老板的"对讲机"和搬运工的"对讲机"调到同一个频道。 -
HasSg判断:检查你昨天在 Vivado 里有没有把 DMA 配置成"高级的 S/G 模式"。咱们用的是简单模式(简单粗暴),所以这里不能报错。 -
setup_intr_system:把中断线(IRQ)连上。告诉 GIC(中断控制器):"如果 TX 或 RX 搬完货了,立刻打断老板的睡眠"。
-
-
❓ 为什么要这样写?
底层硬件初始化必须放在第一步。没有这一步,DMA 就是一块废铁。
🎯 第三幕:装满货物,准备发车
看代码的 第 86 行到 92 行。
c
value = TEST_START_VALUE;
for (i = 0; i < MAX_PKT_LEN; i++) {
tx_buffer_ptr[i] = value;
value = (value + 1) & 0xFF;
}
Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
-
本质含义:
-
咱们往发送仓库里填数据:0, 1, 2, 3... 一直到 255。
-
Xil_DCacheFlushRange(极其核心):咱们之前反复强调的"大坑"。CPU 把数据写在了自己的 Cache(口袋)里。为了保证 DMA 这个大老粗去 DDR 仓库能拿到最新数据,必须把数据"刷(Flush)"进 DDR。
-
-
❓ 为什么要这样写?
如果不刷新 Cache,DMA 发出去的可能会全是乱码或者 0。
🎯 第四幕:扣动扳机!(启动传输)
看代码的 第 94 行到 106 行。
c
// 启动发送(MM2S)
status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
// 启动接收(S2MM)
status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
// 接收仓库的 Cache 也要清空
Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);
-
本质含义:
-
第一句:老板下令:"去
tx_buffer_ptr搬 256 个字节,发到 PL 端去(MM2S)"。 -
第二句:老板下令:"准备好接收!PL 端随时可能把数据传回来,你就放到
rx_buffer_ptr里(S2MM)"。
-
-
❓ 为什么发送和接收都写在这一起?
因为 DMA 是个双通道机器(Read 和 Write),你可以让它在发数据的同时,也打开接收的闸门。这叫"全双工"操作。
🎯 第五幕:老板睡大觉,等报警器响(死等中断)
看代码的 第 107 行。
c
while (!tx_done && !rx_done && !error)
;
-
本质含义 :这是一个死循环(空转)。老板(CPU)现在什么活都不用干,就盯着
tx_done和rx_done这几个标志位。只要都是 0,说明搬运工还没发完。只有其中一个变成了 1(触发了中断),或者出错(error=1),才会跳出循环。 -
❓ 为什么要这样写?
这展示了 DMA 的核心价值:CPU 没有在搬运数据,它只是在等待结果。
🎯 第六幕:验货!
看代码的 第 117 行到 125 行。
c
status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
if (status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
xil_printf("Successfully ran AXI DMA Loop\r\n");
- 本质含义 :中断响了,循环跳出来了。老板去接收仓库(
rx_buffer_ptr)里看看货对不对。因为咱们之前发的是 0,1,2...255,如果经过 PL 端的 FIFO 绕了一圈回来,接收到的也是 0,1,2...255,那就说明 DMA 的整条物理链路和软件配置全部完美通关! 最后打印成功信息。
🎯 隐藏的超级配角:中断服务函数(你的代码里没贴出来)
在代码的末尾,一定会有一个 tx_intr_handler 和 rx_intr_handler 函数。它们长这样(伪代码):
c
static void tx_intr_handler(void *callback) {
// 1. 清除 DMA 的中断标志(非常重要!不清理会无限打断 CPU)
XAxiDma_IntrAckIrq(...);
// 2. 告诉老板,发送完毕了
tx_done = 1;
}
- 这就是唤醒老板的那个"闹钟"。
💡 老师的总结
同学,你回头看看这段代码,是不是觉得它特别有逻辑?
初始化 DMA -> 初始化中断 -> 装填数据 -> 刷 Cache -> 发出指令 -> 等中断 -> 验收数据。
这就是工业级底层代码的"八股文"。你把这一段吃透,以后不管是用 Xilinx 的 DMA,还是用网口的 DMA,甚至是 PL 端的自定义 DMA,都是这个套路。
正点原子的代码写得非常标准。你直接照抄,编译下载跑一下。如果串口打印出 Successfully ran AXI DMA Loop,那就说明你亲手搭建的"高速公路"通车了!去试试吧!
This response is AI-generated, for reference only.
131 //检查数据缓冲区
132 static int check_data(int length, u8 start_value)
133 {
134 u8 value;
135 u8 *rx_packet;
136 int i = 0;
137
138 value = start_value;
139 rx_packet = (u8 *) RX_BUFFER_BASE;
140 for (i = 0; i < length; i++) {
141 if (rx_packet[i] != value) {
142 xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);
143 return XST_FAILURE;
144 }
145 value = (value + 1) & 0xFF;
146 }
147
148 return XST_SUCCESS;
149 }
150
151 //DMA TX中断处理函数
152 static void tx_intr_handler(void *callback)
153 {
154 int timeout;
155 u32 irq_status;
156 XAxiDma *axidma_inst = (XAxiDma *) callback;
157
158 //读取待处理的中断
159 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
160 //确认待处理的中断
161 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
162
163 //Tx出错
164 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
165 error = 1;
166 XAxiDma_Reset(axidma_inst);
167 timeout = RESET_TIMEOUT_COUNTER;
168 while (timeout) {
169 if (XAxiDma_ResetIsDone(axidma_inst))
170 break;
171 timeout -= 1;
172 }
173 return;
174 }
175
176 //Tx完成
177 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
178 tx_done = 1;
179 }
180
181 //DMA RX中断处理函数
182 static void rx_intr_handler(void *callback)
183 {
184 u32 irq_status;
185 int timeout;
186 XAxiDma *axidma_inst = (XAxiDma *) callback;
187
188 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
189 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);
190
191 //Rx出错
192 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
193 error = 1;
194 XAxiDma_Reset(axidma_inst);
195 timeout = RESET_TIMEOUT_COUNTER;
196 while (timeout) {
197 if (XAxiDma_ResetIsDone(axidma_inst))
198 break;
199 timeout -= 1;
200 }
201 return;
202 }
203
204 //Rx完成
205 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
206 rx_done = 1;
207 }
208
209 //建立DMA中断系统
210 // @param int_ins_ptr是指向XScuGic实例的指针
211 // @param AxiDmaPtr是指向DMA引擎实例的指针
212 // @param tx_intr_id是TX通道中断ID
213 // @param rx_intr_id是RX通道中断ID
214 // @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
215 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
216 u16 tx_intr_id, u16 rx_intr_id)
217 {
218 int status;
219 XScuGic_Config *intc_config;
220
221 //初始化中断控制器驱动
222 intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
223 if (NULL == intc_config) {
224 return XST_FAILURE;
225 }
226 status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
227 intc_config->CpuBaseAddress);
228 if (status != XST_SUCCESS) {
229 return XST_FAILURE;
230 }
231
232 //设置优先级和触发类型
233 XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
234 XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
235
236 //为中断设置中断处理函数
237 status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
238 (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
239 if (status != XST_SUCCESS) {
240 return status;
241 }
242
243 status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
244 (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
245 if (status != XST_SUCCESS) {
246 return status;
247 }
248
249 XScuGic_Enable(int_ins_ptr, tx_intr_id);
250 XScuGic_Enable(int_ins_ptr, rx_intr_id);
251
252 //启用来自硬件的中断
253 Xil_ExceptionInit();
254 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
255 (Xil_ExceptionHandler) XScuGic_InterruptHandler,
256 (void *) int_ins_ptr);
257 Xil_ExceptionEnable();
258
259 //使能DMA中断
260 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
261 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
262
263 return XST_SUCCESS;
264 }
265
266 //此函数禁用DMA引擎的中断
267 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
268 u16 rx_intr_id)
269 {
270 XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
271 XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
272 }
老师,刚刚还有一份代码漏掉给您了,这两份应该是一起的,这份代码也讲讲哈,按照之前的方式讲,谢谢啦,
同学,坐。你发来的这份代码极其关键!如果没有它,上一份代码里的 while(!tx_done && !rx_done) 就是一个永远醒不过来的"植物人"。
这份代码就是 DMA 的**"神经中枢"**,包含了数据比对、中断服务函数(ISR)和中断系统的初始化。咱们继续用"物流公司"的比喻,一行一行把它拆解透彻!
🎯 第一幕:库房验货员 ------ check_data 函数
看代码的 第 131 行到 149 行。
c
static int check_data(int length, u8 start_value)
{
u8 value = start_value;
u8 *rx_packet = (u8 *) RX_BUFFER_BASE;
for (i = 0; i < length; i++) {
if (rx_packet[i] != value) {
xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);
return XST_FAILURE;
}
value = (value + 1) & 0xFF;
}
return XST_SUCCESS;
}
-
本质含义 :这就是个"质检员"。你之前往发送仓库(
TX_BUFFER_BASE)里装的是0, 1, 2...255递增的数据。现在货从 PL 端绕了一圈回来了,质检员就跑到接收仓库(RX_BUFFER_BASE),从 0 开始数:第 0 个是不是 0?第 1 个是不是 1?... -
❓ 为什么需要这样做?
只有比对完全一样,才能证明这趟传输没有丢包、没有错位。
value = (value + 1) & 0xFF;意思是如果数到 255,加 1 就自动变回 0(因为硬件里一个字节最大就是 255)。
🎯 第二幕:总指挥部的警报铃 ------ 中断服务函数 (ISR)
看代码的 第 151 行到 207 行。这是整个程序最硬核、最需要你理解的部分。这里分成了 TX(发货)和 RX(收货)两个警报铃。
以 TX 中断处理函数 tx_intr_handler 为例:
c
static void tx_intr_handler(void *callback)
{
u32 irq_status;
XAxiDma *axidma_inst = (XAxiDma *) callback;
// 1. 看看是谁按响了警报?
irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
// 2. 把警报关掉(清除中断标志)
XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
-
❓ 为什么要这两句?
DMA 搬完货,把中断引脚拉高了。CPU 跑过来处理,第一件事必须是**"弄清原因,然后赶紧把警报关掉"**。如果不关,CPU 一回到主程序,DMA 还是高电平,CPU 又被打断,就会陷入"无限死循环"。
c
// 3. 如果警报是因为出事故了(Error)
if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
error = 1; // 告诉老板:出错了!
XAxiDma_Reset(axidma_inst); // 赶紧拍下急停按钮(复位 DMA)
return;
}
// 4. 如果警报是因为正常搬完了(IOC = Interrupt On Complete)
if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
tx_done = 1; // 升起“发货完成”的大旗!
}
-
❓ 为什么需要这样做?
在中断里,绝对不能做死循环或者耗时太长的操作(比如打印一串长文字)。它只负责认字,然后修改全局变量
tx_done(升大旗)。老板(主循环)看到大旗升起来了,就知道活干完了。RX 接收中断的逻辑一模一样,只是把tx_done换成了rx_done。
🎯 第三幕:接线员 ------ setup_intr_system 中断初始化
看代码的 第 215 行到 264 行。这是程序刚开始时,老板把警报线连到总线上的过程。
c
intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(int_ins_ptr, intc_config, intc_config->CpuBaseAddress);
-
❓ 为什么需要这样做?
把 Zynq 内部的"总机"(GIC,通用中断控制器)初始化好。所有的中断都要经过这个总机。
c
XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
-
❓ 为什么需要这样做?
给警报定个"优先级"(0xA0,数字越小优先级越高)和"触发方式"(0x3 代表上升沿触发)。万一火警和防盗警同时响,总机得知道先听谁的。
c
status = XScuGic_Connect(int_ins_ptr, tx_intr_id, (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
XScuGic_Enable(int_ins_ptr, tx_intr_id);
-
❓ 为什么需要这样做?
Connect:告诉总机,如果 TX 警报响了,立刻转接给tx_intr_handler那个电话分机。Enable:把分机的电话线插上(如果不写这句,电话永远打不通)。
c
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(... XScuGic_InterruptHandler ...);
Xil_ExceptionEnable(); // 撕掉 CPU 耳朵上的封条
-
❓ 为什么需要这样做?
这是最底层的"大门"。Zynq 的 CPU 默认是不想理任何人的(屏蔽所有中断)。必须用 ARM 内核的专用指令,把总机(GIC)挂靠到 CPU 的异常向量表上,然后大喊一声
Enable,CPU 才开始听取外界的报警。
c
XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
-
❓ 为什么需要这样做?
这是在 DMA 内部打开开关。告诉 DMA 搬运工:"这活儿干完了,你尽管按警报,我允许你发声"。
🎯 第四幕:下班拉闸 ------ disable_intr_system
看代码的 第 267 行到 272 行。
c
XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
-
❓ 为什么需要这样做?
主程序跑完了,打印了
Successfully ran AXI DMA Loop。为了避免程序结束或者复位时出现幽灵中断,把中断线拔掉,干净利落。
💡 老师的终极总结(把这些知识点串起来)
同学,你对比着看两份代码:
-
第一份代码 是老板的**"对讲机操作指南"**(发出指令、等待结果)。
-
第二份代码 是工厂的**"基础设施与警报系统"**(接线、配优先级、听到警报后清理现场并升大旗)。
一整套完美的工作流是这样的:
主程序把数据装好 -> 刷新 Cache -> 打开 DMA 搬运工 -> 打开警报总机 -> 扣动扳机 SimpleTransfer -> 主程序进入 while 空转等待 -> 搬运工搬完了按响警报 -> 总机把电话转接给中断处理函数 -> 中断函数关掉警报、升起 done 大旗 -> 主程序看到大旗,跳出循环 -> 去 check_data 验收货物。
这就是 Zynq PS 端最经典、最完美的中断驱动模型!
你现在把这两份代码放在一起看,是不是觉得它们像齿轮一样完美咬合在一起了?正点原子的代码确实写得很规范,直接拿去跑,大概率直接通过。如果遇到什么打印报错,立刻截图发给老师!
This response is AI-generated, for reference only.
老师,您能把这两份代码整合成一份main.c吗,方便我直接复制,我可以直接跑的哈,谢谢啦
同学,坐!老师完全理解你的心情,分两份看确实麻烦,直接要一份能"复制粘贴就能跑"的完整代码,这是最痛快的事。
老师已经帮你把这两份代码完美缝合 在一起了。并且,为了适应你正在用的 Vitis (Vivado 2022.1),老师把变量定义、头文件、甚至中断 ID 的命名,都按照最新版本的标准重新梳理了一遍。
⚠️ 注意:代码里有一处你极大概率会遇到报错的地方(第 11 和 12 行的中断 ID),老师已经在注释里写了"补救方案",如果编译报错,请务必看代码后面的说明!
请打开 Vitis,新建一个空工程(Empty Application),把下面这份完整的 main.c 直接复制进去:
/******************************************************************************
* @file main.c
* @brief AXI DMA 简单模式(Direct Register Mode)回环测试,带中断处理
* @note 适用于 Zynq-7000 (Cortex-A9) + Vitis 2022.1
******************************************************************************/
#include <stdio.h>
#include "xil_printf.h"
#include "xparameters.h"
#include "xaxidma.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xil_cache.h"
/************************** 常量定义 *****************************/
#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
// 【⚠️ 注意:如果你的程序编译报错说找不到下面这两个宏定义】
// 请在 Vitis 左侧展开你的 BSP -> ps7_cortexa9_0 -> include -> xparameters.h
// 搜索 "AXIDMA_0" 相关的 ID,把实际的名字填过来。
#define TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
// 内存地址分配 (避开代码区和堆栈区)
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR // 0x00000000
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x01000000) // 0x01000000 (16MB偏移)
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) // 0x01100000
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) // 0x01300000
#define RESET_TIMEOUT_COUNTER 10000
#define TEST_START_VALUE 0x0
#define MAX_PKT_LEN 0x100 // 发送包长度 256 字节
/************************** 变量定义 *****************************/
static XAxiDma axidma; // XAxiDma 驱动实例
static XScuGic intc; // 中断控制器实例
volatile int tx_done; // 发送完成标志
volatile int rx_done; // 接收完成标志
volatile int error; // 传输出错标志
/************************** 函数声明 *****************************/
static int check_data(int length, u8 start_value);
static void tx_intr_handler(void *callback);
static void rx_intr_handler(void *callback);
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr, u16 tx_intr_id, u16 rx_intr_id);
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id, u16 rx_intr_id);
/************************** 主函数 *****************************/
int main(void)
{
int i;
int status;
u8 value;
u8 *tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
u8 *rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;
XAxiDma_Config *config;
xil_printf("\r\n--- Entering main() --- \r\n");
// 1. 查找并初始化 DMA 配置
config = XAxiDma_LookupConfig(DMA_DEV_ID);
if (!config) {
xil_printf("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
status = XAxiDma_CfgInitialize(&axidma, config);
if (status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", status);
return XST_FAILURE;
}
// 检查是否误配成了 Scatter Gather 模式
if (XAxiDma_HasSg(&axidma)) {
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
// 2. 建立中断系统
status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
if (status != XST_SUCCESS) {
xil_printf("Failed intr setup\r\n");
return XST_FAILURE;
}
// 3. 初始化标志信号
tx_done = 0;
rx_done = 0;
error = 0;
// 4. 填充发送缓冲区数据 (0, 1, 2 ... 255)
value = TEST_START_VALUE;
for (i = 0; i < MAX_PKT_LEN; i++) {
tx_buffer_ptr[i] = value;
value = (value + 1) & 0xFF;
}
// 5. 【极其重要】刷新 Data Cache,确保数据写入物理 DDR
Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN);
// 6. 启动 DMA 发送 (PS -> PL)
status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr, MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
if (status != XST_SUCCESS) {
xil_printf("DMA TX error\r\n");
return XST_FAILURE;
}
// 7. 启动 DMA 接收 (PL -> PS)
status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr, MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (status != XST_SUCCESS) {
xil_printf("DMA RX error\r\n");
return XST_FAILURE;
}
// 再次刷新 Cache(防止旧数据污染)
Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);
// 8. 等待中断唤醒
while (!tx_done && !rx_done && !error)
;
// 9. 检查错误
if (error) {
xil_printf("Failed test transmit%s done, receive%s done\r\n",
tx_done ? "" : " not", rx_done ? "" : " not");
goto Done;
}
// 10. 校验数据
status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
if (status != XST_SUCCESS) {
xil_printf("Data check failed\r\n");
goto Done;
}
xil_printf("Successfully ran AXI DMA Loop\r\n");
disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);
Done:
xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
/************************** 子函数实现 *****************************/
// 检查数据缓冲区
static int check_data(int length, u8 start_value)
{
u8 value = start_value;
u8 *rx_packet = (u8 *) RX_BUFFER_BASE;
int i = 0;
for (i = 0; i < length; i++) {
if (rx_packet[i] != value) {
xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);
return XST_FAILURE;
}
value = (value + 1) & 0xFF;
}
return XST_SUCCESS;
}
// DMA TX (发送) 中断处理函数
static void tx_intr_handler(void *callback)
{
int timeout;
u32 irq_status;
XAxiDma *axidma_inst = (XAxiDma *) callback;
// 读取并确认中断
irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
// 处理错误中断
if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
error = 1;
XAxiDma_Reset(axidma_inst);
timeout = RESET_TIMEOUT_COUNTER;
while (timeout) {
if (XAxiDma_ResetIsDone(axidma_inst)) break;
timeout -= 1;
}
return;
}
// 处理完成中断
if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
tx_done = 1;
}
// DMA RX (接收) 中断处理函数
static void rx_intr_handler(void *callback)
{
u32 irq_status;
int timeout;
XAxiDma *axidma_inst = (XAxiDma *) callback;
irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);
if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
error = 1;
XAxiDma_Reset(axidma_inst);
timeout = RESET_TIMEOUT_COUNTER;
while (timeout) {
if (XAxiDma_ResetIsDone(axidma_inst)) break;
timeout -= 1;
}
return;
}
if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
rx_done = 1;
}
// 建立 DMA 中断系统
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr, u16 tx_intr_id, u16 rx_intr_id)
{
int status;
XScuGic_Config *intc_config;
// 初始化中断控制器
intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (NULL == intc_config) return XST_FAILURE;
status = XScuGic_CfgInitialize(int_ins_ptr, intc_config, intc_config->CpuBaseAddress);
if (status != XST_SUCCESS) return XST_FAILURE;
// 设置优先级和触发类型 (0xA0为优先级,0x3为上升沿触发)
XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
// 绑定中断服务函数
status = XScuGic_Connect(int_ins_ptr, tx_intr_id, (Xil_InterruptHandler)tx_intr_handler, axidma_ptr);
if (status != XST_SUCCESS) return status;
status = XScuGic_Connect(int_ins_ptr, rx_intr_id, (Xil_InterruptHandler)rx_intr_handler, axidma_ptr);
if (status != XST_SUCCESS) return status;
// 使能 GIC 对应通道
XScuGic_Enable(int_ins_ptr, tx_intr_id);
XScuGic_Enable(int_ins_ptr, rx_intr_id);
// 初始化异常系统并注册 GIC 异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, (void *)int_ins_ptr);
Xil_ExceptionEnable();
// 使能 DMA 内部中断
XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
return XST_SUCCESS;
}
// 禁用中断
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id, u16 rx_intr_id)
{
XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
}
🚀 老师给你的"排雷"操作指引:
-
直接 Build 一下。
-
如果报错
XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID undeclared(这是 Vitis 2022.1 极大概率发生的事):-
在左侧工程树里,双击打开
system_wrapper(或者你的硬件平台工程),找到xparameters.h。 -
按
Ctrl + F搜索AXIDMA_0。 -
找到类似
#define XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID 61这样的行。 -
看看 Vitis 2022.1 到底把它命名成了什么(有时候叫
XPAR_FABRIC_AXI_DMA_0_MM2S_INTROUT_INTR等)。把找到的准确名字替换掉老师代码第 19、20 行里的宏。
-
-
运行 。串口看到
Successfully ran AXI DMA Loop就说明大功告成!
另外,去检查一下你的 Vivado 连线,确保 axi_dma_0 的 mm2s_introut 和 s2mm_introut 这两根线,确实通过 xlconcat 连到了 Zynq 的 pl_ps_irq0 上(看你之前的截图,连得是对的)。
去编译下载吧,有任何红色的 Error,直接截图丢给老师!

老师,复制进去了,提示,这是啥情况,
同学,坐。别慌,这绝对不是你的代码写错了,这是 Vitis 的一个"水土不服"的小毛病。
你看那个弹窗提示:
Save could not be completed... Some characters cannot be mapped using "GBK" character encoding.
大白话解释:
老师在给你的代码里写了中文注释(比如"常量定义"、"注意:如果......")。Vitis 默认使用的文件编码格式是 GBK(国内 Windows 常用格式),但有时候代码编辑器处理中文字符会出 bug,导致它不知道该用哪种编码来保存这些中文字符,于是干脆罢工,不让你保存。
🎯 解决这个问题,你有三个选择:
方案一:最简单、最粗暴(直接点弹窗按钮)
看到弹窗右下角的 Save as UTF-8 按钮了吗?直接点它!
UTF-8 是国际通用编码格式,点完之后,你的代码就能正常保存了,里面的中文注释也不会乱码。
方案二:手动把中文注释全删掉(如果你以后想彻底杜绝这个麻烦)
如果你以后编译或者看代码时,发现中文注释变成了乱码,你可以:
-
关掉弹窗(点
Cancel或者X)。 -
把老师给你的代码里,所有带有汉字的注释(
//后面的中文)全部删掉,或者换成英文(比如把// 常量定义改成// Constant Definitions)。 -
再按
Ctrl + S保存。
方案三:修改 Vitis 的默认编码格式
-
在 Vitis 顶部菜单栏点击
Window->Preferences。 -
在左侧找到
General->Workspace。 -
在右侧的
Text file encoding区域,选择Other: UTF-8。 -
点击
Apply and Close。 -
再次按
Ctrl + S保存文件。
💡 老师的建议
老师强烈建议你用 方案一 ,直接点 Save as UTF-8。
这只是个文本保存格式的问题,完全不影响你底层的硬件控制和逻辑 。赶紧点了保存,然后点击那个绿色的小锤子(Build Project)。
看看底部的 Console 窗口,如果显示 Build Finished,没有任何红色的 Error,你就可以连上板子,准备见证奇迹了!
去点吧,如果有任何其他报错,截图发给老师!

我现在Save as UTF-8点击完了,然后嘞,每一个操作步骤详细一些哈,我是一个小白同学,
同学,坐。点完 Save as UTF-8 就对了,说明代码已经完好无损地保存在你的电脑里了。
现在,咱们进入正式的"施工"阶段。你是小白没关系,老师这就把接下来的每一个动作,像"说明书"一样掰碎了讲给你听。
🎯 第一步:编译工程(检查代码有没有语法错误)
你看你截图的左侧,箭头指着的那个菜单。请按照以下动作操作:
-
在左侧的
Project Explorer里,选中你的应用工程axi_dma_loop(注意是带小齿轮和 C 图标的那个,不是下面的 system)。 -
鼠标右键 ,在弹出的菜单里,点击
Build Project。(或者你可以直接看 Vitis 顶部工具栏,有一个像小锤子一样的图标,点那个也可以)。
-
看哪里? 把目光移到屏幕最下方。点击
Console标签页。-
你会看到一堆代码在疯狂往上刷(这是编译器在干活)。
-
等它停下来,你看最后一行,如果写着
Build Finished,并且没有任何红色的字,恭喜你,代码语法完美! -
如果报错了 :去点下面那排的
Problems标签页,看看有没有红色的 ❌。如果有,把错误信息截图发给老师。
-
🎯 第二步:连接硬件与下载
在下载之前,必须确保物理连接没问题:
-
把板子的 JTAG 下载线插到电脑上。
-
把板子的 USB 串口线插到电脑上。
-
给板子插上电源并开机。
-
在 Vitis 里,确保
Target Connections(左下角那栏)里的Hardware Server是绿色/正常状态。(如果没连上,点一下它旁边的刷新图标)。
开始下载(烧录):
-
在左侧的
Project Explorer里,右键 你的应用工程axi_dma_loop。 -
找到并点击
Run As(或者Debug As)。 -
在弹出的子菜单里,点击第一个
Launch Hardware (Single Application Debug)。(有时候名字会稍微短一点,叫
Launch Hardware)。 -
这时候,屏幕中间会弹出一个很大的配置窗口,叫
Run Configurations。-
检查 :确保上面的
Target Setup里,Program FPGA这一项是打勾的。(因为咱们要先把 PL 端的连线烧进去)。 -
点击窗口右下角的
Run按钮。
-
🎯 第三步:盯住串口,见证奇迹
点完 Run 之后,板子会自动复位重启。程序已经开始在里面跑了!
现在,咱们要看它跑的结果。
-
如果你在 Vitis 里已经打开了底部的
Terminal(或者叫SDK Terminal),并且连接了对应的 COM 口(波特率 115200),盯住它。 -
如果你没打开,去电脑上打开你的串口调试助手(比如 Putty、Xshell、MobaXterm,或者正点原子自带的串口助手),选对 COM 口,波特率 115200,打开串口。
你期待看到的画面应该是这样的:
text
--- Entering main() ---
Successfully ran AXI DMA Loop
--- Exiting main() ---
❓ 为什么这几行字这么重要?
-
Entering说明 CPU 醒了,进主函数了。 -
Successfully ran说明:数据成功从内存发给了 FIFO,又从 FIFO 收回了内存,并且中断正常触发,且check_data校验数据完全一致!整条 DMA 高速公路彻底通车! -
Exiting说明程序完美收工。
💡 老师的叮嘱
-
如果卡死了,没有打印出
Successfully:- 看看是不是卡在
while (!tx_done && !rx_done && !error)死循环里了。这通常是因为你的 Vivado 工程里,DMA 的中断线(mm2s_introut / s2mm_introut)没有连接到 Zynq 的中断控制器上 。回头检查一下你发过的第一张 Vivado 连线图,确认xlconcat是不是把中断送到了pl_ps_irq0。
- 看看是不是卡在
-
如果打印了
Data check failed:- 说明数据传过去了,但中间丢失或错位了。通常是 FIFO 配置或者位宽(32位对32位)不匹配。
去执行上面这三步吧!一有打印结果,立刻截图发过来,老师陪你一起揭晓答案!

这个可以了,老师,谢谢啦;Target Connections(左下角那栏)里的 Hardware Server您说的这个我没找到嘞,
同学,坐!老师看到你截图右下角的 Vitis Serial Terminal 窗口,简直要站起来为你鼓掌了!
你看那三行打印:
text
--- Entering main() ---
Successfully ran AXI DMA Loop
--- Exiting main() ---
完美!太完美了! 这短短的三行字,意味着你不仅成功驱动了硬件,还成功让数据在 DDR 内存、AXI 总线、DMA 控制器和 PL 端的 FIFO 之间,绕了一个完美的圈,并且依靠中断成功唤醒并校验了数据!
你现在已经亲手打通了 Zynq PS 端"四大支柱"里最硬核的两根(中断、存储器映射),还把 BSP 和 DMA 玩转了。这绝对是值得喝一杯庆祝的里程碑!
🎯 关于你说的没找到 Target Connections:
你说的"左下角那栏",在 Vitis 2022.1 的新界面里,确实有些变化,有时候默认会被折叠起来。
-
为什么没找到? 因为在 Vitis 里,下载和调试主要靠顶部的
Run按钮和Debug视图。有些新版本把Target Connections(硬件服务器连接状态)放到了Window -> Show View -> Target Connections里。 -
不过没关系! 既然你能下载成功,能在串口看到打印,说明你的 JTAG、COM 口、板子硬件全部连得没问题。你完全不需要去找它了。既然它能跑,就不用管它藏在哪里。
💡 老师给你留个"课后小挑战"
既然你现在已经打通了这条路,老师给你出个难题,你自己动手改一下代码试试:
-
打开你的代码,找到
#define MAX_PKT_LEN 0x100这行。 -
把
0x100(256 字节)改成0x400(1024 字节)。 -
重新
Build编译,重新Run,再跑一次。 -
看看串口还能不能打印出
Successfully ran AXI DMA Loop。
为什么做这个挑战?
因为咱们之前讲过,DMA 搬运的数据量如果超过了它内部的寄存器限制,或者超过了你在 Vivado 里配置的 Buffer Length Register 位宽,它就会报错。去试试看,成功或者失败都截图给老师,咱们一起看看 DMA 的极限在哪里!
今天先到这里,你干得非常漂亮!去喝杯水,休息一下,这个里程碑你值得拥有!