Zynq替换FM25F32 QSPI Flash:BOOT.bin烧写与启动适配全攻略 简介面向赛灵思Zynq-7000系列FPGA开发者的这套烧写配置程序专门解决Vivado开发环境无法支持国产复旦微FM25F32 Nor QSPI Flash因而烧写失败的问题。项目以XC7Z020为验证平台放弃SDK自带的烧写工具改用自编测试程序完成uboot与FSBL两个关键启动镜像的写入并在实际硬件上成功引导说明这套方法在国产化Flash替代场景下有效可行。压缩包共438个文件整体约2.27MB工程代码以C源文件和头文件为主体配合makefile构建脚本、链接脚本、目标文件与最终生成的BOOT.bin/elf镜像结构清晰适合直接导入SDK或按工程组织方式复现。目前已有2479人学习下载具有较强的实践参考价值。读者可从中看到非官方Flash的底层擦写时序、QSPI控制器初始化与配置逻辑、FSBL和uboot的启动配合流程以及绕过工具链限制的排错思路适合负责国产化板卡启动方案的中高级嵌入式工程师借鉴。1. xc7z020 配 FM25F32换一颗 QSPI Flash 为什么能让 BOOT.bin 烧不进去把 xc7z020 板子上的 W25Q32 换成复旦微 FM25F32这在国产化替代里是最常见的动作。芯片引脚兼容、电压一致、容量都是 32Mbit看起来就是「焊上去改个型号」的事。但等你真把 BOOT.bin 拿到烧写环节才会发现整套配置程序并没有想象中那么好说话Vivado 里找不到对应型号、U-Boot 的 sf probe 读回一堆 0、烧完上电之后串口一点动静没有。这个标题真正要解决的问题不是「怎么按几下按钮烧进去」而是「怎么让 Zynq 从 BootROM 到 FSBL 再到 U-Boot 的整条链路都认这颗国产 Flash」。本文面向正在做国产化替换或核心板适配的工程师把烧写配置程序的完整路径和背后的坑一次讲清。2. 先把启动链路捋清楚QSPI 控制器、FSBL 与 FM25F32 的时序要求2.1 Zynq 从 QSPI 启动的三段旅程BootROM、FSBL 与应用镜像Zynq xc7z020 的 QSPI 启动不是「上电直接执行 Flash 里的程序」这么简单。整个过程分成三段每一段对 Flash 的要求都不一样。第一段是片内 BootROM。上电后 ARM 核先执行 BootROM 里的固化代码它根据启动模式引脚判断要从哪里启动。如果判断是 QSPIBootROM 会做两件事发一条读 JEDEC ID 的指令确认 Flash 在线然后用一套固定的时序把 BOOT.bin 的引导头读进片内 RAM解析出 FSBL 的地址和长度再把 FSBL 加载起来运行。关键是BootROM 这一阶段不认识任何厂商 ID 表它只要求 Flash 响应标准的 0x9F 读 ID 指令并且能在它的时序窗口内把数据送回来。所以大多数符合标准 SPI NOR 规范的芯片在 BootROM 这一关都不会卡。第二段是 FSBL。FSBL 是 BootROM 加载起来的第一个用户程序它要做的事情包括重新初始化 DDR、配置 MIO、加载 PL 比特流、把应用镜像从 Flash 拷到 DDR。问题就出在这里FSBL 里的 QSPI 驱动是 Xilinx 给的例程它内部通常维护了一张 Flash 型号表或者至少执行一次完整的 ID 读取和判断。遇到表里没有的 ID 时FSBL 一般不会直接死掉但后续的 QSPI 初始化参数可能匹配不上导致读回的数据异常、校验失败表现为「卡在半路」。第三段是 U-Boot 或裸机应用。这一阶段对 Flash ID 的依赖最重因为 sf probe 命令要拿 ID 去查 jedec_flash 表查不到就直接返回失败你连擦和写的命令都发不出去。所以「烧写配置程序」这个名字的背后实际是你要同时搞定第二段和第三段的 Flash 识别再加上第一段的时序兼容验证。提示很多人以为 BootROM 会校验 Flash 型号其实不会。真正的 ID 检查发生在 FSBL 和 U-Boot 的驱动枚举阶段。这个认知能帮你少走一半弯路。2.2 FM25F32 的关键参数与指令集哪些地方和 W25Q32 不一样FM25F32 是一颗 32Mbit4MB的 SPI NOR Flash工作在 3.3V支持标准 SPI、双线 SPI 和四线 SPI。单看参数表它和 W25Q32 几乎是一个模子刻出来的页大小 256B扇区 4KB块擦除分 32KB 和 64KB 两种状态寄存器地址都是 0x05。但「看起来一样」恰恰是后面所有坑的起点。参数FM25F32W25Q32适配时要注意的点容量32Mbit / 4MB32Mbit / 4MB地址位宽一致0x000000~0x3FFFFF页大小256B256B页编程指令 0x02最大一页 256B不能跨页扇区擦除4KB指令 0x204KB指令 0x20兼容块擦除32KB/64KB指令 0x52/0xD832KB/64KB指令 0x52/0xD8兼容但不同批次可能有差异读 ID0x9F 返回 3 字节0x9F 返回 3 字节厂商 ID 不在 Xilinx/U-Boot 默认列表里四线快速读指令 0xEB指令 0xEBdummy cycle 参数可能不同这是 BootROM 阶段最大的变数状态寄存器0x05 读、0x01 写0x05 读、0x01 写写保护位和 WEL 位位置一致最核心的差异就是 JEDEC ID。U-Boot 和 FSBL 里的 Flash 表是按厂商 ID 组织的比如 0xEF 是华邦、0x20 是美光、0xBF 是 SST。复旦微的 ID 落在哪一段不同批次可能还有细微差别。你不需要背这个值但一定要在适配时先把实际读到的 3 个字节记录下来后面所有工作都围绕这 3 个字节展开。2.3 最容易被忽略的 ID 检查sf probe 读回来的 3 个字节有什么意义JEDEC ID 这 3 个字节的格式是厂商 ID、容量 ID、版本/系列 ID。比如一颗芯片读回EF 40 16那就是华邦、128Mbit、某个系列。驱动拿着这三个字节去查表查到了就把对应的名称、扇区大小、擦除指令集加载进来查不到就报错或者按通用 NOR 处理。我一般会在拿到新板子后第一步不是急着烧 Flash而是先想办法把这 3 个字节读出来确认这颗 FM25F32 是不是真的按标准协议返回 ID。读的方式有三种第一种是通过 U-Boot 的sf probe但这要求 U-Boot 已经跑起来。第二种是用 Vivado Hardware Manager 连上 JTAG在 QSPI 控制器里直接读。第三种是写一个最小的裸机程序只初始化 QSPI 控制器然后发 0x9F。对刚接触的板子我建议用第二种因为不依赖任何已有镜像JTAG 连上就能读。「读出来」只是第一步关键是拿这个 ID 去比对现有驱动表。如果落在表外U-Boot 的sf probe会直接失败如果是表内但容量或系列位对不上驱动可能按错误的扇区大小执行擦除表面看着成了实际把地址空间搞错启动照样翻车。所以不要嫌这一步麻烦它决定了后面所有操作的地基。3. 烧写环境的完整搭建从 BIF 到 Vivado Hardware Manager 的第一片 Flash3.1 硬件连接与引脚约束MIO1 到 MIO6 的默认复用表Zynq xc7z020 的 QSPI 控制器走的是 PS 侧的 MIO不是 PL 的 IO。也就是说FM25F32 必须接到固定的 MIO 引脚上不能像 PL 逻辑那样随便分配管脚。这一组引脚在 Zynq-7000 里是约定好的复用功能不需要你在约束文件里额外写 LOC但需要在 FSBL 的引脚初始化代码里确认对应的复用被打开。信号MIO 引脚方向说明QSPI 片选MIO1输出低有效连 Flash 的 CS#QSPI 时钟MIO2输出连 Flash 的 CLKQSPI D0MIO3双向标准 SPI 的 MOSIQSPI D1MIO4双向标准 SPI 的 MISOQSPI D2MIO5双向四线模式的 IO2QSPI D3MIO6双向四线模式的 IO3硬件上常见的坑是核心板已经按 W25Q32 的封装做了设计换 FM25F32 时引脚兼容所以不用改板但有些板子为了高速读取把 D2/D3 上做了其他用途的复用或者片选信号被调试器占用导致四线模式读不到数据。我一般会在首次上电前用万用表量一下 MIO3~MIO6 到 Flash 对应引脚的连通性排除焊接和封装不匹配的问题。FM25F32 的封装脚位和主流 32Mbit NOR 是兼容的但不同批次丝印不同别想当然。3.2 用 Bootgen 生成 BOOT.binBIF 文件与三种镜像组合BOOT.bin 的内容与 Flash 品牌无关它只是把 FSBL、比特流和应用镜像打包成一个带引导头的文件。BootROM 读的就是这个引导头。Xilinx 提供的打包工具是 Bootgen输入一个 BIF 格式的描述文件输出 BOOT.bin。下面是一个典型的 BIF 文件the_ROM_image: { [bootloader] fsbl.elf bitstream.bit app.elf }[bootloader]标记了这一项是 FSBLBootgen 会把它放在引导头之后的第一段。bitstream.bit是可选的。如果你的设计不加载 PL可以把这一行删掉。app.elf是运行在 PS 上的应用可以是裸机程序也可以是 U-Boot。生成 BOOT.bin 的命令bootgen -image boot.bif -o BOOT.bin -w on -flash_type qspi-x4-flash_type qspi-x4告诉 Bootgen 按四线 QSPI 的格式打引导头。如果你的板子只把 D2/D3 引出但仍然想用标准模式可以改成qspi-x1但要注意 BootROM 在读取阶段会按引导头里的模式字段决定用几线读 Flash写错了会在启动早期就失败。还有一种组合是只烧 FSBL 和 U-Boot 镜像不包含比特流。这种方案在多版本 PL 迭代时很常用因为每次更新逻辑不需要重新烧 QSPI只要在 U-Boot 里通过程序化方式加载比特流。但首版调试我还是建议把 bit 流打进 BOOT.bin减少一个变量。3.3 JTAG 方式首烧Vivado Hardware Manager 操作 QSPI 的完整流程第一片空 Flash 没有任何可启动镜像你没法通过 U-Boot 烧自己所以必须走 JTAG。常见做法是打开 Vivado Hardware Manager连接开发板后对 QSPI 器件执行烧写。具体步骤打开 Vivado Hardware Manager点击 Open Target 连接 JTAG确认能看到 xc7z020 器件。在 Hardware 窗口右键点击器件选择 Add Configuration Memory Device。在弹窗里搜索 FM25F32。如果列表里找不到这一步先不慌选一个同容量同电压的兼容型号比如 N25Q32 或 W25Q32并把后续的指令时序模式改成与 FM25F32 一致。选择刚才生成的 BOOT.bin 文件勾选 Verify after programming。点 Program等待烧写完成。这一步最容易迷惑的是第 3 步。Vivado Hardware Manager 的 QSPI 型号列表来自 Xilinx 的 Flash 库里面没有复旦微的型号是正常的。选兼容型号只影响烧写工具使用的指令集不影响 Flash 里最终存下的内容。你真正要在意的是这个兼容型号是否也支持四线快速读、dummy cycle 是否一致。否则烧写时用标准 SPI 没问题上电后 BootROM 切换四线读却读不出数据。这里补充一个更直接的路径用 XSCT 命令行工具做同样的事适合批量产线脚本化。常见做法是写一段 Tcl 脚本用program_qspi -fsbl fsbl.elf -flash_type qspi-x4 -file BOOT.bin完成烧写。但首块板子我建议先用 GUI 走一遍因为你能直观看到烧写器的状态和报错排错成本更低。4. FSBL 与 U-Boot 适配让启动代码认识复旦微的 ID 与时序4.1 FSBL 的 QSPI 初始化里要加什么ID 分支与四线读检查FSBL 的 QSPI 驱动通常会执行一次 Flash 识别。Xilinx 提供的默认代码里有一张已知 Flash 的列表里面主要是美光、华邦、Spansion 这些老面孔。FM25F32 的 ID 不在这张表里时FSBL 的行为取决于版本和配置有些会直接按通用参数继续跑有些会打印一行警告然后使用默认时序。这两种情况都可能踩坑因为默认时序未必匹配这颗 Flash 的快速读 dummy cycle。我一般会在 FSBL 的 QSPI 初始化代码里加上一个 ID 分支逻辑示意如下/* 读取 JEDEC ID前 3 字节是厂商、容量、系列 */ u8 id[3]; QspiReadId(id); if (id[0] FM25F32_MANUFACTURER_ID) { /* 复旦微 FM25F324MB4KB 扇区支持 x1/x2/x4 */ qspi_para.page_size 256; qspi_para.sector_size 4096; qspi_para.block_size 65536; qspi_para.total_size 0x400000; qspi_para.enable_x4 1; qspi_para.dummy_cycles 8; /* 以实际规格书为准 */ } else { /* 走默认兼容路径 */ }QspiReadId发送 0x9F 命令读出 3 字节 ID。dummy_cycles必须按规格书填写四线快速读如果 dummy cycle 不对读回来的数据会整体移位校验必挂。这个分支加在 FSBL 的 QSPI 初始化函数里而不是应用代码里原因是 FSBL 要在加载 U-Boot 之前就把 QSPI 控制器配置好。改完 FSBL 后重新编译替换 BIF 文件里的fsbl.elf再重新生成 BOOT.bin 烧入。注意这个 BOOT.bin 会覆盖掉旧的启动镜像所以如果你改错了导致 FSBL 起不来要用 JTAG 重新烧最好先把旧镜像备份到内存里。4.2 U-Boot 侧配置device tree 的 jedec,spi-nor 与 jedec_flash 表U-Boot 访问 QSPI Flash 走的是 SPI NOR 框架。新版 U-Boot 支持设备树描述和 SFDP 自动探测但你的板子未必开了这些配置而且 FM25F32 不一定完整实现 SFDP 串口发现协议。最稳妥的做法是双管齐下设备树里显式声明 Flash 节点同时确保驱动表里能查到这颗芯片。设备树节点一般长这样qspi { status okay; flash0 { compatible jedec,spi-nor; reg 0; spi-max-frequency 50000000; spi-tx-bus-width 4; spi-rx-bus-width 4; }; };compatible jedec,spi-nor让驱动走通用 SPI NOR 框架不要绑定具体厂商驱动。spi-tx-bus-width和spi-rx-bus-width设成 4表示支持四线读写。如果板子四线信号没接全改成 1 更安全。spi-max-frequency先按 50MHz 验证稳定后再往上提。如果 U-Boot 编译时开启了CONFIG_SPI_FLASH_SFDP并且 Flash 支持 SFDP驱动会自动解析容量和擦除指令集。但实测中不少国产 Flash 的 SFDP 表并不完整解析结果反而异常。所以我会在jedec_flash表里手动补一条记录{ .name fm25f32, .id { 0xA1, 0x40, 0x16 }, /* 以 sf probe 实际读回为准 */ .sector_size 4096, .nr_sectors 1024, .page_size 256, },id数组里的三个字节就是前面说的 JEDEC ID必须用你实际读到的值替换。nr_sectors是扇区数4MB 除以 4KB正好 1024。补进表里的位置通常在drivers/mtd/spi/spi-nor-ids.c这类文件中具体文件因 U-Boot 版本而异搜索jedec_flash就能找到。改完重新编译 U-Boot烧写或通过 tftp 加载到内存运行再执行sf probe。看到返回fm25f32型号名说明 U-Boot 这一关过了。4.3 实测烧写命令sf probe、sf erase、sf write、sf read 校验一遍过U-Boot 起来后的烧写流程是标准四连探测、擦除、写入、回读校验。以 4MB 整片烧写为例命令序列如下sf probe sf erase 0x0 0x400000 sf write 0x2000000 0x0 0x400000 sf read 0x3000000 0x0 0x400000 cmp.b 0x2000000 0x3000000 0x400000sf probe重新探测 Flash加载 ID 和参数表。如果改了设备树或驱动表执行完这条才生效。sf erase 0x0 0x400000从偏移 0 开始擦除 4MB。整片擦除耗时较长串口输出done才能继续。sf write 0x2000000 0x0 0x400000把内存地址 0x2000000 处的 BOOT.bin 写入 Flash 偏移 0x0。源地址要确保镜像已经通过 tftp 或 fatload 加载到 DDR。sf read把 Flash 内容回读到内存另一块区域再用cmp.b和源镜像逐字节比较。我习惯在sf probe后先手动执行一次sf erase 0x0 0x10000再写回头验证擦除和写保护状态。因为有些 Flash 出厂时状态寄存器里的写保护位是置位的直接整片擦会返回失败或部分失败。如果遇到sf erase报错先读状态寄存器确认是否处于写保护状态再通过写状态寄存器指令解除。5. 换 Flash 后的五个高频坑现象、原因与解决办法5.1 sf probe 读回全 0 或厂商 ID 不对现象U-Boot 执行sf probe返回unrecognized JEDEC id bytes: 00 00 00或者 ID 明显不是复旦微的编码。原因最常见的有三种。一是 Flash 压根没被片选选中MIO1 的复用没配置对二是四线模式的 D2/D3 信号被其他外设占用导致读 ID 时序错乱三是 SPI 频率过高FM25F32 在板子上的信号完整性扛不住。解决先退回标准 SPI 模式把设备树里的spi-tx-bus-width和spi-rx-bus-width都改成 1spi-max-frequency降到 10MHz 再 probe。如果 ID 正常了再逐步提频率和开启四线。如果改了还是全 0用示波器量 MIO2 上有没有时钟MIO1 片选是否正常拉低基本能定位到硬件层。5.2 erase 正常但 write 后 verify 对不上现象sf erase执行成功sf write也没有报错但cmp.b回读比对时大量数据不一致且错误地址呈现整段偏移的规律。原因分为两类。一类是四线快速读的 dummy cycle 不匹配。写入时用的是标准页编程指令 0x02单线传输回读时驱动可能切到了四线快速读 0xEB如果 dummy cycle 配错数据会在读时序里产生固定的 bit 偏移。另一类是页编程跨越了页边界实际写到下一个页回读时自然错位。解决先强制用标准读指令 0x03 回读验证排除写本身的问题。再把sf read改成单线模式如果对比通过就锁定是四线读时序问题去核对规格书里的 dummy cycle 参数修正 FSBL 或 SPI NOR 驱动里的配置。页边界问题则要检查写入长度是否按 256B 对齐以及驱动是否自动做了跨页拆分。5.3 BOOT.bin 烧完上电没反应JTAG 却能连上 PS现象烧写过程全程无报错verify 也通过但断电重启后串口没有任何输出JTAG 仍然能连接到 PS 内核。原因BootROM 已经把控制权交出去了但它在 QSPI 启动模式下没能读到有效的引导头。常见原因是 BOOT.bin 没有烧在偏移 0x0 的位置烧到了别的偏移另一个原因是 BootROM 按四线模式读取时FM25F32 的时序参数和引导头里记录的参数不一致读回的数据是乱的。解决先用sf read从 0x0 读 1MB 数据检查引导头是否存在确认后核对引导头前 8 字节是否正常。如果引导头在且完整再看 BootROM 使用的读取模式和 BOOT.bin 的flash_type是否一致。必要时临时把 boot mode 引脚切换到 SD 卡启动用 SD 里的 U-Boot 去读 QSPI 里的内容这一步能把「BootROM 读不出来」和「Flash 里根本没烧对」彻底分开。5.4 Vivado Hardware Manager 里找不到 FM25F32 型号现象在 Add Configuration Memory Device 的搜索框里输入 FM25F32列表为空无法继续烧写。原因Xilinx 的 Flash 库不维护复旦微的型号这是正常的不代表芯片不能用。解决按容量和电压选一颗兼容的型号比如 W25Q32 或 N25Q32。关键是确认所选型号的页大小和扇区大小与 FM25F32 一致两者都是 4KB 扇区、256B 页。烧写完成后不要急着断电用烧写工具的回读功能和源文件比对一次。Hardware Manager 的校验机制只保证它对这颗「名义型号」的擦写成功真正的内容正确性要用回读来保证。5.5 批量烧写偶发失败重启开发环境后又正常现象产线或实验室里 10 块板子烧同样的 BOOT.bin两三块在擦除或写入阶段报错重新连接 JTAG 再烧一次又能成功。看起来像玄学换线、换电脑后概率还有变化。原因绝大多数是 JTAG 链路或供电问题。QSPI 烧写时 Flash 的峰值电流比读操作大如果板子的 3.3V 电源在擦写瞬间跌落超过 10%Flash 内部逻辑会复位或状态寄存器写入失败。另外 JTAG 线缆过长或接地不实会导致 DAP 访问 QSPI 控制器时丢响应表现为偶发失败。解决先示波器抓烧写瞬间的 3.3V 波形确认电源跌落幅度。再检查 JTAG 线缆是否用了屏蔽线、地线是否可靠连接。产线场景我一般会在批处理脚本里加重试机制同一块板失败后自动重试一次同时记录失败时的错误码。实践下来九成以上的偶发失败在断电重连后都能通过剩下的就是硬件本身的问题不该靠烧写软件去扛。6. 量产阶段值得养成的三个习惯回读校验、串口时序图与分层烧写先说回读校验。量产和研发最大的区别是没人守在旁边看报错。U-Boot 里的cmp.b校验只能证明「内存里的源镜像」和「Flash 里的内容」一致但源镜像从 tftp 传到 DDR 的过程中如果出了问题这一层校验是发现不了的。所以我在产线流程里会加一步先crc32计算源镜像的哈希再sf read后计算 Flash 内容的哈希两个值比对。这一步用脚本固化下来避免每次靠人眼确认输出。crc32 0x2000000 0x400000 sf read 0x3000000 0x0 0x400000 crc32 0x3000000 0x400000其次是串口时序图。Zynq 的 BootROM 在上电后会通过 UART 输出少量字符这些字符对判断「Flash 是否被正常读取」极有价值。烧完第一块板子后我会把串口从按下电源到 U-Boot 提示符出现之间的完整输出存成文件作为这块板子的标准启动日志。之后每一块板子烧完都抓同一段窗口做对比任何一步输出缺失都能反向定位到 Flash 时序还是镜像内容的问题。最后是 Flash 布局固化。4MB 的空间对 BOOT.bin 来说绰绰有余我一般这样分配0x0 起始放 BOOT.bin一般不超过 1MB剩余空间划成两个区一个放校准参数或 MAC 地址另一个留作冗余启动镜像。这样下次升级固件时先写冗余区再切换启动标记即使写坏了还有最后一层后悔药。这套布局从量产第一天就固化下来不要在项目中途才想起改因为改布局意味着要重写烧写脚本和 U-Boot 环境变量代价很高。我自己经历过最难受的一次就是烧写工具和回读校验全通过、量产了两百块板子之后才发现 FSBL 里的四线读 dummy cycle 差了 2 个周期导致部分体质弱的板子在低温下启动不稳定。从那以后每一颗替换 Flash 的板子我都会在方案定型前做一次完整的启动日志留档和回读校验而不是烧进去能开机就算完事。希望这篇笔记能帮你把 Zynq 配复旦微 FM25F32 的每一条链路都提前踩平少走这些弯路。本文还有配套的精品资源点击获取