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

资讯详情

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

Zynq MPSoC QSPI固化实战:MT25QU256的4字节地址与配置避坑指南

Zynq MPSoC QSPI固化实战:MT25QU256的4字节地址与配置避坑指南

做Zynq UltraScale+ MPSoC的板子,QSPI固化是绕不过去的坎。我最近在一款用MT25QU256做启动Flash的平台上栽了个跟头:程序烧进去能读出来,校验全对,可一断电重启就卡死在FSBL加载之前,串口连个像样的日志都没有。折腾了整整两天,最后发现根本不是硬件焊接问题,而是这颗32MB的Flash在Zynq MPSoC的QSPI固化流程里有一堆“特殊配置”需要提前处理好。今天把这些坑按从原理到实操的顺序整理出来,给准备在这条路上踩雷的朋友们做个参考。

1. 先认识MT25QU256:它为什么比隔壁那颗W25Q“难搞”

1.1 一颗容量32MB却藏着“半扇区陷阱”的Flash

MT25QU256是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起始地址0x0,U-Boot起来一点问题没有;但U-Boot里执行sf read去读一个放在0x1000000(16MB)位置的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放在0x0,U-Boot放在0x400000,image.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,容量为32MB,page 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_cpu=a53-0] fsbl.elf [destination_cpu=a53-0] bitstream.bit [destination_cpu=a53-0] u-boot.elf }

这个文件本身不直接规定每个分区烧录到Flash的哪个物理地址,物理地址是由你在烧写时指定的偏移决定的。比如你用U-Boot把BOOT.BIN写到Flash偏移0x0,那BootROM就会在0x0处找Boot Header,然后按BIF里的顺序依次加载FSBL、bitstream、U-Boot到指定的CPU和内存位置。

这里容易出问题的点在于:如果你把BOOT.BIN整个烧到了Flash偏移0x1000000(16MB)以上的位置,而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 0x2000000

cmp.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、写保护,这些“特殊配置”只要挨个确认清楚,基本都能一把过。如果在调试中遇到类似问题,建议对照上面的排查表,一段一段确认自己的配置,别上来就怀疑硬件。毕竟在这种问题里,九成以上都是配置没对齐,而不是芯片坏了。

返回列表