简介:针对Xilinx公司Zynq-7000系列现场可编程门阵列在国产化替代中,开发环境无法识别复旦微电子Nor型QSPI闪存FM25F32的问题,这套基于XC7Z020器件的验证工程给出了完整解决思路。作者利用自编烧写测试程序,绕过开发套件自带工具的器件限制,成功将二级引导加载程序与U-Boot写入闪存,并完成启动加载验证。压缩包共四百三十八个文件,以一百五十一个C源码文件和一百六十五个头文件为主体,辅以编译脚本、链接脚本、汇编文件及BOOT.bin等镜像文件,整体大小仅二点二七兆字节,结构清晰便于按目录查阅。其中涉及QSPI驱动、缓存管理、以太网控制器等示例代码,可深入理解闪存初始化、扇区擦除、写入状态轮询等底层实现。目前已有两千四百七十三人学习使用,适合从事Zynq国产化存储与启动方案开发的硬件工程师参考。 做Zynq开发的人应该都有体会:Vivado里跑通逻辑、写好PS端程序只是第一步,真正让板卡“上电就能跑”的烧写配置环节,才是最容易卡壳的地方。尤其当手里拿的不是常规的W25Q系列,而是国产复旦微的FM25F32时,很多现成的教程和脚本就不灵了——Flash ID对不上、擦除时序不兼容、启动模式配置错误,任何一个细节出问题都会让你怀疑人生。
这篇文章打算完整梳理一遍“Zynq xc7z020 + 复旦微FM25F32”这套组合的烧写配置全过程。从器件选型思路、硬件连接要点,到三条不同的烧写路径(SDK、U-Boot、Vivado Hardware Manager)怎么选怎么用,再到我实际调试中踩过的几个坑,一次性讲清楚。内容面向正在做Zynq平台开发、尤其是需要国产化Flash替代方案的硬件和嵌入式工程师,也适合刚接触Zynq启动镜像制作的朋友作为实操参考。
1. 项目背景与整体思路拆解
1.1 为什么选用QSPI Flash作为启动介质
Zynq xc7z020支持从多种介质启动,包括SD卡、QSPI Flash、NOR Flash、NAND Flash等。其中QSPI Flash是兼顾成本、性能和可靠性的均衡选择:单颗芯片体积小、引脚少、不需要额外的控制器,PS端的QSPI控制器直接挂在APB总线上,读速度够用,而且不像SD卡那样存在文件系统损坏的风险,也不像NAND那样需要坏块管理和ECC。
对于裸机程序或者轻量级Linux系统来说,QSPI Flash完全够用。FM25F32的容量是32Mbit(4MB),如果只放BOOT.bin——也就是FSBL + bitstream + 应用elf的打包体——空间绰绰有余。即使要放一个小型内核和设备树,4MB也足够支撑一个精简的启动方案。当然,如果你打算在QSPI上挂一个完整的根文件系统,这个容量就捉襟见肘了,这是后面选型时需要提前想清楚的问题。
1.2 复旦微FM25F32与Zynq xc7z020的搭配思路
复旦微FM25F32是国产QSPI NOR Flash,封装和引脚定义上对标市面上主流的W25Q32系列,指令集也基本兼容——这意味着硬件上可以直接替换,PCB不用改。但在软件层面就不能简单“拿来主义”了:Flash的JEDEC ID不同,时序参数也有差异,如果直接套用针对W25Q32编写的初始化代码或U-Boot驱动,可能在识别阶段就直接报错。
我这次的板卡设计初衷很明确:处理器用Xilinx Zynq xc7z020(国产化板卡上这颗料很常见),存储介质选用复旦微FM25F32,目标是实现上电后从QSPI Flash自动加载FPGA配置并启动PS端程序。整套方案的难点不在于某一个环节,而在于把Zynq的BootROM引导流程、FSBL对QSPI的初始化、以及复旦微Flash的时序特性三者对齐——这三个环节任何一处不匹配,启动就会静默失败。
2. 硬件连接与启动模式配置
2.1 启动模式引脚设置
Zynq上电后,BootROM会读取MIO[6:2]的电平状态来决定从哪个介质引导。从QSPI Flash启动,需要把这几个引脚配置成对应的电平组合,具体编码在UG865中有详细表格,我这里只强调几个实操中容易忽略的细节。
首先是MIO的上下拉电阻。很多开发板为了兼容多种启动方式,会在MIO[6:2]上预留0欧电阻的焊接位,你在量产板卡上必须确认这些电阻的实际焊接状态和启动模式要求一致,否则就会出现“明明BootROM在跑,却怎么也进不了QSPI引导流程”的诡异现象。其次是MIO[6:2]和QSPI用到的MIO[1:0]、MIO[3:2]在部分模式下会有功能复用,务必确认PS端的MIO分配没有冲突。
我习惯在硬件投板前就画一张启动模式配置检查表:启动介质、MIO[6:2]电平值、对应模式编码、板上实际电阻状态,逐项打勾确认。这个习惯帮我挡掉过至少两次量产阶段的低级错误——焊接工人把0欧电阻贴错位置,导致整批板卡启动不了,排查起来非常痛苦。
2.2 FM25F32关键电气参数与连接要点
FM25F32供电电压为2.7V~3.6V,和Zynq PS端的MIO Bank电压(通常是3.3V)可以直连。QSPI接口的信号线包括:C(时钟)、D0~D3(数据)、CS_N(片选)和WR_N/VIO等,其中数据线在Quad模式下是双向的,Zynq QSPI控制器会统一管理方向和时序,硬件上不需要额外加方向切换逻辑。
实际布线时注意几点:时钟线C尽量短,保持阻抗连续;四根数据线长度尽量等长;CS_N到Flash的距离不要太远,否则在高速读模式下可能出现片选不够快导致误触发;电源引脚旁边放一个100nF去耦电容,位置越靠近Flash的VCC脚越好。这些虽然是PCB设计的基本功,但在高速QSPI读取时,布局不当会直接影响时序裕量,我在测试中就遇到过因为D0线走线过长导致4-bit模式读数据偶发错误的情况。
2.3 QSPI Flash容量与地址映射
Zynq QSPI控制器支持单颗Flash,也支持两片Flash堆叠(Stacked)或并行(Parallel)模式。FM25F32是单颗4MB容量,工作在Single模式时地址范围是0xFC000000~0xFC3FFFFF,这个地址映射关系在FSBL和U-Boot中都会被用到。
如果你后续要换用更大容量的Flash,比如16MB甚至32MB,需要注意两点:一是Zynq的线性地址映射空间是否足够覆盖完整容量,二是FSBL里对Flash容量大小的编译配置要及时更新。很多人在换Flash后出现“高地址读不到数据”的问题,往往就是忽略了第二个点。
3. 烧写配置的三条实现路径
3.1 路径一:SDK的QSPI Flash Programmer
Vivado SDK里提供了一个图形化的QSPI Flash Programmer工具,其实就是用FSBL来驱动QSPI控制器,把指定的镜像文件写到Flash里。这是最直接的方式,适合量少、调试阶段的烧写。
操作步骤并不复杂:先在Xilinx菜单里选择Program Flash,然后选择BOOT.bin或单独的bitstream文件,配置好Flash类型(这时候FM25F32往往不在下拉列表中,需要选一个兼容型号,比如Spansion或Micron的同类Quad Flash)、地址偏移和校验选项,点击烧写即可。但这套工具对Flash型号的识别比较死板,经常会因为JEDEC ID不匹配而拒绝继续,或者写入后校验失败。
我使用SDK烧写器时踩过最典型的坑是:烧写过程正常,但校验报错,而且错误地址是随机的。后来定位到原因是SDK里选择的Flash时序参数是按照别的型号来的,FM25F32的写使能或状态寄存器轮询时序与之有微妙差异,导致偶发写入失败。解决方法是手动修改FSDL中QSPI初始化部分的时序配置。
3.2 路径二:U-Boot环境下的烧写
如果板卡上已经有一套能跑起来的U-Boot,我强烈推荐用U-Boot的sf命令来烧写Flash,这是量产阶段效率最高的方式。U-Boot的QSPI驱动通常已经适配了各种常见Flash,如果FM25F32不能直接被识别,可以通过修改驱动程序中的JEDEC ID表来快速添加支持。
U-Boot下的烧写流程是:先通过tftp把镜像文件下载到内存,再操作Flash擦除和写入。关键命令就是那么几条:
# 初始化QSPI控制器 sf probe # 擦除从0x0开始、大小为0x1E0000的区域(1.875MB,按自己的镜像大小调整) sf erase 0x0 0x1E0000 # 把DDR中0x8000000地址的镜像写入Flash # 0x1000000是1MB,按实际文件长度修改 sf write 0x8000000 0x0 0x1000000 # 读取Flash内容到内存,用于校验 sf read 0x9000000 0x0 0x1000000 # 对比两个内存区域的内容 cmp.b 0x8000000 0x9000000 0x1000000这套流程看着简单,但有几个细节决定成败:擦除的时候,Flash的Sector和Block边界要搞清楚,FM25F32支持4KB Sector擦除和32KB/64KB Block擦除,如果你的擦除起始地址不在Block边界上,某些型号的Flash会直接报参数错误;写入长度如果不是Page(256字节)对齐,末尾部分可能会写入失败。另外,U-Boot下载镜像到DDR时,注意不要覆盖U-Boot自身运行所在的地址区域。
3.3 路径三:Vivado Hardware Manager间接烧写
当板卡上没有可用的U-Boot,SDK的Programmer又识别不了Flash时,还有一条路:使用Vivado Hardware Manager,通过JTAG先把一个临时FSBL加载到Zynq的DDR中运行,再利用这个FSBL间接操作QSPI Flash。
具体做法是:在Vivado Hardware Manager里连接目标板卡,选择“Add Configuration Memory Device”,手动添加FM25F32(如果列表中没有就选兼容型号添加),然后加载bootloader镜像到DDR执行。这个路径成功的关键是FSBL必须包含QSPI驱动,而这需要你在SDK创建FSBL工程时把QSPI相关的选项勾上。
这条路径我没有在项目量产中使用,但在调试早期、板卡上还没有任何启动镜像的时候,它就是唯一的烧录手段。流程稍微繁琐,但是可靠。
4. 实操过程实录:从镜像生成到启动验证
4.1 制作BOOT.bin
无论后续用哪种方式烧写,第一步都是生成BOOT.bin。这个文件的结构很清晰:头部是BootROM能解析的启动镜像头,里面包含了镜像的各分区信息,后面依次是FSBL、FPGA bitstream和PS端应用程序。
在Vivado里,先综合、实现、生成bitstream;然后在SDK里,用bitstream文件创建一个FSBL工程并编译;最后在Xilinx Tools的Create Boot Image工具里指定分区:第一个分区放FSBL,第二个分区放FPGA bitstream,第三个分区放应用elf或者U-Boot镜像。分区顺序不能搞反,BootROM会先加载FSBL到OCM执行,FSBL再去配置FPGA并跳转到下一个分区。
如果使用命令行方式生成BOOT.bin,BIF文件的写法如下:
the_ROM_image: { [bootloader] ./fsbl.elf ./system.bit ./u-boot.elf }生成的BOOT.bin不要超过FM25F32一半的容量,留出足够的冗余空间。我在实际项目里还习惯把BOOT.bin用十六进制工具打开看一眼头部,确认偏移地址和分区表正常——这招帮我发现过一次bitstream分区偏移错乱的问题。
4.2 FM25F32写入操作实录
假设你已经通过U-Boot的tftp命令把BOOT.bin下载到内存地址0x8000000,接下来的操作顺序是:先probe,确认Flash能被正常识别;再erase,把目标区域清理干净;紧接着write,把数据写入;最后一定要做一次read和compare校验。
Zynq> sf probe Zynq> sf erase 0x0 0x400000 Zynq> sf write 0x8000000 0x0 0x400000 Zynq> sf read 0x9000000 0x0 0x400000 Zynq> cmp.b 0x8000000 0x9000000 0x400000注意erase命令的第三个参数是长度而不是结束地址,很多人在这里把0x3FFFFF写成0x400000,导致实际擦除的区域比预期少1字节,最后一位的数据就可能写不进去。虽然这是个小问题,但在排查时容易被忽略。
U-Boot下烧写的一个额外好处是,sf命令自带Flash ID识别信息,你可以通过probe的输出来确认控制器和Flash之间的通信是否正常。如果输出里显示unknown ID,那就说明时序或ID表需要调整。
4.3 启动模式切换与上电验证
烧写完成后,先把板卡断电,把启动模式引脚从JTAG模式切换到QSPI模式(或者直接调整对应拨码开关),重新上电。如果一切正常,串口终端里能看到BootROM加载FSBL的打印信息,接着是FPGA配置完成,然后进入U-Boot或直接启动裸机程序。
我建议在切换启动模式后,第一次上电时用示波器抓一下QSPI时钟和CS_N信号,确认BootROM确实在尝试访问Flash。很多时候问题不是Flash里没数据,而是BootROM压根没走到QSPI那条路上去——这时候检查启动模式引脚比反复烧写Flash更有价值。
5. 常见问题与排查技巧实录
5.1 典型故障速查表
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 上电后串口无任何输出 | 启动模式配置错误、BootROM未正确加载 | 检查MIO[6:2]电平、确认JTAG模式是否误开 |
| sf probe返回unknown ID | 驱动ID表缺少FM25F32 | 手动添加JEDEC ID或改用兼容型号配置 |
| 烧写过程成功但校验失败 | 时序参数不匹配、擦除不完整 | 调整FSBL/U-Boot中的Quad时序配置,重新擦除 |
| 烧写完成但上电启动失败 | BOOT.bin分区错误、bitstream偏移不对 | 检查BIF文件分区顺序,用十六进制工具验证头部 |
| 程序运行到一半卡死 | Flash容量配置不对、地址越界 | 检查FSBL中Flash大小映射,缩小镜像占用 |
| QSPI读取速度很慢 | Quad模式没使能、工作在SPI模式 | 确认FSBL和U-Boot中QSPI模式都设置为Quad |
5.2 复旦微FM25F32的差异化适配
如果说W25Q32是市场上最常用的QSPI Flash,那FM25F32就是它的国产化替身。两者指令集高度兼容,但FM25F32在状态寄存器的某些行为上有自己的逻辑。最典型的差异是写保护位的默认状态:FM25F32出厂时部分扇区的写保护可能处于开启状态,如果不预先解除写保护,烧写时会出现“写入命令发出去了但Flash数据没变”的诡异现象。
解决的办法是在U-Boot中手动查询状态寄存器:
# 读取状态寄存器值 sf read 0x9000000 0x0 0x10 md.b 0x9000000如果发现SR的低位存在保护标记,就发送写状态寄存器指令,先关闭所有保护区域,再执行擦写操作。U-Boot环境里有些版本的sf命令没有提供直接写状态寄存器的接口,这时可以通过md/mw命令直接向QSPI控制器的寄存器写入指令序列,或者更省事的办法是在FSBL初始化代码里加一小段操作,上电时自动解除写保护。
5.3 一个实战排查案例
我调试这套系统时遇到过一个很隐蔽的问题:单独在SDK里下载FPGA bitstream一切正常,单独在U-Boot里sf write也一切正常,但把BOOT.bin烧到Flash、重新上电后,FPGA迟迟没有配置成功。反复看FSBL日志都没有异常,直到用示波器抓Flash的D0、D1、D2、D3时才发现问题——在Quad模式下,部分数据线电平异常。
原因是BOOT.bin中的bitstream被FSBL读出来之后,还需要进行设备配置,而Zynq的这个配置过程会频繁读取Flash。STM32裸机程序在DDR初始化之前运行完全没问题,因为OCM够用;但FPGA bitstream的体积一旦超过OCM容量,FSBL就会启用DMA从Flash搬运数据到DDR。问题就出在DMA传输的时序和FM25F32读操作的状态机在某些边界条件下没有完全对齐,导致偶发的读数据错误。
这个问题的解决方式比较讨巧:在FSBL的QSPI初始化代码里,把DMA传输的突发长度调低一些,牺牲一点读取速度换取稳定性。虽然听起来像是绕过问题而不是解决问题,但在量产场景下,稳定性和交付优先级高于极限性能。
写在最后的经验和建议
这次把Zynq xc7z020和复旦微FM25F32搭在一起调试,最大的体会是:国产化替换不只是把芯片焊上去那么简单,IP核适配、驱动兼容、时序调优,每一步都得亲自验证。FM25F32相比W25Q32,在绝大多数方面可以无缝替换,但细节上的差异——JEDEC ID、状态寄存器行为、写保护逻辑——才是真正决定项目进度的拦路虎。
如果你也准备用FM25F32替代常规QSPI Flash,我建议在产品开发初期就尽早搭建烧写和启动验证环境,而不是等量产前再切换Flash型号。另外,U-Boot环境下先验证sf probe是否能正确识别Flash,这是一个成本最低但信息量很大的测试:能识别,说明硬件连接和基本时序没问题;不能识别,尽早排查还能省下后面大量的调试时间。
最后分享一个小习惯:每次烧写结束后,我都会把Flash里的内容完整读回来,与原始镜像做一个二进制对比。这一步虽然会多花几十秒,但能避免把“烧写失败”误判成“程序bug”,在调试链条里少走不少弯路。希望这篇实战记录能帮你把Zynq和FM25F32这套组合快速跑通。
本文还有配套的精品资源,点击获取