ZYNQ 7020 PS端SPI读写实战:SDK驱动与片选时钟避坑指南

发布时间:2026/8/31 18:20:46
ZYNQ 7020 PS端SPI读写实战:SDK驱动与片选时钟避坑指南 简介本资源是面向嵌入式开发工程师与FPGA协同开发者的一套ZYNQ 7020平台SPI外设驱动实践方案聚焦于ARM端SDK环境下标准SPI读写功能的完整实现解决软硬件协同调试中驱动初始化、时序配置、中断响应及数据收发等核心问题。压缩包共含多个C源文件、头文件、BSP配置脚本及Xilinx SDK工程文件涵盖SPI控制器初始化、主模式读写函数、极性/相位参数配置、超时错误处理及基础寄存器访问逻辑总大小为7.98MB。已有405人学习下载适用于ZYNQ初学者进阶实践或工业通信模块快速原型开发。读者可直接导入SDK工程编译运行结合硬件平台验证SPI通信时序与数据一致性并参考代码结构理解Xilinx官方驱动框架设计逻辑为后续扩展DMA传输或从设备驱动开发奠定坚实基础。 在ZYNQ 7020上做SPI读写这个问题我在实际项目里被问过太多次了。很多人一提到FPGA做SPI第一反应就是在PL侧用Verilog/VHDL撸一个SPI时序或者直接调用Xilinx的SPI IP核完全不记得PS侧本身就带着两个SPI控制器。其实ZYNQ-7020的PS端集成了两个SPI控制器SPI0和SPI1支持主从模式、FIFO缓冲、三档片选用SDK驱动操作起来比在PL里写时序快得多尤其在数据量不大、时序要求不极端的场景下PS端SPI完全够用。这篇文章就是围绕ZYNQ 7020用SDK驱动实现SPI读写这条主线来写的内容包括Vivado里的硬件搭建、SDK里的驱动调用逻辑、片选与时钟这两个最容易翻车的点以及最终用回环测试和真实SPI设备验证。适合两类人看一类是刚接触ZYNQ、在PS和PL之间纠结选哪条路的初学者另一类是已经在用PS端SPI但被片选、FIFO、时钟分频问题卡了几天的工程师。1. 为什么在7020上做SPI通信很多人第一个想到的是PL而不是PS这个现象很有意思。我在不少技术交流和培训场合问过同样的问题ZYNQ做SPI通信你会用PS还是PL大多数人的第一反应是PL理由无外乎FPGA不就是干这个的嘛时序可控、速率高。但如果你的SPI设备速率需求在几十MHz以内、通信逻辑不复杂PS端自带的SPI控制器反而是更务实的选择。1.1 PS端SPI控制器到底能干什么ZYNQ-7020的PS端集成两个SPI控制器每个控制器具备以下能力支持SPI主机模式Master和从机模式Slave多数应用用到的是主机模式。支持标准SPI、双通道Dual、四通道Quad模式在连接SPI Flash这类器件时比较有用。内置128字节的收发FIFO发送和接收各64字节。提供3个片选信号SS0、SS1、SS2可以挂多个从设备。支持时钟极性和相位可编程配置对应SPI Mode 0到Mode 3。时钟分频由内部波特率发生器完成可以按需配置SCLK频率。这些特性意味着PS端SPI不是阉割版的SPI而是一个功能完整的控制器。尤其是三档片选和Dual/Quad模式比你自己在PL里写Verilog要省事得多。1.2 什么时候必须用PL做SPIPS端SPI控制器当然不是万能的下面几种情况我会建议走PL路线时钟频率要求超过PS控制器上限。PS端SPI的SCLK最高频率取决于参考时钟和分频比通常到几十MHz没问题但如果你追求100MHz以上就要用PL侧的SPI IP或者自己写RTL。需要自定义时序。比如某些传感器要求特殊的CS保持时间、特定字节间隙或者需要多字节连续传输过程中切换MOSI方向这些定制化要求用PS固定控制器很难满足。需要高密度并行多路SPI。如果你的板子上有8路、16路SPI从设备需要同时采集PS端两个控制器就不够用了PL侧例化多个SPI IP更现实。需要把SPI数据直接接入DMA或自定义数据通路。虽然PS端SPI也可以配合DMA但灵活性不如PL侧自己掌控。所以在项目启动阶段先做一个判断如果速率不极端、协议不诡异、路数不多直接用PS端SPI能省下不少开发和调试时间。1.3 一个容易忽略的前提MIO还是EMIO在Vivado里配置PS端SPI时引脚可以选择落在MIO上也可以引到EMIO再通过引脚约束连到PL侧的普通IO。MIO路线SPI引脚直接连接到PS的MIO引脚不需要在约束文件里写引脚位置上电即用不占PL逻辑资源。缺点是MIO引脚位置是固定的如果PCB上没有把SPI信号引到你想要的引脚就没得选。EMIO路线SPI控制器信号先从PS出来通过EMIO接口进入PL再通过普通IO引脚外接。优点是可以自由选择引脚位置灵活性更高缺点是要手动添加引脚约束并且占用了PL侧的IO资源。在7020上MIO引脚有限如果MIO已经被UART、SDIO、USB等占满或者PCB布局导致MIO无法走线那就只能走EMIO。这个选择在Block Design阶段就要确定后面改起来比较麻烦。2. Block Design里SPI外设的搭建与引脚分配陷阱确定了走PS端SPI之后Vivado里的操作其实很直接但有几个细节不留意会卡住后面SDK的开发。2.1 使能SPI控制器的完整步骤在Vivado里新建或打开Block Design添加ZYNQ7 Processing System IP核后需要双击该IP核进入配置界面在Peripheral IO选项里把SPI 0或者SPI 1勾上。我一般习惯勾选SPI 1因为SPI 0的MIO引脚经常和SD卡、NAND等接口复用容易冲突。勾选SPI之后还需要注意以下几点在MIO Configuration里确认SPI引脚的MIO编号避免与其他外设冲突。如果想走EMIO则不要勾选MIO上的SPI引脚而是在SPI配置里选择EMIO然后引出SPI0/1的接口信号。如果使用EMIO需要在Constraints文件中添加引脚约束生成比特流bitstream。在时钟配置里确保给SPI控制器所在的处理器时钟域提供合理的参考时钟ZYNQ的SPI控制器时钟来自CPU的IO PLL或者外设时钟域通常默认的100MHz就行。导出硬件时注意勾选包括比特流选项因为EMIO方式下的PL引脚约束信息需要通过比特流加载。2.2 硬件平台导出与SDK工程创建这一步很多人是因为操作顺序不对而卡住。无论用Vivado 2019.1还是Vitis 2024核心逻辑都是一样的在Vivado中完成Block Design和约束后生成比特流。File - Export Hardware选择包括比特流生成XSA文件。打开Vitis老版本叫SDK创建一个新的平台工程Platform Project指向这个XSA文件。基于该平台工程创建应用工程Application Project。在应用工程中可以用Xilinx提供的模板也可以手动添加C文件编写SPI读写代码。在Vitis 2024版本中流程基本一致只是界面和术语有些变化但核心仍然是XSA文件承载硬件配置信息。遇到过不少人在这一步忘记包括比特流导致下载运行时PL侧没有任何逻辑EMIO引脚自然也不工作。2.3 引脚分配中让我踩过一次的坑有一次我把SPI信号通过EMIO引出到PL引脚在约束文件里写了引脚位置生成了比特流然后下载进板子发现SPI时钟完全没输出。排查了半天最后发现是复位顺序的问题PS端SPI控制器在软件初始化之前EMIO引脚的默认状态是不确定的而且PL侧的IO需要等待配置完成和释放复位。如果是通过SDK下载程序自动加载比特流IO初始状态受硬件约束管理但如果手动单独下载比特流再运行程序就可能出现IO未被正确初始化的情况。这类问题其实有章可循排查时先确认引脚约束语法再确认EMIO接口信号方向最后确认PS-PL复位顺序。多数情况下问题出在缺少或写错了引脚约束或者导出XSA时未包含比特流。3. SDK驱动中SPI初始化的关键参数与执行流程到了SDK阶段主要工作就是熟悉Xilinx提供的SPI驱动库。PS端SPI的驱动文件是xspips.h和xspips.c底层实现已经封装好了但有几个参数和调用顺序初学者特别容易理解错。3.1 SPI驱动初始化的标准代码用PS端SPI做主机初始化流程一般是查找设备配置、初始化驱动、配置工作模式、配置片选、配置时钟分频。以下是我在7020上用SPI1的初始化代码示例#include xspips.h #include xparameters.h #include xstatus.h #define SPI_DEVICE_ID XPAR_XSPIPS_1_DEVICE_ID #define SPI_CLOCK_HZ 1000000 // 期望的SCLK频率 XSpiPs SpiInstance; XSpiPs_Config *SpiConfig; int SpiInit(void) { int Status; // 1. 查找SPI控制器配置 SpiConfig XSpiPs_LookupConfig(SPI_DEVICE_ID); if (SpiConfig NULL) { return XST_FAILURE; } // 2. 初始化SPI控制器 Status XSpiPs_CfgInitialize(SpiInstance, SpiConfig, SpiConfig-BaseAddress); if (Status ! XST_SUCCESS) { return XST_FAILURE; } // 3. 设置为主机模式默认禁止从机模式 Status XSpiPs_SetOptions(SpiInstance, XSPIPS_MASTER_OPTION | XSPIPS_MANUAL_CS_OPTION); if (Status ! XST_SUCCESS) { return XST_FAILURE; } // 4. 设置时钟分频使SCLK接近目标频率 Status XSpiPs_SetClkPrescaler(SpiInstance, SpiConfig-MaxFrequency); if (Status ! XST_SUCCESS) { return XST_FAILURE; } // 5. 默认选中SS0 XSpiPs_SetSlaveSelect(SpiInstance, 0x01); return XST_SUCCESS; }代码里有一个地方特别容易误用XSpiPs_SetClkPrescaler的第二个参数很多资料写的是分频值但实际上它的含义是期望的SCLK频率驱动会根据这个值计算出实际分频比再配置到寄存器里。如果你直接传一个分频比数字得到的时钟频率和你预期的会差很多。3.2 初始化背后发生了什么XSpiPs_CfgInitialize做的事比较多读取硬件配置确认SPI控制器基地址。复位SPI控制器把所有寄存器的值清零到默认状态。禁止SPI控制器等待后续配置完毕后再使能。配置中断相关寄存器。XSpiPs_SetOptions设置控制器的工作模式。常用的选项有XSPIPS_MASTER_OPTION主机模式。XSPIPS_SLAVE_OPTION从机模式。XSPIPS_MANUAL_CS_OPTION手动控制片选由软件通过寄存器直接选择从设备。XSPIPS_FORCE_CS_OPTION强制片选在整个传输过程中保持有效。有一点要特别注意XSpiPs_SetOptions里如果不加XSPIPS_MANUAL_CS_OPTION控制器会自动管理片选信号在每次事务结束后片选会恢复为无效。但如果使用XSPIPS_MANUAL_CS_OPTION就需要自己调用XSpiPs_SetSlaveSelect来选择要操作的从设备。这个手动和自动的选择会直接影响你后面读多字节数据的稳定性和CS时序。3.3 中断方式还是轮询方式SDK里的SPI驱动支持轮询和中断两种模式。轮询模式简单直接调用XSpiPs_PolledTransfer或XSpiPs_Transfer即可中断模式需要初始化中断控制器、注册中断回调函数代码量更大但更稳定特别是在和操作系统或复杂任务并发时。我个人的经验是裸机环境下如果SPI通信数据量不大且对实时性要求不高直接用轮询模式就够了代码简单出问题也好排查。但如果是64字节以上的数据块传输或者需要在后台持续采集传感器数据建议上中断因为SPI控制器的FIFO只有64字节轮询大数据块时如果没有及时读取接收FIFO很容易溢出丢数据。4. 硬件片选与软件片选的取舍多设备场景下的实战选择片选问题是ZYNQ SPI开发里被问得最多的点之一。在SDK驱动里片选有硬件自动管理和软件手动控制两种方式网上关于这两者的讨论也比较多这里结合7020的实际行为展开说说。4.1 硬件片选与软件片选的行为差异所谓硬件片选就是SPI控制器在启动一个传输事务时会自动把对应的片选信号拉低传输结束后自动拉高。软件片选则是由软件通过寄存器设置片选电平之后不依赖控制器自动翻转。在SDK驱动中使用XSPIPS_FORCE_CS_OPTION选项可以实现强制片选即片选信号在整个传输期间保持有效适用于需要多个SPI事务连续操作同一个从设备比如读SPI Flash的JEDEC ID、读状态寄存器的场景。两者有一个关键区别片选方式选项适用场景注意点硬件自动片选默认每个事务独立传输事务之间CS会拉高连续读操作可能被从设备当作一次传输结束软件手动片选XSPIPS_MANUAL_CS_OPTION多个事务连续操作同一个设备需要手动设置SlaveSelect结束后手动释放强制有效片选XSPIPS_FORCE_CS_OPTION读操作需要连续时钟的场景只影响当前事务事务结束自动释放例如读AD7606这种ADC芯片读取转换结果时需要CS在整个读取过程中保持低电平如果使用默认的自动片选每个字节之间CS会拉高ADC可能认为本次读取结束返回的数据就不对了。这时候就要结合XSPIPS_FORCE_CS_OPTION或者手动控制GPIO实现CS。4.2 多设备挂载时的地址选择7020的每个SPI控制器有3个片选信号SS0、SS1、SS2理论上可以挂3个SPI从设备。通过XSpiPs_SetSlaveSelect(SpiInstance, slave_mask)选择具体设备slave_mask是位掩码例如0x01对应SS00x02对应SS10x04对应SS2。实际的业务场景里我踩过一个坑SPIFlash挂在SS1上传感器挂在SS0上程序运行时初始化代码里执行了XSpiPs_SetSlaveSelect(SpiInstance, 0x01)选择Flash读完Flash之后切换到传感器执行XSpiPs_SetSlaveSelect(SpiInstance, 0x02)但紧接着读取传感器数据时发现读出来的全是0xFF。排查后发现是片选切换的时序问题从一个从设备切换到另一个从设备时需要保证前一个事务彻底结束片选信号完全释放再切换下一个片选。我在代码里加了一点延时问题就解决了。4.3 片选信号不够用怎么办如果需要挂载超过3个SPI从设备可以用GPIO控制额外的CS引脚实现软件扩展片选。做法是在Block Design里把额外的CS引脚配置为GPIOMIO或者EMIO都可以。SDK初始化阶段把这些GPIO配置为输出并拉高初始化无效状态。操作某个设备时先把对应GPIO拉低选通设备操作完后再拉高。这种方式其实就是手动扩展片选在嵌入式系统里很常见。不过需要注意如果使用PS端GPIOMIO控制片选GPIO的翻转速度比SPI控制器内部的硬件片选更快如果GPIO拉低后立刻发起SPI事务某些从设备可能因为CS建立时间太短而无法响应。建议在GPIO拉低后加一小段延时或者至少确保CS低电平的建立时间满足从设备手册要求。5. 回环测试和真实设备读写验证驱动是否真正可用的标准动作初始化代码写完、编译下载之后不要急着接外部设备先在板子上做一套完整的验证流程。SPI通信最常见的故障就是代码看起来没问题但就是读不到数据这时候回环测试是最有效的定位手段。5.1 自环测试把MOSI和MISO短接一种最直接的验证方法就是把SPI主机的MOSI和MISO在物理上短接。这样主机发送的数据会被自己的接收通道收到可以验证整个SPI控制器的收发链路是否正常。在SDK中实现自环测试很简洁int SpiLoopbackTest(void) { u8 SendBuffer[8] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; u8 RecvBuffer[8] {0}; int Status XSpiPs_PolledTransfer(SpiInstance, SendBuffer, RecvBuffer, 8); if (Status ! XST_SUCCESS) { return XST_FAILURE; } for (int i 0; i 8; i) { if (SendBuffer[i] ! RecvBuffer[i]) { return XST_FAILURE; } } return XST_SUCCESS; }这里要注意的是字节数和FIFO的关系。SPI控制器的接收FIFO为64字节如果你一次传输超过64字节轮询模式下必须及时读取FIFO否则会溢出。我建议自环测试时先传小于64字节的数据确认链路正常后再扩展。还有一个细节自环测试通过后只能说明PS端SPI控制器自身收发没问题不能证明外部SPI设备能正常工作。外部设备还需要单独验证。5.2 真实设备读写以SPI Flash为例自环测试通过后就可以接真实设备了。我以W25Q16 SPI Flash为例说明一个完整的读写流程读JEDEC ID是最简单的验证因为不涉及写操作只需要发送9个时钟周期的命令就能拿到3字节的设备ID。int SpiReadJedecId(u8 *pId) { u8 WriteBuf[4] {0x9F, 0x00, 0x00, 0x00}; // JEDEC ID命令 u8 ReadBuf[4] {0}; int Status XSpiPs_PolledTransfer(SpiInstance, WriteBuf, ReadBuf, 4); if (Status ! XST_SUCCESS) { return XST_FAILURE; } pId[0] ReadBuf[1]; pId[1] ReadBuf[2]; pId[2] ReadBuf[3]; return XST_SUCCESS; }JEDEC ID读出来是3个字节比如W25Q16的ID是0xEF 0x40 0x15。如果你读到的全是0xFF往往说明片选没有选中设备、SPI模式不匹配、或者时钟频率太高导致从设备无法响应。5.3 读状态寄存器与写使能SPI Flash的写操作比读操作麻烦一些必须遵循写使能 - 擦除/写入 - 等待完成的流程。在等待Flash内部操作完成时通常通过读状态寄存器0x05命令判断BUSY位。int SpiWaitFlashReady(void) { u8 WriteBuf[2] {0x05, 0x00}; // 读状态寄存器命令 u8 ReadBuf[2] {0}; int Timeout 100000; while (Timeout--) { XSpiPs_PolledTransfer(SpiInstance, WriteBuf, ReadBuf, 2); if ((ReadBuf[1] 0x01) 0) { // BUSY位为0 return XST_SUCCESS; } } return XST_FAILURE; }等待循环里加一个超时计数很重要。有一次我调试Flash读ID正常但写数据总是不成功程序卡在忙等待里出不来添加超时后定位到是Flash的写保护寄存器被设置了导致写操作始终没有被接受。如果不用超时机制这种问题很难自动发现。5.4 遇到读不到数据时的排查顺序SPI调试是一个典型的信号链路逐级排查过程。我总结了一套排查顺序供参考先确认时钟是否输出。用示波器或逻辑分析仪测SCLK引脚如果没有时钟问题在控制器配置或引脚约束。确认MOSI是否输出。发送数据时观察MOSI引脚如果没有数据检查发送缓冲区和命令是否正确。确认CS是否正确拉低。如果CS信号始终为高从设备根本不会响应。确认MISO信号是否回来。如果MISO始终为高或低说明从设备没有驱动MISO可能是SPI模式不匹配或设备未选通。检查电平标准。ZYNQ的MIO和PL的IO大多支持1.8V/2.5V/3.3V如果外部设备的电平域和ZYNQ不一致需要在两者之间加电平转换芯片。排查过程中示波器和逻辑分析仪几乎是必需品特别是查SPI时序问题不能只靠软件打印数据。回环测试是排除软件问题的利器把发送和接收短接后如果数据一致说明软件层没问题问题大概率在硬件连接或外部设备配置上。6. 时钟频率、SPI工作模式与常见Error的对应关系这一节单独拿出来讲是因为很多稀奇古怪的问题归根结底都出在时钟或SPI Mode不匹配上而且SDK里返回的错误码往往不会直接告诉你时钟频率太高或者模式不对需要自己对照手册分析。6.1 SPI Mode的选择与被遗忘的CPOL/CPHASPI通信有4种模式由时钟极性CPOL和时钟相位CPHA决定。很多从设备手册上会写SPI Mode 0或SPI Mode 3但没有解释这意味着什么。简单回忆一下Mode 0CPOL0, CPHA0空闲时SCLK为低电平数据在第一个边沿上升沿采样。Mode 1CPOL0, CPHA1空闲时SCLK为低电平数据在第二个边沿下降沿采样。Mode 2CPOL1, CPHA0空闲时SCLK为高电平数据在第一个边沿下降沿采样。Mode 3CPOL1, CPHA1空闲时SCLK为高电平数据在第二个边沿上升沿采样。在SDK驱动中通过XSpiPs_SetOptions的XSPIPS_CLK_PHASE_OPTION和XSPIPS_CLK_POLARITY_OPTION来配置// 配置为SPI Mode 0 Status XSpiPs_SetOptions(SpiInstance, XSPIPS_MASTER_OPTION | XSPIPS_MANUAL_CS_OPTION); // 配置为SPI Mode 3 Status XSpiPs_SetOptions(SpiInstance, XSPIPS_MASTER_OPTION | XSPIPS_MANUAL_CS_OPTION | XSPIPS_CLK_POLARITY_OPTION | XSPIPS_CLK_PHASE_OPTION);我遇到过一次很典型的故障用SPI读取一颗ADC芯片的数据配置的是Mode 0读出来的数据偶尔会跳变。后来用逻辑分析仪抓时序MISO上的数据在采样边沿附近发生变化导致采样点不稳定。改成Mode 3之后就稳定了。原因很简单这颗ADC手册里要求的是Mode 3我默认按Mode 0配置了。所以在连接新设备之前第一件事就是查清楚该设备支持的SPI Mode不要想当然。很多设备手册里表格式的时序图会标明默认的Mode直接照抄即可。6.2 时钟分频计算与最大频率限制SPI控制器的SCLK频率由参考时钟和波特率分频器共同决定。SDK驱动里XSpiPs_SetClkPrescaler的第二个参数是期望频率驱动会计算实际分频比。但需要注意实际得到的SCLK频率可能并不是期望值而是接近期望值的某个分频结果。ZYNQ PS端SPI时钟分频寄存器是 8-bit 的支持的分频系数一般是 2、4、6、8 ... 256 这些偶数实际上还会受到参考时钟的限制。因此如果期望频率设得很高超出了最大允许频率驱动会返回错误或者自动配置出接近的值。我在实际项目中曾试图把SCLK配到40MHz结果读取外部ADC信号时发现转换结果经常出现丢位的情况。排查后发现不是SPI控制器的速度上限不够而是PCB走线过长信号完整性变差40MHz的边沿太陡反射和串扰导致采样点不可靠。后来把SCLK降到10MHz就稳定了。经验法则SPI走线长度超过5cm时尽量不要把SCLK配到20MHz以上超过10cm时建议降到10MHz以下。这个不是绝对标准但能避开大部分信号完整性问题。6.3 SDK返回Error的常见含义与处理开发过程中经常会在终端里看到SPI驱动返回错误码。我自己整理了一个对照表错误现象可能原因处理方式XST_SUCCESS返回错误参数非法或初始化未完成检查设备ID、基地址和初始化顺序传输数据不对全0xFF从设备未选中或MISO未连接检查CS、MISO接线和电平标准传输数据不对全0x00MOSI未连接或从设备未驱动MISO检查MOSI连接SCLK无输出控制器未使能或时钟分频配置错误检查控制器使能状态和分频寄存器偶发数据错误时钟频率过高或信号完整性差降低SCLK检查PCB走线读操作CS期间被拉高使用了自动片选每个字节间CS释放改用FORCE_CS或手动控制GPIO尤其注意一点SDK驱动在XSpiPs_PolledTransfer返回失败时并不总是说明总线错误有时候是参数校验没过比如传输的字节数为0、缓冲区为空等。这类软件层面的校验错误在调试时很容易被误认为是硬件问题所以先确保传入参数没问题再朝硬件方向排查。6.4 与DMA配合时的同步问题PS端SPI控制器支持DMA传输适合大数据量的SPI读写。在SDK里使用DMA模式需要额外初始化DMA控制器配置传输方向、长度和中断。相比轮询模式DMA模式能减轻CPU负担但调试复杂度也会上升。如果是为了提高吞吐量而用DMA需要注意两点SPI控制器和DMA之间的握手信号需要正确配置尤其是XSpiPs_SetDmaOptions和中断回调函数里的状态判断。DMA传输的字节粒度可能有对齐要求比如4字节对齐不满足时会出现总线错误。实际项目里如果只是偶尔读写几十个字节DMA带来的收益并不明显轮询模式反而是最简单可靠的方案。DMA用在一个特定的高速数据采集项目上当时要求SPI连续读取ADC数据每秒产生几兆字节的数据轮询模式确实扛不住DMA模式才能稳定跑。不过DMA的初始化配置明显更复杂我的建议是先用轮询模式把业务逻辑跑通再根据瓶颈决定是否迁移到DMA。7. 从SDK到Vitis工具链迁移中的几个适配点很多人的开发环境已经从老的Xilinx SDK迁移到Vitis或者正在迁移的路上。Vitis 2024里如何烧录7020、如何加载PL比特流、如何调试PS端应用这些问题在SPI开发过程中同样会遇到。7.1 Vitis下SPI工程仍然使用XSpiPs驱动Vitis替代了SDK之后SPI驱动API并没有变化仍然是XSpiPs_* 这一套接口Xilinx从传统驱动模型过渡到embeddedsw仓库但驱动源码、函数名、行为基本保持一致。所以网上的SDK路径经验教程包括本文的所有代码在Vitis里依然适用。有一个变化值得注意Vitis里创建应用工程时默认会生成一个helloworld框架SPI初始化代码需要自己加到main函数里。同时Vitis对编译选项和库的依赖更严格比如如果用到中断需要确保使能了对应的驱动库。7.2 烧录与启动方式从JTAG下载到QSPI固化SPI开发过程中通常先用JTAG调试模式下载程序。当功能验证完成后如果需要上电自启动就需要把程序固化到启动设备中。ZYNQ 7020的启动模式支持QSPI Flash、SD卡、JTAG等。这里提一下QSPI固化的大致流程在Vivado里生成BOOT.BIN所需的三个文件FSBLFirst Stage Boot Loader、bitstream、应用程序elf。使用Xilinx提供的创建启动镜像功能生成BOOT.BIN。通过JTAG使用Vivado的Program Flash工具把BOOT.BIN写入QSPI Flash。拨动启动模式跳线到QSPI重新上电观察启动是否正常。如果SPI控制器挂在PS的MIO上固化后启动时不需要PL的参与SPI控制器在FSBL阶段就可以工作。如果SPI引脚通过EMIO接到了PL侧那么启动后必须等FPGA加载完成SPI引脚才会生效。这个区别在做上电自测时容易被忽略表现为JTAG调试时SPI通信正常固化后上电却读不到设备其实就是PL还没来得及加载。7.3 不带DDR的7020OCM加载方式还有一个不少人在网上问的问题如果板子上没有DDRZYNQ 7020还能跑SPI读写程序吗答案是能但要使用OCMOn-Chip Memory。ZYNQ-7020的OCM总容量是256KB程序和数据都放在OCM里足够跑一个轻量级的SPI读写应用。在Vitis里设置链接脚本把代码段、数据段、堆栈都放到OCM地址段编译时去掉DDR相关地址即可运行。这种方式适合内存需求小的场景比如做一个简单的SPI Flash读取器、传感器采集器。但要注意OCM容量有限不能跑复杂的嵌入式系统也不能分配太大的缓冲区。这一块在实际项目里我用过一次帮一个硬件工程师调试一块不带DDR的最小系统板PS端跑一个SPI驱动从一颗温湿度传感器读取数据然后通过串口打印。因为代码量非常小OCM完全够用硬是把一个看似缺少内存无法跑系统的板子做成了可用的传感器测试工具。8. 项目做完之后我最后想说的几个点写到这里ZYNQ 7020上实现SPI读写的主要技术环节都已经过了一遍。最后分享几个实际项目中的体会可能对你有帮助。如果你正在纠结PS端SPI还是PL端SPI我的建议是先评估外部设备的速率和协议复杂度不要一上来就写Verilog。PS端SPI在7020上是一套成熟、稳定、代码量小的方案配合SDK驱动从搭建到调通熟练的话半天就能搞定。而PL端SPI虽然灵活但代码量、仿真调试成本都高不少。除非你明确知道PS端SPI满足不了需求否则优先用PS端。如果你已经被片选、时钟分频这类问题卡住了建议不要反复猜测直接上逻辑分析仪或者示波器抓信号。SPI就是一个实打实的串行协议时序对不对信号一看就知道。我调试SPI踩过的坑绝大多数都是没看时序就开始改代码浪费时间不说还容易把简单问题复杂化。如果这个项目后续要扩展到Linux系统那么SPI控制器的设备树配置、SPI驱动框架等又是一套新知识需要另外单独梳理。裸机SDK的开发路径是理解ZYNQ底层寄存器行为的最佳方式先把裸机跑通再去做Linux下的SPI移植思路会清晰很多。本文还有配套的精品资源点击获取