拿到一块Zynq板卡,大多数人第一件事是点灯、跑个Hello World,但真正让板子脱离JTAG和电脑独立工作,必须把启动镜像“固化”到板载Flash里。我最近用Vitis给Zynq-7020配合的W25Q256FV做烧写,先后碰到过芯片不识别、Flash下载时报“Target DLL has been cancelled”、烧写过程中提示无法与Flash芯片通信等一系列问题。这篇文章就是以W25Q256FV为对象,把Zynq通过Vitis烧写QSPI Flash的完整流程、每个环节的原理、以及这些报错的真实排查链路讲清楚。适合正在做Zynq-7000系列产品,或者刚接触Vitis烧写、卡在报错里出不来的同学参考。
1. 为什么板卡上选W25Q256FV:这颗Flash和Zynq的搭配逻辑
先别急着打开Vitis,得先搞清楚W25Q256FV到底是什么、在板子上承担什么职责。W25Q256FV是Winbond(华邦)的256Mbit SPI NOR Flash,换算过来是32MB存储空间,供电3.3V,支持标准SPI、Dual SPI、Quad SPI以及QPI模式。它和后面经常见到的W25Q256JV、W25Q128JV是同一个家族,指令集基本兼容,只是工艺、频率和ID有些差异,JEDEC ID通常是0xEF 0x40 0x19,这也是区分Flash型号时最直接的依据。
Zynq-7000系列的PS端自带一个QSPI控制器,通过固定的MIO引脚外接SPI NOR Flash。也就是说,只要在Vivado里把PS端QSPI外设打开,硬件连好线,软件侧就能通过FSBL、U-Boot或者裸机驱动访问这颗Flash。Zynq的上电启动流程是:BootROM先读取QSPI里的启动头,把FSBL加载到OCM(片上内存)运行,FSBL再初始化DDR、配置PL端bitstream、把应用程序加载到DDR里执行。所以这块Flash实际上承担的是“启动介质”的角色,一切脱离JTAG跑的裸机程序、Linux内核和根文件系统,最终都要落到它上面。
那么为什么很多Zynq开发板宁可选W25Q256FV,也不用SD卡或eMMC?我个人的理解是,QSPI NOR在几个维度上比较适合Zynq这种场景:
- 引脚占用少,6根信号线就够,PCB布线压力小。
- 启动时序简单,Zynq BootROM原生支持QSPI启动,不需要额外驱动。
- 可靠性高,NOR Flash的随机读取性能远好于NAND,读写寿命一般标称10万次擦写,做启动介质很稳妥。
- 比SD卡稳,不会出现接触不良、文件系统异常导致启动失败的问题。
- 32MB容量足够放FSBL、bitstream和体积不大的应用程序,一些Linux系统如果裁剪得当也能塞进去。
要注意的是,W25Q256FV不是按照字节随机写入的,写入前必须先擦除,擦除最小单位是4KB扇区,读取则是按页(256字节)或连续读取。只要理解了“先擦后写、擦除粒度大、写地址要落到扇区内”这几个特性,后面看烧写日志就不会一头雾水。
还有一个很多人忽略的点:这颗Flash的地址宽度是24位/32位可切换。256Mbit总容量是32MB,而24位地址最多只能寻址16MB。上电默认是3字节地址模式,要访问高16MB空间,必须切换到4字节地址模式。这个特性直接关系到大镜像烧写和高地址分区,后面细说。
2. 烧写前的硬件核对清单与镜像类型选择
很多人烧写失败不是软件操作问题,而是硬件状态没对上。Vitis烧写Flash和普通JTAG调试有一个显著区别:Program Flash流程会把FSBL下载到Zynq内部运行,由FSBL来初始化QSPI控制器,然后通过JTAG间接完成Flash的擦除、写入和校验。换句话说,FSBL相当于一个“代理”,所以硬件上必须保证PS能正常工作,JTAG链路要通畅,而且QSPI引脚的电平、片选、时钟都要正确。
2.1 连线与电平:先查原理图再动手
Zynq-7000的QSPI控制器信号一般分配在MIO[6:0]这一组里,包括SCLK、DI/DO(IO0/IO1)、IO2、IO3、以及片选,具体哪一个MIO对应哪一个信号,不同板卡可能略有差异,强烈建议打开自己板卡原理图确认,不要照抄网上的引脚编号。确认点主要有三个:
- QSPI Flash的VCC是否和PS端的MIO bank共电源域,一般都要3.3V,如果Flash供电是1.8V而MIO bank是3.3V,通信会不稳定,甚至直接烧坏器件。
- 片选上拉、HOLD引脚和WP引脚的接法是否合理,HOLD和WP如果悬空,会导致进入写保护或者暂停状态,烧写时表现就是Flash ID能读、擦除却没反应。
- 板上是否使用了电平转换或总线开关,有些板子同时让QSPI和别的外设共享MIO,需要通过跳线切换,没有切过去的话信号被别的器件占用,必然通信失败。
如果你用的是杜邦线外接Flash模块来做实验,我劝你趁早放弃。QSPI在几十MHz下对信号完整性还是有要求的,杜邦线接触电阻大、寄生电感高,在2MHz下也许能跑,一旦提高QSPI时钟就会随机报错。板载贴片Flash是首选。
2.2 启动模式拨码:JTAG模式还是QSPI模式
Zynq的启动模式由MIO上的启动模式引脚决定,Vitis烧写时必须把板卡设置为JTAG启动模式。这样做的好处是,BootROM不会尝试从QSPI或SD卡执行代码,JTAG控制器能直接控制PS,FSBL能干净地从OCM启动。有些板卡在QSPI启动模式下也能烧写,但偶尔会发生启动代码和调试器抢总线的情况,排查起来很绕,所以不要给自己添乱。
烧写完成后,再把拨码切回QSPI启动,复位板卡。如果这个顺序搞反,经常出现“烧写显示成功,但一上电根本没反应”的现象。
2.3 镜像类型:BOOT.BIN、BIN还是ELF
Vitis的Program Flash界面能接受多种文件,但要注意它们的作用完全不同:
- BOOT.BIN是最终用于启动的镜像,包含启动头、FSBL、bitstream和应用程序,通常烧写到Flash起始地址0x0。
- FSBL.elf是第一阶段启动加载器,它既可以是BOOT.BIN的一部分,也可以在烧写时作为“过渡代理”,先被JTAG加载到OCM运行,再操作Flash。
- .bit文件是PL配置数据,可以单独烧写,但单独烧写它不代表板子能启动,因为还需要FSBL来引导。
我见过不少初学者直接拿一个hello_world.elf去Program Flash,然后抱怨启动不了。单个elf本身不是启动镜像,必须通过Bootgen或者Vitis的Create Boot Image功能打包成BIN格式才行。所以烧写前脑袋里要有一张图: 烧BOOT.BIN是最终目标,FSBL是烧写过程用的代理,bitstream和应用程序只是BOOT.BIN的组成部分 。
3. Vitis中的实际烧写操作:GUI和命令行两种走法
这一章是操作主线。我以Vitis 2020.1及以上版本为例,因为从2019.2开始Xilinx SDK逐渐被Vitis取代,菜单名和界面有一些变化,但这个流程在Vitis 2020~2023版本里基本通用。
3.1 第一步:在Vivado里导出包含QSPI配置的XSA
烧写的前提是硬件工程要正确。打开Vivado的Block Design,在Zynq PS配置界面里,把QSPI外设打开,选择Single QSPI或Dual QSPI,并确认MIO分配和你板卡实际一致。QSPI的工作频率建议先按默认值,等烧写稳定后再考虑提高速度。然后generate bitstream,完成综合实现。
生成完bitstream后,菜单File -> Export Hardware,勾选Include bitstream,导出XSA文件。这个XSA会带着QSPI外设配置和bitstream信息,是后面Vitis工程的“硬件底座”。如果XSA里忘了包含bitstream,有些烧写环节也能跑,但Program Flash在需要配置PL时会找不到数据。
提示:Vivado和Vitis版本最好匹配,比如Vivado/Vitis 2021.1配2021.1。跨大版本使用XSA经常出现兼容性问题,最常见的现象就是Vitis里建平台时报错,或者打开硬件工程后外设列表为空。
3.2 第二步:在Vitis里创建平台工程并生成FSBL
打开Vitis,设置好Workspace后,用XSA创建Platform Project。平台工程里会自动包含FSBL相关的源文件和BSP。接着创建一个Application Project,模板选择“Empty Application(C)”即可,因为我们只想要一个能编过的裸机hello_world,或者干脆就用平台工程生成的FSBL作为烧写代理。
你可能会问:为什么明明只是烧写Flash,还要创建一个应用工程?原因在于Program Flash需要用到FSBL.elf,而FSBL一般随着平台工程生成。在Vitis里构建平台工程,构建选项里选择Release还是Debug都行,重点是最后能在工程目录下找到
fsbl.elf
。如果你后续要生成BOOT.BIN,还需要有一个应用elf,所以顺手建一个hello_world应用用来验证链路,也是常见做法。
3.3 第三步:确认JTAG连接和芯片识别
烧写前一定先在Vivado Hardware Manager或Vitis的“Xilinx -> Hardware Manager”里确认目标芯片已经被识别。打开Hardware Manager,如果能看到xc7z020或者xc7z010,说明JTAG链路是通的。如果这里都识别不到,后面Program Flash大概率会报“Target DLL has been cancelled”或者“Cannot detect target”。
这一步顺便可以读一眼Flash的ID。有些版本的硬件管理器能直接看到连接的SPI Flash型号,如果显示0xEF4019,说明W25Q256FV在链路上应答正常。注意: 硬件管理器看到的是JTAG链上的器件,不一定代表QSPI通信正常 ,但它至少能证明PS没有死锁、电源没问题。
3.4 第四步:执行Program Flash
在Vitis菜单栏选择Xilinx -> Program Flash,或者直接点击工具栏里的“Program Flash”按钮,弹出对话框后填写:
- Image File:选择BOOT.BIN,或者你想烧写的.bin文件。
- FSBL File:选择前面构建出的fsbl.elf。
- Flash Type:选择qspi-x4-single,这是最常用的Zynq QSPI四线模式。
- Offset:通常填0x0。
- Cable / Target:选择当前连接的JTAG目标。
点Program后,Vitis会先通过JTAG把FSBL加载到OCM,然后由FSBL初始化QSPI,执行擦除、写入、校验的流程。日志窗口会出现类似
Erase Operation. Please wait...
、
Program Operation...
、
Verify Operation...
的提示。整个烧写W25Q256FV的速度取决于镜像大小和QSPI时钟,一般BOOT.BIN在几MB以内,一两分钟内完成都算正常。
除了GUI,命令行方式适合批处理和脚本集成。在Vitis的Xilinx Shell(或Vivado的xsct环境)里可以调用program_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
如果你想在烧写后额外做校验,可以加上
-verify
参数。命令行和GUI底层调用的其实是同一套东西,GUI适合单板调试,命令行适合量产或自动化流程。
烧写完成后,先不要急着断电。把启动模式拨码切换到QSPI启动,按一下板卡复位键,观察串口输出是否出现FSBL的启动日志或hello_world打印。如果这一步通过,说明整个固化流程闭环了。
4. 排查现场:目标取消、Flash通信失败、芯片识别不到
标题里的“常见问题解决”是重点,我按实际排障频率从高到低讲。每次排障我都会坚持一个习惯:先看完整日志,再猜原因,不要一上来就怀疑硬件。
4.1 Error: Flash download failed - Target DLL has been cancelled
这个报错很经典,基本每个用Vitis/SDK烧写过的人都会遇到一次。它的英文原文是
Flash download failed - Target DLL has been cancelled
,中文大概意思就是“Flash下载失败,目标DLL被取消”。很多人看到DLL这个词就以为是Windows动态库损坏,其实不是。
实际排查中,我发现这个报错绝大多数来自JTAG目标连接被中断,而不是Flash本身的问题。日志里如果已经出现“Connecting to target”然后立刻跳到“Target DLL has been cancelled”,优先检查下面几项:
- JTAG连接线是否松动,尤其是那种多根杜邦线拼出来的下载线,接触不良是最常见原因。
- Vitis是否和Vivado Hardware Manager同时打开了同一个目标,两个软件抢占JTAG资源,导致连接被取消。关掉其中一个再试。
- hw_server进程是否异常。Vitis连接目标时依赖后台的hw_server,这个进程如果崩溃或端口被占用,就会中断连接。可以用任务管理器关掉所有hw_server进程,重新打开Vitis再试。
- 板卡电源是否稳定。有些板子JTAG和PS供电是分开的,如果PS没上电,JTAG虽然能看到芯片,但一旦尝试加载FSBL到OCM,就会因为CPU没运行而中止。
- 目标连接地址设置问题。Vitis默认通过本地TCF端口连接,如果之前手动设置过远端地址,端口对不上同样会取消。
还有一个容易忽略的点:如果板卡处于QSPI启动模式,而QSPI里恰好有一个异常镜像,BootROM启动失败导致PS进入异常状态,也可能让JTAG无法建立控制。这时候先把启动模式切成JTAG,再重新连接。
4.2 Warning: failed to communicate with the flash chip
这个日志经常出现在Program Flash的早期阶段,完整信息类似
Warning: failed to communicate with the flash chip, read/write operations will fail
。它的直接含义是:FSBL的QSPI驱动已经尝试和Flash交互,但Flash没有给出正确的响应,或者响应超时。
按我的经验,主要原因有这些:
-
Flash Type选错。Vitis里的Flash Type要选对,W25Q256FV一般用
qspi-x4-single,但你板子如果接的是1-bit SPI模式,那就要改成qspi-x1-single。模式不匹配时,Flash ID可能都读不出来。 - 片选信号没拉低。检查板卡QSPI片选有没有连接到Zynq的QSPI CS引脚,有些板子把CS做成了GPIO控制,需要软件手动拉低,而FSBL默认不操作这个GPIO。
- HOLD引脚被拉低。W25Q256FV的HOLD引脚(通常是IO3)如果被拉低,Flash会暂停通信。检查原理图上HOLD有没有接上拉电阻。
- 供电不稳或Flash没焊接好。不要忽略这个基础问题,我曾遇到一块板子过回流焊后Flash虚焊,导致能识别JTAG但始终无法和Flash通信。
- FSBL里的QSPI配置参数和实际Flash不匹配,比如时钟分频、模式寄存器配置不对。遇到这种情况,可以试试降低QSPI时钟频率,在Vitis的BSP设置里把QSPI时钟调低,排除高速信号问题。
如果上述都排除了,还有一个办法:直接Program Flash界面里手动选择Flash型号。Vitis会弹出一个下拉列表,里面有很多Spansion/Cypress/Micron的QSPI型号,如果你的W25Q256FV没有在列表里,可以手动添加或选择同容量、同类型的兼容型号。选择时要注意地址模式,32MB容量的Flash需要支持4字节地址。
4.3 Vitis下载调试时“不识别芯片”怎么解决
这个热搜问题经常出现在刚装完Vitis的用户身上。现象是Hardware Manager里空白,或者连接时提示“No hardware target detected”。排查思路按下面顺序来:
- 检查驱动。Vitis连JTAG通常依赖FTDI驱动或Digilent驱动。在Windows的设备管理器里,如果看到箭头感叹号,先重装驱动。
- 检查线缆类型。Platform Cable USB II、Digilent JTAG-HS3这类线缆有不同的驱动要求,确认你用的线缆被当前Vitis版本支持。
- 检查板卡的JTAG供电。有的板卡JTAG口需要板子主电源供电,只插USB下载线是不够的。
- 检查多个JTAG器件的菊花链。如果板上有多个JTAG器件,Zynq需要正确配置链的位置,Vitis有时识别不到是因为链上某个器件占用了IDCODE。
- 试着重启硬件服务器。Vitis菜单里有时候有Restart Hardware Server,或者直接关掉所有Vivado/Vitis进程,重新打开。
还有一种情况: 在隔离转接板或仿真头上,JTAG信号的电平和Zynq不匹配 。Zynq的JTAG引脚属于MIO bank,通常电平是1.8V或3.3V,取决于板卡设计。如果下载线是3.3V标准,而Zynq所在bank是1.8V,必须用转换板。
4.4 烧写期间Flash扇区擦除失败
Program Flash是“擦除-写入-校验”三步,如果擦除阶段失败,日志往往会指向某个扇区地址无法擦除,比如出现
Erase timeout
或者反复重试。这里要注意:W25Q256FV的擦除命令分为4KB扇区擦除、32KB块擦除、64KB块擦除和整片擦除。Vitis工具会根据镜像大小和Flash布局自动选择擦除粒度,但个别情况下,工具对这款Flash的支持不够完善,会把擦除命令发错。
这种情况的应急方案有三个:
- 改小镜像尺寸,避开跨块边界的地址,看是否固定在某一个地址失败。
-
手动切到
qspi-x1-single,用最基础的单线SPI模式重试,排除四线模式下IO2/IO3虚焊或配置问题。 - 先用Vivado Hardware Manager里的Flash编程功能把整片擦除,再回到Vitis写。这种做法相当于绕过程序自动擦除。
还有一个点,W25Q256FV有块保护(Block Protection)机制,状态寄存器里的BP位如果被置位,扇区会变成只读。FSBL或历史程序有可能修改过状态寄存器。遇到擦除不了的时候,可以先用Flash编程指令把状态寄存器恢复成非保护状态。Vitis界面里有些版本提供“Erase Entire Device”选项,实际执行时通常也会顺带清保护位。
4.5 烧写成功但上电不启动,或启动一半卡住
这个现象比上面几种更隐蔽。烧写流程全程没报错,校验也通过,但把启动模式切到QSPI后,板卡没有任何反应,或者只打印几行FSBL日志就卡住。
先查启动模式拨码是否真的切到了QSPI,有的板卡拨码丝印和实际逻辑是反的,我之前就被坑过。再查BOOT.BIN的组成是否正确,最稳妥的做法是在Vivado/Vitis的Create Boot Image界面里重新生成一次,确保启动头、FSBL、bitstream、app.elf的顺序和地址偏移正确。
还有一个经常被忽略的点: 如果你的镜像超过16MB,而FSBL或者BootROM没有正确进入4字节地址模式,那高16MB的内容虽然被“写进去”了,但启动时根本读不到 。W25Q256FV上电默认3字节地址模式,Zynq的QSPI驱动虽然能配置4字节模式,但不是所有版本的FSBL都默认开启。遇到大镜像启动失败,先确认FSBL的QSPI驱动配置里地址宽度是否正确,或者干脆把镜像控制在16MB以内验证。
如果启动只卡在FSBL阶段,优先怀疑DDR初始化失败。Zynq的FSBL一大功能就是初始化DDR控制器,如果DDR颗粒型号、时序参数和实际硬件不匹配,FSBL会在DDR测试或者加载阶段卡死。这个和Flash本身没有关系,但很多人不知道,会误以为Flash没烧好。
5. 固化后的启动验证、DDR问题与批量生产建议
5.1 烧写完成后如何“证明”真的好了
很多工程师的习惯是烧完马上断电切拨码,其实更好的顺序是:烧写完成后,保持JTAG连接,先读回Flash内容做个校验,再切到QSPI启动模式。Vitis Program Flash默认带校验,但如果你用的是命令行且没加
-verify
参数,就要额外做一次读回验证。
读回验证有两个层面:
- 在Vitis里重新执行Program Flash,但不勾选擦除,只做Verify,或者使用Hardware Manager的Readback功能,把Flash内容读成二进制文件,和原始BOOT.BIN做对比。
-
在U-Boot或Linux环境下执行
sf probe && sf read,把Flash某段内容读到DDR,再用md5sum和原文件比对。
我个人更推荐第二种,因为它在真实启动环境下验证了Flash可读性。如果U-Boot环境下能正常
sf probe
识别到W25Q256FV,基本说明Flash通信、地址模式、ID识别都正常。
5.2 Zynq-7020用JTAG固化Flash时,必须使用DDR吗
这问题被问得非常多。答案是不必须,但在标准Vitis流程里,DDR往往会被初始化一次,于是很多人产生了“固化Flash必须依赖DDR”的错觉。
Program Flash的机制是:先把FSBL加载到Zynq的OCM里运行,FSBL再去控制QSPI。FSBL是由Vivado硬件工程生成的,如果硬件工程里启用了DDR控制器,FSBL启动时就会初始化DDR。所以你会看到烧写过程中DDR其实已经被初始化了一遍,但这不是“烧写Flash”本身的要求,而是FSBL顺带做的动作。
如果你的板卡没有DDR、DDR焊接有问题、或者DDR颗粒型号和Vivado配置不一致,标准流程就会在FSBL阶段失败,表现为Target DLL被打断、或者FSBL日志卡在DDR初始化相关位置。解决办法不是“必须有DDR”,而是让FSBL跳过DDR初始化,或者使用一个只针对QSPI的FSBL版本。在Vitis的BSP里可以调整FSBL源码,把DDR初始化相关代码注释掉,重新编译FSBL,再用这个FSBL去Program Flash。这样即使板上没有DDR,也能完成QSPI烧写。
对于Zynq-7020这种片内只有256KB OCM的芯片,如果你想在完全没有DDR的情况下跑应用程序,程序必须链接在OCM里。固化这类小镜像时,只要FSBL不初始化DDR,QSPI烧写是可以顺利完成的。
5.3 镜像布局建议:不要一股脑塞在0地址
很多人的BOOT.BIN就是把FSBL、bitstream、app.elf依次排开,全部烧在Flash偏移0x0。这样不是不行,但会给后续升级带来麻烦。更常见的做法是在BIF文件里显式指定每个分区的偏移地址:
the_ROM_image:
{
[bootloader] fsbl.elf
[offset=0x100000] system.bit
[offset=0x500000] app.elf
}
这样FSBL在0x0,bitstream在1MB处,应用程序在5MB处。后续如果只更新app,就不需要把整个Flash擦掉重写,可以按分区擦除和烧写,速度更快,风险也更小。
实际项目中,QSPI Flash不只有启动镜像,还可以划出几个区域用来存参数、日志、或者做OTA备份。W25Q256FV有32MB空间,合理规划分区能让开发和量产都轻松很多。我的建议是至少预留两个镜像槽位:一个当前运行镜像,一个备份镜像,升级时先写备份区,校验成功后再切换,避免升级中途断电导致板子变砖。
5.4 烧写速度优化和量产烧写
开发阶段烧写慢一点无所谓的,但到了量产阶段,几十块板子每块等两三分钟,生产效率就很受影响。烧写速度主要受QSPI时钟频率影响,Vitis默认的FSBL里QSPI时钟通常比较保守,可以适当提高。改FSBL的BSP设置,把QSPI时钟从几十MHz提高到100MHz以上,但前提是板子布线质量要过关,否则会擦写失败或校验不一致。
量产烧写还要考虑另一个问题:不要用JTAG一板一板慢慢烧。如果产品量级达到几百上千,建议优先考虑以下方案:
- 制作烧写底板,用Vivado Hardware Manager配合批量烧写脚本,通过多路JTAG转接板同时烧写多块板。
- 如果产品有网口或USB口,先把一个Bootloader烧进Flash,后续通过Bootloader配合串口或网口升级应用程序,这也是最常见的量产流程。
- 出厂前把W25Q256FV先通过编程器烧好镜像,再贴片到PCB上,适合镜像内容固定、不需要现场烧写的场景。
我个人在实际项目里的习惯是:开发阶段用Vitis GUI烧,方便观察日志;小批量试产用命令行脚本烧,稳定可追溯;真正量产让产线用专用烧录底板。三种方式并不冲突,重要的是把镜像内容、分区布局、烧写版本号都管理好,否则半年后连自己都分不清板上烧的是哪一版。
回到这篇文章的主题,Vitis给Zynq烧写W25Q256FV本身并不神秘,本质就是FSBL当代理、QSPI控制器操作NOR Flash。把硬件连接、启动模式、镜像格式、FSBL可用性这几个要素理清楚,绝大多数报错都能在十分钟内定位。如果以后你在烧写时再遇到“Target DLL has been cancelled”,先别急着重装Vitis,按第4章的链路排查一遍,大概率是连接问题或FSBL启动问题。烧写已经到了最后一步,多花几分钟做一次完整的启动验证,能省下后面一整天的调试时间。
1万+


完整指南与报错排查&spm=1001.2101.3001.11974&articleId=94082850&d=1&t=3&u=105707538777459792cba3d7afbf3732)

被折叠的 条评论
为什么被折叠?



