Zynq MPSoC QSPI固化实战:MT25QU256的4字节地址与配置避坑指南
发布时间:2026/9/29 18:31:58 作者:尧图编辑部 阅读量:1,286

做Zynq UltraScale MPSoC的板子QSPI固化是绕不过去的坎。我最近在一款用MT25QU256做启动Flash的平台上栽了个跟头程序烧进去能读出来校验全对可一断电重启就卡死在FSBL加载之前串口连个像样的日志都没有。折腾了整整两天最后发现根本不是硬件焊接问题而是这颗32MB的Flash在Zynq MPSoC的QSPI固化流程里有一堆“特殊配置”需要提前处理好。今天把这些坑按从原理到实操的顺序整理出来给准备在这条路上踩雷的朋友们做个参考。1. 先认识MT25QU256它为什么比隔壁那颗W25Q“难搞”1.1 一颗容量32MB却藏着“半扇区陷阱”的FlashMT25QU256是Micron出的一款256Mbit串行NOR Flash换算过来正好32MB。容量本身不算夸张但它正好卡在了一个非常尴尬的位置传统的SPI NOR Flash用24位地址3字节地址最多只能寻址16MB超过16MB的部分必须切换成4字节地址模式或者通过扩展地址寄存器去访问。这个“超过16MB”的设定在QSPI固化场景里特别容易出问题。因为Zynq MPSoC的BootROM和FSBL早期阶段默认是按老式3字节地址去读Flash内容的。如果你的BOOT.BIN或者后续的image.ub被放到了Flash偏移0x1000000也就是16MB以上的位置而相关驱动又没有主动切换成4字节地址模式那读出来的数据就是错的表现为启动流程走到一半直接卡死或跳飞。块结构上MT25QU256也有点“个性”。它支持4KB的sub-sector擦除也支持64KB的main sector擦除。很多人图省事一上来就全片擦除擦除时间可以接受但如果你只是更新一个不到1MB的镜像也用全片擦除那后续量产测试的效率会非常难看。实际项目里我习惯先按64KB扇区对齐做区域擦除除非是首次烧写空片。1.2 电压、封装与JEDEC ID每一个都是坑MT25QU256并不是“一个型号走天下”。它按电压分成了1.8V版本MT25QU256ABA和3V版本MT25QU256ABB引脚定义在一些关键功能上并不完全一致。我见过有人拿着3V版本的封装库画了块1.8V的板子上电后Flash根本不能被QSPI控制器识别。这个问题在原理图阶段就要确认清楚等板子回来再查就是纯粹的浪费时间。JEDEC ID方面MT25QU256的ID是0x20 0xBA 0x20。这个ID在Vivado、Vitis的FSBL、U-Boot的spi-nor驱动里都有对应条目但不同软件版本的适配程度不一样。U-Boot 2023.04之后的版本基本是即插即用老版本或者某些裁剪过的BSP里可能压根没把这个ID写进Flash型号表这时你就得手动在驱动里补一条否则sf probe会直接报Unknown flash。写保护也是这颗Flash的常见陷阱。MT25QU256上电后状态寄存器里的BP位不是默认全零如果硬件上WP_N引脚又被拉低那你的擦除和写入命令都会被硬件屏蔽。很多“烧不进去”的故障最后查下来都是这个原因。2. Vivado里的QSPI配置——不少坑从第一站就埋下了2.1 选错Flash型号会导致FSBL不认ID在Vivado的Block Design里配置Zynq UltraScale MPSoC时PS-PL配置界面中有一个QSPI页面里面会让你选择Flash类型。这里一定要选到MT25QU256对应的条目或者至少选择同厂家的兼容型号。这个选择不只是为了“看着对”它会直接影响Vitis/Vivado生成FSBL时使用的QSPI驱动配置表。FSBL在启动时会向Flash发送读ID命令然后把读回来的JEDEC ID和驱动内置的型号表比对。如果型号不匹配虽然底层读写寄存器还能工作但后续的Flash size、page size、sector size都会用默认值一旦你实际烧写的分区大小超过驱动认为的容量FSBL就会在加载阶段翻车。有些版本的Vivado里MT25QU256还区分x1_x2_x4和x1_x2_x4_x8这个跟硬件接法强相关。你在原理图里只画了一颗Flash数据线只引出4根那就选普通的x4模式如果画了两颗Flash做并联数据线加起来8根那就得找对应8位模式的型号条目。这个细节直接关系到下一个坑。2.2 Single、Dual Stack你搞混了轻则启动失败重则数据错乱Zynq MPSoC的QSPI控制器支持两种比较常见的Flash连接方式Single模式和Dual Stack模式。Single模式就是常规的一颗Flash用MOSI/MISO两线或者四线IO0~IO3通信Dual Stack模式下两颗Flash共用命令和时钟数据线合在一起组成8位总线控制器把相邻地址交替分配到两颗Flash上从而在同样的时钟频率下把吞吐量翻倍。这个模式配置必须和硬件原理图严格对应。如果板子上明明只有一颗MT25QU256你却在Vivado里选了Dual Stack控制器会按两片Flash交错映射地址来访问FSBL读到的Boot Header内容全是乱的启动自然失败。反过来如果板子上有两颗Flash组成x8阵列你却选了Single模式那控制器一次只能访问到其中一颗后面的镜像全部丢失。我自己的排查经验是先看原理图数一下Flash的数据引脚是4根还是8根再看Vivado里QSPI Mode选的是Single还是Dual。两边一致了再往下查软件配置不然都是白费功夫。2.3 时钟频率、读取命令与Dummy Cycle要跟Flash握手Zynq MPSoC的QSPI控制器时钟来自PS端的IO PLL可以配置分频。启动阶段BootROM会用一个相对保守的频率去读Flash但FSBL和U-Boot起来之后驱动会按照设备树或者BSP里的配置去重新初始化QSPI控制器这时频率可能被拉高。MT25QU256在高速读模式下需要插入Dummy Cycle空周期这个值不是固定的它和读取命令、时钟频率都有关系。如果驱动发包时用的Dummy Cycle数量和Flash当前非易失配置寄存器里的值不一致读回来的数据就会错位典型表现是能读到ID但读数据全是0xFF或者读出来的数据每隔一段就丢几个字节。稳妥的做法是启动阶段不要追求极限频率先把QSPI时钟压在40MHz以内跑通流程等整个启动链路没问题了再慢慢往上拉。另外如果你用烧录器或者命令行工具改过Flash内部的非易失配置寄存器务必确认Dummy Cycle设置是否被一起改了这个不查清楚会浪费大量时间。3. 真正的大坑32MB Flash的4字节地址模式3.1 为什么16MB是“分界线”前面已经提过传统SPI NOR Flash用3字节地址只能访问16MB空间。MT25QU256是32MB如果你不做任何特殊处理驱动在3字节地址模式下最多只能访问到Flash偏移0xFFFFFF的位置也就是前16MB。在实际固化中这个限制会以各种“莫名其妙”的形式暴露出来。最常见的场景是BOOT.BIN只有几MB放在Flash起始地址0x0U-Boot起来一点问题没有但U-Boot里执行sf read去读一个放在0x100000016MB位置的image.ub时读回来的数据根本不是Flash里存的内容。这是因为U-Boot驱动在3字节地址模式下访问高地址时地址回绕到了低16MB的某个位置数据当然对不上。还有个隐蔽场景跟BootROM有关。Zynq MPSoC的BootROM在启动早期会不会主动切换4字节模式说实话受版本和配置影响很大我不建议赌这个行为。最可靠的做法是让所有启动关键内容都放在16MB以内或者明确让FSBL/U-Boot去切换4字节模式。两条路选一条不要模棱两可。3.2 进入4字节地址模式的正确姿势MT25QU256支持通过命令进入4字节地址模式。标准的Micron命令是B7h进入4字节模式E8h退出4字节模式。上电后默认是3字节模式除非你把非易失配置寄存器里的对应位也改成上电即4字节模式。如果你用的是U-Boot而且版本不算太老spi-nor驱动通常会自动识别这颗Flash并处理好4字节模式切换。这也是为什么很多人用U-Boot时没觉得这是个坑因为驱动已经替你做了。但在FSBL阶段就不一样了Vitis生成的FSBL里QSPI驱动对Flash的适配能力比U-Boot弱得多如果你用的BSP版本比较旧可能根本不会去发B7h命令。这时你有两个选择。一个是改FSBL的BSP配置在QSPI驱动的Flash参数表里明确加上MT25QU256的4字节模式支持另一个更省事的办法是先用烧录工具把MT25QU256的非易失配置寄存器设置成上电即4字节模式。不过第二个办法有个副作用以后这颗Flash无论放到哪个板子上都会默认以4字节模式上电如果别的工程固件里没有对应的处理逻辑反而会带来新的兼容问题。我个人不太建议在产品里这么干。3.3 是“全片4字节”还是“前16MB兼容模式”这里想给一个实操建议在产品固件的分区规划阶段就先决定到底要不要用4字节模式。如果产品功能简单BOOT.BIN放在0x0U-Boot放在0x400000image.ub放在0x1000000以内根文件系统用别的介质或者放在更后面由内核自己挂载那么你完全不用纠结4字节模式。保持Flash默认3字节模式FSBL和U-Boot用默认配置就能跑得很稳这是最不容易出错的方案。如果你的image.ub有几十MB或者需要把根文件系统也放到同一个Flash里那前16MB肯定不够用就必须走4字节模式。这种情况下你要保证从FSBL到U-Boot再到内核的整个链路里QSPI驱动的4字节模式支持都是开启的。中间断了一环就会出现“U-Boot能读内核挂载失败”这种夹生问题。我踩过一次最憋屈的坑U-Boot下sf read读高地址完全正常但内核起来后挂载JFFS2分区总是报错。查了半天才发现U-Boot驱动在读取时自动切换了4字节模式但内核里的SPI NOR驱动没有做对应配置两边状态不一致数据交错读取文件系统自然就废了。4. FSBL、Bootgen、U-Boot三者的协同配置4.1 FSBL BSP中如何对齐Flash ID如果使用Vitis 2024.2版本工程结构比老SDK时代清晰了一些但FSBL里QSPI驱动的配置逻辑没变。当你新建FSBL工程后可以在BSP目录下找到xqspipsu_g.c或者类似的驱动配置文件里面有一张大表记录了各种支持的Flash型号、容量、JEDEC ID、page size、sector size等信息。FSBL启动时QSPI驱动会调用XQspiPsu_FlashReadID之类接口去读Flash的JEDEC ID然后在表中查找匹配项。如果你的这颗MT25QU256不在表里驱动会返回一个未知设备错误表现出来就是串口打印卡在xqspipsu初始化附近后续的DDR初始化、镜像加载全部不执行。解决办法有两种。一是直接在配置表里加一行把MT25QU256的ID和参数填进去类似这样{ 0x20BA20, 0x2000000, 0x100, 0x10000, 0x0 },这行数据的含义大致是JEDEC ID为0x20BA20容量为32MBpage size为256字节sector size为64KB扇区擦除命令使用默认值。具体结构体字段顺序要以你当前Vitis版本里的驱动头文件为准不同小版本可能有差异。二是干脆修改FSBL代码在QSPI初始化之后主动发送一次B7h命令强制进入4字节模式。这个方案更粗暴但能解决高地址访问问题。注意如果驱动里的Flash型号表没有MT25QU256你还得在表中补上ID否则同样会卡初始化。4.2 BIF文件里分区地址别乱写用Vitis/Bootgen生成BOOT.BIN时BIF文件的内容直接决定了启动镜像里有哪些分区、每个分区怎么加载。对于Zynq UltraScale MPSoC典型的BIF文件长这样the_ROM_image: { [bootloader, destination_cpua53-0] fsbl.elf [destination_cpua53-0] bitstream.bit [destination_cpua53-0] u-boot.elf }这个文件本身不直接规定每个分区烧录到Flash的哪个物理地址物理地址是由你在烧写时指定的偏移决定的。比如你用U-Boot把BOOT.BIN写到Flash偏移0x0那BootROM就会在0x0处找Boot Header然后按BIF里的顺序依次加载FSBL、bitstream、U-Boot到指定的CPU和内存位置。这里容易出问题的点在于如果你把BOOT.BIN整个烧到了Flash偏移0x100000016MB以上的位置而Flash还处于3字节地址模式BootROM读到的Boot Header可能就是错的整个启动流程直接不成立。所以BOOT.BIN的分区偏移我强烈建议放在0x0或者非常靠前的位置尽量不要跨过16MB分界线。4.3 U-Boot端的环境变量与MTD分区U-Boot启动之后QSPI Flash的访问就灵活多了。sf probe命令能识别Flash之后就能用sf read把Flash内容读到内存再用bootm启动内核。这里的关键是环境变量里的偏移地址要和在Flash里的实际烧写位置保持一致。举个例子如果image.ub在Flash偏移0x2000000处那U-Boot里应该这样操作setenv bootcmd sf probe; sf read 0x20000000 0x2000000 0x1000000; bootm 0x20000000 saveenv这里sf read的三个参数分别是内存加载地址、Flash偏移地址、读取长度。很多人会把这几个地址搞混尤其是内存加载地址不同板卡的DDR布局不一样最好先bdinfo查看一下可用内存范围别把镜像加载到了DDR的保留区域。另外如果Linux内核里也用到了QSPI分区那么U-Boot设备树和内核设备树里的MTD分区表必须对上。分区名、偏移、大小有一处不一致内核启动后就会出现分区重叠或者数据读不到的问题。5. 烧写与验证两种路线怎么避开反复重启的循环5.1 先解锁再擦除别跟写保护硬刚不管用哪种方式烧写第一步都是确认Flash没有被写保护。MT25QU256的写保护来自两方面一是WP_N引脚的电平二是状态寄存器里的BP位。如果硬件上WP_N被拉低软件层面发什么解锁命令都白搭。所以先拿万用表量一下WP_N引脚确保它在烧写过程中是高电平。然后再通过软件清除状态寄存器里的BP位。在U-Boot下可以直接用sf probe sf protect off 0x0 0x2000000如果U-Boot的spi-nor驱动对MT25QU256支持得好sf protect off会帮你清理状态寄存器。如果提示命令不支持那就得手动发写状态寄存器命令或者用Vivado Hardware Manager里的间接编程功能去清。擦除的时候注意对齐。MT25QU256的64KB扇区要求起始地址必须按64KB对齐如果你从0x10000开始擦没有问题但如果你从0x10001开始擦驱动一般会报错有的驱动不报错但会默默地帮你向上或向下取整最后烧进去的位置和你预期的不一样。所以写代码或者敲命令时偏移地址一定要算清楚。5.2 program_flash与U-Boot写Flash的实测命令在Vitis环境下最省事的烧写方式是直接用program_flash工具。它通过JTAG/TCF连接板卡会先把一个FSBL加载到CPU上执行然后用这个FSBL去初始化QSPI控制器再把镜像写入Flash。典型命令如下program_flash -f BOOT.BIN -offset 0x0 -flash_type qspi-x4-single \ -fsbl FSBL.elf -cable type xilinx_tcf url TCP:127.0.0.1:3121这里-flash_type参数要看你的实际硬件配置qspi-x4-single表示一颗Flash的x4模式如果你用的是两片x8阵列可能要换成qspi-x8-dual之类的写法。-offset 0x0表示从Flash起始地址开始烧这个参数要和你的启动规划一致。如果是想直接更新某个分区比如只更新image.ub可以把-offset改成对应分区的物理偏移-f参数指向新的image.ub文件。这样不用整片擦除速度快很多。U-Boot下的烧写命令也很直观sf probe sf erase 0x0 0x2000000 tftp 0x100000 BOOT.BIN sf write 0x100000 0x0 ${filesize}先擦除整片再通过tftp把BOOT.BIN下载到内存0x100000处然后写入Flash偏移0x0。写完之后最好读回来校验一下sf read 0x100000 0x0 0x2000000 cmp.b 0x100000 0x100000 0x2000000cmp.b会把内存里的数据和Flash读回的数据逐字节比较输出OK说明烧写成功。这一步千万别省尤其是刚接触QSPI烧写的时候十次里有八次问题都是数据没写全或者地址算错校验能第一时间暴露。5.3 断电重启的完整验证流程烧写完成后的第一次断电重启建议串口全程盯着看。完整启动链路会经历这么几个阶段BootROM初始化、FSBL加载、DDR初始化、U-Boot加载、内核启动。每个阶段在串口上都有特征日志可以用来快速定位问题。如果断电后串口完全没有输出连BootROM的打印都没有那问题大概率不在Flash配置上先检查电源、时钟和启动模式引脚。Zynq MPSoC的启动模式由BOOT_MODE引脚决定如果引脚电平配置成了SD卡启动或者JTAG启动QSPI里即使烧了正确镜像也不会执行。如果串口有BootROM打印但卡在FSBL早期优先怀疑QSPI Flash的识别和配置问题。按照前面的思路检查JEDEC ID是否匹配、Flash型号表有没有添加、4字节模式是否必要。如果FSBL过了U-Boot起来了但启动内核时找不到根文件系统那就要查U-Boot里的sf read地址、内核cmdline里的root参数、以及MTD分区表是否三者一致。这个阶段的问题往往不是Flash本身而是各软件层次之间的地址约定没对齐。6. 常见问题排查与解决实录6.1 问题速查表我发现把这几个典型问题和对应解法整理成表格在实际Debug时非常有用直接照着对症状就能缩小排查范围。症状直接原因处理方式烧写后上电无任何打印启动模式引脚不对或电源问题检查BOOT_MODE引脚电平量Flash供电电压FSBL在QSPI初始化处卡死JEDEC ID匹配失败在驱动配置表中添加MT25QU256的ID和参数能识别Flash但读0x1000000以上全FF3字节地址模式访问高地址回绕切换4字节地址模式或把内容改放到16MB以内U-Boot的sf probe显示Unknown flash驱动版本老Flash型号表缺失升级U-Boot版本或手动添加mt25qu256条目擦除命令报写保护错误WP_N引脚被拉低或BP位被置位硬件拉高WP_N软件清状态寄存器BP位烧写校验通过但启动卡死Boot Header被破坏或烧写偏移与规划不一致重新生成BOOT.BIN确认烧写偏移为0x0内核挂载Flash分区失败U-Boot和内核的MTD分区表不一致对齐设备树中的MTD分区定义6.2 几条保命经验再分享几个我在实际项目里总结出来的习惯。第一新板子回来先别急着烧系统。先用U-Boot或者Vivado Hardware Manager读一次Flash ID确认控制器能和Flash正常通信。这一步能排除一大半硬件问题。第二量产固件里如果不需要4字节模式就不要去动Flash的非易失配置寄存器。这个寄存器改一次就能永久生效一旦固化到片上后续所有板卡都会继承这个状态很容易留下隐蔽的兼容性隐患。第三同时维护多个工程的时候BOOT.BIN、image.ub和Flash烧写偏移最好用一个表格记录下来放到项目文档里。这听起来像小题大做但实际调试时“你烧的偏移是0x100000还是0x1000000”这种问题能瞬间把人的思路带到沟里去。第四Vivado和Vitis版本会影响QSPI驱动对MT25QU256的支持程度。如果项目不是必须锁定版本建议直接用2020.1之后的版本至少在Flash型号匹配和驱动兼容性上会省很多事。MT25QU256这颗Flash本身性能不差但在Zynq MPSoC的固化流程里确实需要多花点心思。电压版本、Single/Dual模式、4字节地址、Dummy Cycle、写保护这些“特殊配置”只要挨个确认清楚基本都能一把过。如果在调试中遇到类似问题建议对照上面的排查表一段一段确认自己的配置别上来就怀疑硬件。毕竟在这种问题里九成以上都是配置没对齐而不是芯片坏了。