ZedBoard上玩转AXI-Stream FIFO:手把手教你用PS程序点亮LED并理解数据流
ZedBoard实战:AXI-Stream FIFO数据传输与LED交互开发指南
当我们在ZYNQ平台上第一次看到自己编写的程序通过硬件真实地改变LED状态时,那种成就感是无可替代的。本文将带你完成一个完整的PS-PL交互项目,从代码编写到硬件行为观察,深入理解AXI-Stream协议的数据流动本质。
1. 项目环境搭建与硬件架构解析
在开始编写PS端程序前,我们需要先理解整个系统的硬件连接架构。根据Harald's Embedded Electronics教程搭建的Block Design包含三个关键组件:
- AXI-Stream FIFO:作为PS与PL之间的数据通道,负责缓冲和传输流式数据
- mySPI_Tx_AXIS:自定义IP核,将接收到的AXI-Stream数据转换为SPI协议格式
- myHeartbeat:另一个自定义IP核,将输入信号分频后驱动LED闪烁
提示:在Vivado中打开Block Design后,建议使用"Validate Design"功能确认所有接口连接正确,特别是AXI-Stream的信号方向。
硬件连接的核心在于AXI-Stream接口的四个关键信号:
| 信号名称 | 方向 | 作用描述 |
|---|---|---|
| TDATA | PS→PL | 传输的实际数据 |
| TVALID | PS→PL | 指示当前数据是否有效 |
| TREADY | PL→PS | 接收方准备就绪信号 |
| TLAST | PS→PL | 数据包结束标志 |
在ZedBoard上,这个设计最终会通过LED0的闪烁模式来直观反映数据传输状态。这种"代码→数据流→硬件行为"的闭环验证方式,是学习ZYNQ平台最有效的途径之一。
2. PS端程序开发详解
现在我们来深入分析PS端的C程序实现。完整的工程需要包含以下关键操作:
#include <stdio.h> #include "platform.h" #include "xil_printf.h" #include "xil_types.h" #include "xstatus.h" #include "xllfifo.h" #include "xparameters.h" // 定义常量 #define WORD_SIZE 4 // 32位字长(字节) #define MAX_PACKET_LEN 4 #define NO_OF_PACKETS 642.1 FIFO初始化与配置
任何外设操作前都需要正确的初始化,AXI-Stream FIFO也不例外:
XLlFifo fifo; // 创建FIFO实例 // 初始化FIFO配置 int xStatus = XLlFifo_CfgInitialize(&fifo, XLlFifo_LookupConfig(XPAR_AXI_FIFO_MM_S_0_DEVICE_ID), (UINTPTR)XPAR_AXI_FIFO_MM_S_0_BASEADDR); if(XST_SUCCESS != xStatus) { print("FIFO初始化失败\n\r"); return XST_FAILURE; }这段代码做了三件重要事情:
- 通过设备ID查找FIFO的硬件配置信息
- 将FIFO映射到PS的内存地址空间
- 验证初始化是否成功
注意:XPAR_AXI_FIFO_MM_S_0_DEVICE_ID和XPAR_AXI_FIFO_MM_S_0_BASEADDR这些宏定义是由Vivado根据硬件设计自动生成的,位于xparameters.h文件中。
2.2 数据传输流程
数据传输的核心在于理解FIFO的写入机制:
// 清除所有可能的中断状态 XLlFifo_IntClear(&fifo, 0xffffffff); // 主循环 for(;;) { print("按回车键发送数据...\n\r"); getchar(); // 等待用户输入 // 写入两个32位数据字 XLlFifo_TxPutWord(&fifo, 0xAAAAAAAA); // 二进制: 1010... XLlFifo_TxPutWord(&fifo, 0x55555555); // 二进制: 0101... // 设置传输长度(字节数) XLlFifo_TxSetLen(&fifo, 2 * WORD_SIZE); }这里有几个关键点需要注意:
- 数据写入FIFO后并不会立即传输,需要明确指定传输长度才会启动
- 0xAAAAAAAA和0x55555555的交替模式将在LED上产生明显的闪烁效果
- 每次传输前等待用户输入是为了方便观察每次数据传输的效果
3. 数据流分析与调试技巧
理解数据从PS到PL的完整路径对于调试至关重要。让我们分解整个传输过程:
PS端数据准备阶段
- 应用程序调用XLlFifo_TxPutWord写入数据
- 数据暂存在FIFO的发送缓冲区
- 调用XLlFifo_TxSetLen后,数据开始向PL端传输
PL端数据处理阶段
- mySPI_Tx_AXIS IP核接收AXI-Stream数据
- 数据被转换为SPI格式信号
- myHeartbeat IP核根据数据模式控制LED闪烁
调试时最常见的三个问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| LED完全不亮 | FIFO初始化失败 | 检查设备ID和基地址配置 |
| LED常亮不闪烁 | 数据传输未触发 | 确认调用了TxSetLen |
| 闪烁模式不符合预期 | 数据写入顺序错误 | 检查TxPutWord的调用顺序 |
一个实用的调试技巧是在关键节点添加状态输出:
// 检查FIFO状态 u32 status = XLlFifo_Status(&fifo); xil_printf("FIFO状态: 0x%08X\n\r", status); // 检查发送缓冲区剩余空间 u32 txVacancy = XLlFifo_TxVacancy(&fifo); xil_printf("发送缓冲区剩余: %d字\n\r", txVacancy);4. 项目扩展与进阶应用
掌握了基础的数据传输后,我们可以考虑以下几个方向的扩展:
4.1 动态数据模式生成
替换固定的0xAA和0x55模式,实现可编程的数据序列:
// 生成可编程的脉冲序列 void generatePattern(XLlFifo *fifo, u32 pattern[], int length) { for(int i=0; i<length; i++) { XLlFifo_TxPutWord(fifo, pattern[i]); } XLlFifo_TxSetLen(fifo, length * WORD_SIZE); }4.2 中断驱动传输
轮询方式效率较低,可以改为中断驱动:
// 设置中断处理函数 XLlFifo_IntHandler fifoHandler = &fifoInterruptHandler; XLlFifo_SetHandler(&fifo, fifoHandler, (void*)&fifo); // 启用发送完成中断 XLlFifo_IntEnable(&fifo, XLLF_INT_TC_MASK);4.3 性能优化技巧
对于高速数据传输场景,可以考虑:
- 使用DMA加速数据搬移
- 增大FIFO深度减少阻塞
- 优化PL端IP核的数据处理流水线
在实际项目中,我们可能会遇到需要传输更复杂数据格式的情况。例如,下面的表格展示了一个典型的数据帧结构:
| 字段 | 长度(字节) | 描述 |
|---|---|---|
| 帧头 | 4 | 固定值0xA5A5A5A5 |
| 数据长度 | 2 | 后续数据字段的长度 |
| 数据 | N | 实际有效数据 |
| CRC校验 | 2 | 整个帧的校验和 |
实现这样的协议需要在PS端进行相应的数据封装:
void sendFrame(XLlFifo *fifo, u8 *data, u16 length) { // 发送帧头 XLlFifo_TxPutWord(fifo, 0xA5A5A5A5); // 发送长度字段 XLlFifo_TxPutWord(fifo, length); // 发送实际数据 for(int i=0; i<length; i+=4) { u32 word = *(u32*)(data+i); XLlFifo_TxPutWord(fifo, word); } // 计算并发送CRC u16 crc = calculateCRC(data, length); XLlFifo_TxPutWord(fifo, crc); // 触发传输 XLlFifo_TxSetLen(fifo, (3 + (length+3)/4) * WORD_SIZE); }通过这个ZedBoard上的实践项目,我们不仅理解了AXI-Stream FIFO的工作原理,更重要的是建立起了PS与PL协同开发的完整思维框架。当看到LED按照预期开始闪烁时,所有的理论都变得具体而生动了。
