拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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。板子上选的美光 MT25QU256,256Mb 容量,当时觉得这颗料很常规——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”,它有三个容易被忽略的身份标签,每一个都对应一类坑。

第一个身份:它是 256Mb(32MB)的大容量器件,地址突破 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 列表主要覆盖的是 MT25QU128(ID 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 版本较老,列表里可能只有:

  • MT25QU128
  • N25Q128 / N25Q256
  • 其他厂商的 128Mb 型号

如果确实选不到 MT25QU256,我建议不要纠结,直接选 MT25QU128 作为兼容型号。原因有三点:

  1. MT25QU128 和 MT25QU256 使用同一套 QSPI 指令集,读、写、擦除、状态寄存器操作完全一致,唯一差异是容量和 JEDEC ID;
  2. Vivado 生成 FSBL 时,会根据所选 Flash 型号生成对应的 QSPI 初始化代码(xqspipsu 驱动),MT25QU128 的参数对 MT25QU256 基本通用;
  3. 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.scr(U-Boot 启动脚本)。在较新版本的 PetaLinux 里,boot.cmd 的常见写法如下:

setenv bootargs 'console=ttyPS0,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 只负责读头部和加载 FSBL,FSBL 之后还要读取 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 自动判断容量并切换地址模式的逻辑。如果你的工程还是老版本,有两个补救办法:

  1. 手工给 FSBL 定义一个容量宏(通常是XQSPIPSU_FLASH_SIZE之类),把 Flash 大小设置为 256Mb,重新编译 FSBL;
  2. 调整启动镜像布局,把所有需要 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.BIN

petalinux-create --type project --template zynqMP --name qspi_demo cd qspi_demo petalinux-config --get-hw-description=../xsa/mpsoc_qspi.xsa

在配置界面做三件事:

  1. 在Subsystem AUTO Hardware Settings -> Flash Setting确认 Flash 大小和类型;
  2. 在Image Packaging Configuration -> Root filesystem type选INITRAMFS或SD,取决于根文件系统放在哪;
  3. 确认串口(通常是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 只负责加载 FSBL,FSBL 再根据 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.BIN(FSBL + PMU + ATF + U-Boot)1-2MB
0x0020000环境变量区1MB
0x00300000image.ub(内核 + 设备树 + initramfs)8-16MB
高地址区用户文件 / 数据区剩余空间

把 BOOT.BIN 放在 0x0,image.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.ub:

program_flash -f ./images/linux/image.ub \ -offset 0x300000 \ -flash_type qspi-x4-single \ -fsbl ./images/linux/zynqmp_fsbl.elf \ -verify

3.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 是否能正确读取取决于更多因素。

排查顺序建议:

  1. 检查启动模式引脚。MPSOC 上电时会锁存 BOOT_MODE,如果拨码位置不对,BootROM 可能跑去 SD 卡或 JTAG,根本不会访问 QSPI;
  2. 确认 BOOT.BIN 确实在 0x0 偏移。BootROM 的 QSPI 启动固定从 Flash 起始地址读取 Boot Header,如果你用 program_flash 时不小心加了-offset 0x100000,那 BootROM 什么都读不到;
  3. 确认 Boot Header 校验正确。生成的 BOOT.BIN 里如果 FSBL 版本与 Vivado 版本不匹配,Boot Header 校验字段可能不对,BootROM 会报 CRC 错误;
  4. 检查 QSPI 时钟频率。MPSOC 的 QSPI 控制器默认输出时钟可能偏高,Flash 在未配置状态下读时序不稳定,BootROM 阶段表现为读取超时或数据错误;
  5. 最后才是 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 和 size

5. 我踩完这些坑之后养成的几个习惯

最后分享几个从这次项目沉淀下来的工作习惯,不一定对所有人都适用,但对 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 固化上卡了很久,希望这篇避坑指南能帮你快速定位问题。遇到文中没覆盖的新情况,欢迎在评论区一起讨论补充。

返回列表