2026/9/29 18:31:46

ZYNQ MPSOC QSPI固化避坑:MT25QU256地址模式与配置全解析

ZYNQ MPSOC QSPI固化避坑:MT25QU256地址模式与配置全解析 我最近在做一个 ZYNQ MPSOC 项目产品从样机阶段的 SD 卡启动切到最终的 QSPI Flash 固化Flash 用的是美光 MT25QU256。本来以为 QSPI 固化是个轻车熟路的活结果在这颗芯片上连踩了好几天坑先是 Vivado 里翻不到这个型号再是烧写成功但上电没反应最后又发现访问 16MB 之后的地址全是 0xFF。整条链路排查下来问题全部集中在 MT25QU256 这个型号的特殊配置上。这篇避坑指南我就把完整的排查思路和可复现步骤写出来帮正在做 MPSOC QSPI 固化、刚好也用这颗 256Mb Flash 的朋友少走三天弯路。1. 这个坑到底坑在哪MT25QU256 给 MPSOC 固化埋了三个雷1.1 项目背景从 SD 卡启动切到 QSPI 固化问题一下全冒出来了我们这块 MPSOC 板子ZU 系列开发阶段一直用 SD 卡启动图的是调试方便改一个镜像直接换卡插上就跑不用频繁跟烧写器打交道。等产品功能稳定后要把启动方式固化到 QSPI做成“上电就运行”的正式形态于是按常规思路把启动镜像用 program_flash 写进 Flash。板子上选的美光 MT25QU256256Mb 容量当时觉得这颗料很常规——ZYNQ-7000 时代 QSPI 固化做过十几次以为一天能搞完。结果这一天变成了一周。第一天Vivado 的 Flash 型号列表里找不到 MT25QU256第二天用兼容型号把镜像烧进去上电串口毫无回应第三天终于从 SD 卡辅助引导进去又在 U-Boot 里发现 16MB 边界之后读回全是 0xFF。每过一个坎都以为是个例最后串起来一看所有问题全部指向这颗 Flash 自身的特殊逻辑跟 ZYNQ-7000 时代常见的 128Mb 级别 NOR Flash 完全不是一回事。这里先给结论MPSOC 的 QSPI 固化流程本身不复杂真正容易翻车的是 Flash 选型与工具链的匹配以及大容量 Flash 的地址模式切换。只要把 MT25QU256 这几层特殊性看明白固化就是半小时的事。1.2 MT25QU256 的三重特殊身份先说清楚MT25QU256 不是“一颗普通的 QSPI Flash”它有三个容易被忽略的身份标签每一个都对应一类坑。第一个身份它是 256Mb32MB的大容量器件地址突破 16MB 边界。这是最核心的坑。QSPI NOR Flash 的传统读地址是 3 字节24 位最大覆盖 16MB。MT25QU256 容量 32MB超过 16MB 的部分必须切换到 4 字节地址模式32 位寻址。这颗 Flash 上电后默认处于 3 字节地址模式也就是说如果软件不做任何配置直接访问高地址区只会读回 0xFF。问题在于工具链里各个组件对“4 字节地址模式”的支持程度不一样BootROM 读取启动头部用的可能是标准 3 字节读FSBL 如果要加载高地址区的数据需要驱动里主动切模式U-Boot 和 Linux 内核的 SPI-NOR 框架大多能自动处理但前提是配置正确。这种“部分支持、部分不支持”的状态最容易产生“明明烧进去了却启动不了”的诡异现象。第二个身份它的 JEDEC ID 不是工具链默认认识的型号。MT25QU256 读 SFDP 得到的 JEDEC ID 通常是 0x20 0xBB 0x20而 Vivado 早期版本2019.x 及之前自带的 Flash 列表主要覆盖的是 MT25QU128ID 0x20 0xBB 0x18等型号。列表里找不到 MT25QU256导致两个后果在 Vivado 的 QSPI IP 配置里没法直接选这颗 Flash生成的硬件描述文件里 Flash 参数可能不完整使用 program_flash 工具烧写时如果 Flash ID 与工具预设不匹配工具可能拒绝写入或者按错误的时序操作导致数据错乱。实际处理方式通常是选命令集兼容的型号代替或者升级到对这颗 Flash 有原生支持的 Vivado 版本。这里有个细节MT25QU 和 MT25QL 电压不同前者 1.8V后者 3.3V选兼容型号别搞混。第三个身份它是 1.8V 器件对硬件设计和模式配置更敏感。MT25QU256 是低电压版本1.8V 供电这要求它的电压域与 MPSOC 的 PS 端 QSPI I/O 电压严格匹配。很多开发板为了兼容 3.3V NOR Flash在 PCB 上加了电平转换这本身没问题但如果转换芯片的速率跟不上 QSPI 时钟高频读写就会随机出错。这种错误没有软件配置那么有规律表现出来就是“刚才还能烧进去换个速率就全乱了”。把三个身份放在一起你基本就能理解为什么这颗 Flash 在 MPSOC 固化中总出“玄学问题”。接下来逐个击破。2. 固化前必须搞定的三处配置Vivado、PetaLinux、FSBL 一个都不能漏确认问题根源之后接下来就是要动手解决了。这一节把三处必须检查的配置位置写清楚。每一步都不难但漏掉任意一个后面都要返工。2.1 Vivado 侧Flash 型号识别与兼容替代方案先说最直接的操作。打开 Vivado 工程进入 Zynq UltraScale MPSoC 的 IP 配置界面在PS-PL Configuration - Peripheral Configuration - QSPI一栏能看到 QSPI 的启用和模式选择。这里可以配置 Single/Dual/Quad/Octal 等模式以及 Flash 型号。以 MPSOC 的 QSPI 控制器为例它默认支持的外部器件列表里往往能直接选到某个大厂型号但如果你用的 Vivado 版本较老列表里可能只有MT25QU128N25Q128 / N25Q256其他厂商的 128Mb 型号如果确实选不到 MT25QU256我建议不要纠结直接选 MT25QU128 作为兼容型号。原因有三点MT25QU128 和 MT25QU256 使用同一套 QSPI 指令集读、写、擦除、状态寄存器操作完全一致唯一差异是容量和 JEDEC IDVivado 生成 FSBL 时会根据所选 Flash 型号生成对应的 QSPI 初始化代码xqspipsu 驱动MT25QU128 的参数对 MT25QU256 基本通用program_flash 工具烧写时通过-flash_type指定正确通信模式如 qspi-x4-single通常不会因为 JEDEC ID 不完全一致而拒绝操作个别版本会提示 ID 不匹配这时可以加-noverify跳过校验但强烈不建议。反过来如果你用 2020.2 以上版本的 Vivado列表里已经原生支持 MT25QU256直接选上即可。我个人的经验是哪怕版本里能选到也建议去Address Editor里看一眼 QSPI 的映射地址和大小确认它把整个 32MB 都映射到了 PS 地址空间。还有一个隐藏选项容易犯错QSPI 的 Daisychain级联设置。MT25QU256 的引脚支持多片级联但绝大多数板卡用的是单片。如果你在配置里不小心把 Daisychain 打开FSBL 初始化时读取 Flash ID 就会异常。这个一旦选错后面烧写报错的风格跟地址模式问题很像排查起来非常费时间。这一段的重点不是“选哪个型号”而是“让工具链输出的初始化代码匹配这颗 Flash 的电气和协议特性”。只要命令集一致、电压等级一致兼容型号就是可用的。2.2 PetaLinux 侧启动镜像分区与 boot.scr 方案PetaLinux 在 MPSOC 上的作用是把 PMU Firmware、ATF、U-Boot、设备树全部编译打包生成可以直接烧写的 BOOT.BIN 和可选的 image.ub。但涉及 QSPI 固化时有两处必须手动确认。第一处是petalinux-config里的 Flash 参数。路径大致是Subsystem AUTO Hardware Settings - Flash Setting这里会看到 Flash 类型、总线宽度、地址偏移等参数。对 MT25QU256要确认它识别出的 Flash 大小是 32MB而不是默认的 16MB。如果大小不对后续生成的分区表会把高地址区间全部排除。第二处是生成 boot.scrU-Boot 启动脚本。在较新版本的 PetaLinux 里boot.cmd 的常见写法如下setenv bootargs consolettyPS0,115200 root/dev/ram0 rootwait setenv loadaddr 0x10000000 sf probe 0 0 0 sf read ${loadaddr} 0x300000 0x800000 bootm ${loadaddr}这段命令的含义是探测 QSPI Flash从偏移 0x300000 处读取 8MB 数据到 DDR 地址 0x10000000然后 bootm 启动。针对 MT25QU256这里有两个关键点sf probe 0 0 0中间的参数分别表示 SPI 总线号、片选、时钟频率。MT25QU256 在 1.8V 供电下标准 QSPI 读时钟可以到 133MHz但 U-Boot 侧建议先用保守值30MHz 或 50MHz跑通流程再逐步提高避免高频读写出错读取源地址0x300000必须落在 Flash 分区的正确范围内同时必须确认 U-Boot 的 QSPI 驱动已经适配了 MT25QU256 的地址模式。如果使用近两年的 PetaLinux内置 SPI-NOR 驱动一般能识别这颗 Flash 并自动处理 4 字节地址模式如果内核或 U-Boot 版本太老就有可能出现前 16MB 正常、高地址读取失败的问题后面会详细讲。boot.scr 的生成命令是mkimage -c none -A arm64 -T script -d boot.cmd boot.scr在较新的 PetaLinux 里也可以用petalinux-package --boot --boot-script参数把 boot.cmd 直接打进去省去手工输 mkimage 的麻烦。2.3 FSBL 与 4 字节地址模式的隐藏依赖FSBL 是整个固化链路里最容易“背锅”的一环。BootROM 上电后会从 QSPI Flash 的第 0 个扇区读取 Boot Header并根据其中信息把 FSBL 镜像加载到 OCM。这个过程里BootROM 自己用标准 QSPI 读命令并且在内部处理了 Flash 识别和地址映射——但注意BootROM 只负责读头部和加载 FSBLFSBL 之后还要读取 PMU Firmware、ATF、U-Boot 等后续镜像。也就是说当 FSBL 开始工作后Flash 的访问权已经交给了 FSBL 里的 xqspipsu 驱动。如果 FSBL 使用的是老版本裸机驱动它可能默认认为 Flash 容量是 128Mb不会自动切换到 4 字节地址模式。如果你的 BOOT.BIN 里所有后续镜像都放在低 16MB 范围内那没问题但只要镜像总大小超过 16MB或者为了布局方便把 U-Boot 放在 0x1000000 之后FSBL 读取就会失败。解决这个隐藏依赖最靠谱的方法是使用与 Vivado 版本匹配的、较新的 FSBL 源码来编译 BOOT.BIN。Xilinx 从 2020.2 开始FSBL 的 QSPI 驱动已经加入根据 SFDP/ID 自动判断容量并切换地址模式的逻辑。如果你的工程还是老版本有两个补救办法手工给 FSBL 定义一个容量宏通常是XQSPIPSU_FLASH_SIZE之类把 Flash 大小设置为 256Mb重新编译 FSBL调整启动镜像布局把所有需要 FSBL 加载的镜像全部放在低 16MB把环境变量区、文件数据等放在高地址——但那部分不再由 FSBL 负责而是由 U-Boot 或 Linux 驱动访问。我建议不要长期用第二种“绕过去”的办法因为风险在于一旦某个镜像体积膨胀跨过 16MB又要重新布局。一次性把 FSBL 容量配置改对后面一劳永逸。3. 一次完整的 MPSOC QSPI 固化实操记录前面铺垫够多了这一节直接放可以照着抄的完整流程。假设前提已有 MPSOC 板卡JTAG 连接正常Vivado 和 PetaLinux 环境已装好。3.1 生成包含 QSPI 配置的 XSA 与 FSBL在 Vivado 中打开工程确认 QSPI 已启用并按上一节方式选择正确的 Flash 型号。然后重新综合、生成硬件描述文件write_hw_platform -include psu_init -force xsa/mpsoc_qspi.xsa这里有个容易被忽略的步骤导出 XSA 前一定要确认工程里psu_init.tcl中关于 QSPI 的 MIO 引脚和 I/O 电压配置已经生成。QSPI 的 MIO 引脚和电压域直接影响硬件能否正常工作。很多“烧写失败”的案子最后查下来都是 MIO 配置错误或电压域不匹配。3.2 创建 PetaLinux 工程并生成 BOOT.BINpetalinux-create --type project --template zynqMP --name qspi_demo cd qspi_demo petalinux-config --get-hw-description../xsa/mpsoc_qspi.xsa在配置界面做三件事在Subsystem AUTO Hardware Settings - Flash Setting确认 Flash 大小和类型在Image Packaging Configuration - Root filesystem type选INITRAMFS或SD取决于根文件系统放在哪确认串口通常是psu_uart_1和波特率。然后编译和打包petalinux-build petalinux-package --boot --format BIN \ --fsbl ./images/linux/zynqmp_fsbl.elf \ --u-boot ./images/linux/u-boot.elf \ --pmufw ./images/linux/pmufw.elf \ --atf ./images/linux/bl31.elf \ --output ./images/linux/BOOT.BIN生成的 BOOT.BIN 包含 FSBL、PMU Firmware、ATF 和 U-Boot。对 QSPI 启动来说BootROM 只负责加载 FSBLFSBL 再根据 Boot Header 描述依次加载后续镜像。所以打包顺序不能乱--fsbl、--pmufw、--atf、--u-boot的顺序要和实际链接地址匹配这是第一道关卡。3.3 确定 Flash 布局并生成 boot.scr / image.ub如果需要从 QSPI 启动 Linux 内核则需要把内核和设备树打包成 image.ub并编写 boot.scr。PetaLinux 编译完成后images/linux/下通常已经生成image.ub。如果需要手动指定内容可以用petalinux-package --image --format uImage --kernel ./images/linux/image.ub我个人常用的 Flash 布局如下偏移地址内容建议大小0x0000000BOOT.BINFSBL PMU ATF U-Boot1-2MB0x0020000环境变量区1MB0x00300000image.ub内核 设备树 initramfs8-16MB高地址区用户文件 / 数据区剩余空间把 BOOT.BIN 放在 0x0image.ub 放在 0x300000 附近这样用户在高地址放文件时不会覆盖启动镜像。3.4 用 program_flash 分步烧写在 Vivado Hardware Manager 里连接 JTAG然后使用 program_flash。注意program_flash 烧写时需要先通过 JTAG 把 FSBL 加载到 OCM 运行由 FSBL 来初始化 QSPI 控制器和时钟再执行 Flash 擦写。所以这里也需要 FSBL 的 elf 文件。program_flash -f ./images/linux/BOOT.BIN \ -offset 0x0 \ -flash_type qspi-x4-single \ -fsbl ./images/linux/zynqmp_fsbl.elf \ -verify参数说明-flash_type qspi-x4-single根据板卡实际连接选择四根数据线都接了用 x4-single 最快只有一根线用 x1-single-verify烧写后自动回读校验强烈建议加上能帮你第一时间发现时序问题-offset 0x0从起始地址开始烧。BOOT.BIN 烧完后再烧 image.ubprogram_flash -f ./images/linux/image.ub \ -offset 0x300000 \ -flash_type qspi-x4-single \ -fsbl ./images/linux/zynqmp_fsbl.elf \ -verify3.5 设置启动模式并上电验证MPSOC 的启动模式由一组模式引脚决定BOOT_MODE[3:0]。QSPI 启动对应的组合在官方手册有明确表格具体数值以板卡原理图为准。设置完后连接串口上电观察打印。如果一切正常串口会依次输出 BootROM 调试信息、FSBL 版本信息、U-Boot 启动信息然后进入 Linux。如果输出停在“DDC”或者反复出现 BootROM 重试信息基本就是 Flash 侧问题优先检查启动模式引脚和烧写地址。第一次固化时建议先用 boot.scr 从 QSPI 把 image.ub 读到 DDR 再 bootm这样能快速验证 Flash 的高地址读取能力。这一步通了说明前面关于地址模式和分区布局的配置都正确后面再慢慢调速度和布局。4. 典型报错盘点从启动失败到地址异常的完整排查思路这一节是全篇收获最大的部分也是我踩坑最多的环节。按“现象 - 原因 - 排查步骤 - 解决方案”的结构梳理最后附一张速查表。4.1 现象一烧写成功但上电后串口完全没有输出这个问题在 QSPI 固化里出现率极高。先说结论烧写成功不等于启动成功。program_flash 的-verify通过只能说明写入数据时读回正确但 BootROM 是否能正确读取取决于更多因素。排查顺序建议检查启动模式引脚。MPSOC 上电时会锁存 BOOT_MODE如果拨码位置不对BootROM 可能跑去 SD 卡或 JTAG根本不会访问 QSPI确认 BOOT.BIN 确实在 0x0 偏移。BootROM 的 QSPI 启动固定从 Flash 起始地址读取 Boot Header如果你用 program_flash 时不小心加了-offset 0x100000那 BootROM 什么都读不到确认 Boot Header 校验正确。生成的 BOOT.BIN 里如果 FSBL 版本与 Vivado 版本不匹配Boot Header 校验字段可能不对BootROM 会报 CRC 错误检查 QSPI 时钟频率。MPSOC 的 QSPI 控制器默认输出时钟可能偏高Flash 在未配置状态下读时序不稳定BootROM 阶段表现为读取超时或数据错误最后才是 Flash 本身。如果你选的兼容型号和实际芯片的 JEDEC ID 差异较大BootROM 早期读 ID 做 SFDP 分析时可能失败。4.2 现象二能进 U-Boot 但加载 Linux 失败能进 U-Boot说明 BootROM、FSBL、ATF 链路是通的问题集中在 U-Boot 访问 Flash 或 DDR 的环节。常见原因boot.scr 中的读取地址不对。比如sf read源地址是 0x300000但实际把 image.ub 烧到了 0x200000读出来的自然不是内核镜像DDR 地址没有正确初始化。MPSOC 的 DDR 初始化依赖 FSBL 里的 psu_init如果 XSA 里 DDR 配置有问题U-Boot 虽然能跑在 OCM但往 loadaddr 写数据时会异常image.ub 本身损坏。可以用md5sum ${loadaddr}对比一下读出来镜像与主机上的 md5确认数据完整性。排查技巧在 U-Boot 环境里手动执行sf probe、sf read、crc32等命令逐步定位是 Flash 读取问题、DDR 问题还是 bootcmd 配置问题。不要一上来就反复烧写那样既低效又容易磨损 Flash。4.3 现象三前 16MB 访问正常超过 16MB 后读回全 0xFF这是 MT25QU256 最典型的“身份证”问题。前面讲过这颗 Flash 默认处于 3 字节地址模式最大寻址 16MB。如果 U-Boot 里sf read 0x10000000 0x2000000 0x1000读 32MB 地址返回全 0xFF基本就是地址模式没切换。在不同软件层这个问题的表现和处理方式U-Boot 阶段查看sf probe后是否有 Flash 型号识别信息。识别为 mt25qu256 说明驱动已正确解析 SFDP识别为 unknown 或 mt25qu128说明驱动没真正识别这颗 Flash需要检查驱动配置或手动指定型号设备树里加jedec,spi-nor参数Linux 内核阶段Linux 的 SPI-NOR 框架通常能自动识别 MT25QU256 并启用 4 字节寻址前提是设备树 QSPI 节点 compatible 属性正确。如果内核日志显示spi-nor: unrecognized JEDEC id bytes按 2.1 的兼容方案处理裸机/FSBL 阶段老版本 FSBL 驱动的容量宏需要手工改大参照 2.3。一句话总结先看软件栈对 Flash 的识别结果再看地址模式是否切换成功最后才查硬件信号质量。90% 的案例在前两步就能定位。4.4 关于“JTAG 固化时必须使用 DDR 吗”的怪问题这个问题经常在 ZYNQ-7020 和 MPSOC 的论坛里出现。起因是 program_flash 的默认工作方式先把镜像加载到 DDR 或 OCM再搬运到 Flash。如果目标板 DDR 没初始化或映射有问题工具就会报错。MPSOC 的 OCM 有 256KB理论上小于这个体积的镜像可以直接在 OCM 里暂存再写 Flash但实际操作中 program_flash 常因镜像大小、DMA 通道配置而要求 DDR 存在。所以答案不是绝对“必须”但如果镜像超过 OCM 容量DDR 基本就是必需的。4.5 QSPI 固化常见问题速查表现象可能原因排查方向典型处理烧写报 Flash ID 不匹配兼容型号与实际芯片 JEDEC ID 不同查看 program_flash 完整日志升级 Vivado 或指定 flash_type烧写后校验失败QSPI 时钟过高 / 电平转换带宽不足降低频率检查供电调整时钟参数或改用 x1 模式上电串口无输出MODE 引脚错误 / Boot Header 损坏检查拨码开关、BOOT.BIN 偏移重设启动模式重新生成 BOOT.BIN停在 BootROM 重试QSPI 读取失败 / 时钟异常观察串口 DDC 信息降低时钟或检查 Flash 焊接U-Boot 能进但内核不启动boot.scr 地址错误 / image.ub 损坏手动 sf read crc32修正 boot.cmd 源地址高地址读回 0xFF4 字节地址模式未切换检查 U-Boot/Linux 驱动识别日志更新驱动/设备树手动切模式Linux 启动后 MTD 分区不对设备树分区表与烧写布局不一致查看 /proc/mtd同步设备树分区 offset 和 size5. 我踩完这些坑之后养成的几个习惯最后分享几个从这次项目沉淀下来的工作习惯不一定对所有人都适用但对 MPSOC 加大容量 QSPI 的组合确实能省时间。第一先在 U-Boot 里把 Flash 摸透再考虑固化。拿到新板子先把 SD 卡启动调试好进 U-Boot 后用sf probe、sf read、md5sum把 Flash 的识别、读写、高地址访问全部验证一遍。这样能把 Flash 本身的问题和固化流程的问题切成两段。如果 U-Boot 阶段读写正常那固化失败一定出在 BootROM 或 FSBL 配置上排查范围瞬间小很多。第二烧写动作保持“小步快跑 完整校验”。不要一上来就烧整个 32MB 镜像。先烧一个几 KB 的测试文件到 0x0回读对比再按最终布局逐步烧写。-verify一定不要省它能在第一时间告诉你数据是否写坏。第三版本尽量新工具链尽量统一。MPSOC 项目里PetaLinux、Vivado、Vitis 版本匹配问题非常容易触发隐藏 bug。这次如果一上来就用支持 MT25QU256 的新版本 Vivado前两天的排查大概率可以完全省掉。升级工具链本身有成本但相比在旧工具上绕一周升级往往更划算。第四大容量 NOR Flash 先把“地址模式”四个字刻在脑子里。以后遇到超过 16MB 的 QSPI Flash先确认各环节对 4 字节地址模式的支持情况再动手配置。这个习惯帮我避免了不少类似的坑。如果你也在 MPSOC 的 QSPI 固化上卡了很久希望这篇避坑指南能帮你快速定位问题。遇到文中没覆盖的新情况欢迎在评论区一起讨论补充。