做Zynq开发的同学,十有八九都会遇到这个时刻:JTAG在线调试一切正常,代码跑得飞起,但只要一断电,整个系统就像失忆一样回到原点。想要让Zynq上电就自动运行程序,必须把启动镜像固化到非易失存储里。W25Q256FV就是Zynq开发板上非常常见的一颗SPI NOR Flash,容量256Mbit,也就是32MB,通过QSPI接口和Zynq相连。这篇文章就来聊聊怎么用Vitis自带的Program Flash功能,给这块Flash烧写程序,顺便把我在几个不同板子上踩过的高频报错和解决办法做个完整整理。
不管你是刚接触Zynq的新手,还是已经被Flash烧写折磨过几次的开发者,这篇文章应该都能给你一些参考。我会尽量把每一步操作背后为什么这么做讲清楚,而不是只丢给你一串“点这里、点那里”的操作步骤。毕竟,烧写Flash这件事,出问题往往不是你不会点按钮,而是不懂工具背后的工作流程,出了报错就两眼一抹黑。
1. 烧写前的全局认知:Vitis烧Flash到底干了什么
1.1 一条简洁命令背后的三段接力
第一次用Vitis的Program Flash时,很多人以为这会像用普通烧录器一样,直接把文件从JTAG灌进Flash。实际上完全不是这么回事。Zynq的QSPI Flash烧写,其实是一个“接力赛”:先是JTAG接口把一小段引导代码加载到Zynq的内存里,然后由这段代码初始化DDR和QSPI控制器,再配合上位机软件把镜像数据一笔一笔写到Flash里。这个在内存里运行的临时程序,就是FSBL(First Stage Boot Loader)。
这就能回答那个被问烂了的问题:Zynq 7020使用JTAG固化Flash时,必须使用DDR吗?答案是,只要你用的是Vitis/SDK自带的Program Flash功能,就必须要有一块可用的DDR。逻辑不复杂:FSBL本身要运行在内存里,而Zynq的BootROM能用的OCM只有256KB,既要放引导代码又要放烧写协议栈,根本放不下。所以工具链默认先把FSBL加载到DDR里,再由它在DDR里跑起来,完成QSPI控制器初始化、扇区擦除、数据写入这一整套动作。你可以把它类比成用电脑刻录光盘:光盘本身不能自己启动刻录软件,必须靠另一台电脑先跑起来,才能把数据写进去。DDR就是那台“临时电脑”的内存条。
理解了这条链路,很多现象就说得通了。比如烧写时板子必须处于JTAG启动模式,因为只有JTAG模式下BootROM才不会尝试从其他介质启动,而是安静地等调试器把FSBL塞进来,避免烧写过程中发生总线冲突。再比如为什么烧写到一半突然报dll cancelled,很多情况下就是DDR没初始化好、JTAG链抖动或者FSBL根本没跑起来。工具表面上是在“烧Flash”,实际上它先完成了一遍“在内存里启动临时程序”的过程。
1.2 先认识W25Q256FV这颗Flash
W25Q256FV是Winbond的256Mbit SPI NOR Flash,换算下来是32MB容量。Zynq-7000系列内置的QSPI控制器和它就是天生一对,标准SPI、Dual SPI、Quad SPI都支持,硬件上只需要一根片选加4根数据线。它的供电电压是3.3V,JEDEC ID是0xEF4019,其中0xEF是Winbond的厂商ID,0x40是内存类型,0x19是容量代码。烧写工具全靠这个ID来识别芯片,所以后面排查“failed to communicate with the flash chip”时,第一个动作就是确认读回来的ID是不是这串数。
这颗Flash有个很重要的特性:默认工作在3字节地址模式,一次最多寻址16MB。要访问完整的32MB空间,必须切到4字节地址模式。用在Zynq上时,BootROM通常只认Flash前16MB的内容,所以启动镜像一定要放在0偏移处,并且整个BOOT.BIN别太大,塞进前16MB以内最稳妥。烧写工具内部会自己在3字节和4字节地址模式之间切换,不需要你手动敲指令,但如果哪天你脱离Vitis,用裸机代码直接操作这颗Flash,就要格外当心地址模式这个坑。
1.3 为什么推荐用Vitis自带烧写功能
当然,给W25Q256FV烧写不是只有Vitis这一条路,不同场景下选择也不同。我把常见的三种方案放在一起对比过:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Vitis Program Flash | 图形界面、和工程集成、不需要额外硬件 | 必须有DDR、速度一般、Flash型号受工具支持列表限制 | 日常开发调试、单板少量烧写 |
| 外部SPI Flash编程器 | 不依赖Zynq能否运行、支持空板和量产 | 需要额外买设备、板载Flash可能要拆芯片或飞线 | 无DDR板卡、量产、维修备件 |
| U-Boot下sf命令烧写 | 传输速度快、可反复操作、不挑电脑环境 | 得先把U-Boot从SD或JTAG启动起来 | 板子能启动U-Boot时的常规更新 |
如果你手里的板子有DDR,日常开发阶段完全没必要折腾外部编程器。Vitis自带的烧写功能虽然速度不算快,但胜在稳定、链路清晰,出问题也好排查。这篇文章后面的步骤也是围绕这个方案写的。
2. 硬件与软件准备:动手前先做好这些检查
2.1 硬件连接和启动模式设置
烧写Flash之前,先把硬件环境过一遍,能省掉后面一大半的报错排查时间。我这里列的是一个通用检查清单,具体到某块板子时以板卡手册为准:
- JTAG下载器接到板卡的JTAG口。常见的有Xilinx Platform Cable USB II、Digilent JTAG-HS3,以及其他兼容的调试器。
- USB线尽量选短而粗的,带磁环更好。烧写32MB Flash是个持续几分钟的活儿,USB链路不稳定是最容易踩的坑。
- 板卡要独立供电。JTAG接口只提供电平参考,不负责给目标板供电。
- 启动模式拨码开关拨到JTAG boot。Zynq-7000的boot mode引脚是MIO[5:2],JTAG模式对应0000,但不同板卡的拨码位置不一样,务必看手册。
- 如果你用的是定制板,确认一下Flash的WP(写保护)引脚有没有被正确拉高,HOLD引脚有没有被拉高,这两个引脚处理不好,烧写时会出现写不进去或者通信失败。
强调一下启动模式这件事。有次我在一块板子上烧写,一直报target dll cancelled,排查半天发现是拨码开关停留在QSPI模式。板子上电后BootROM拼命去读Flash,JTAG链路被干扰,FSBL加载不进去,烧写自然失败。把拨码拨到JTAG模式再上电,一切就正常了。所以烧写前拨码检查真的不能省。
2.2 Vitis版本、驱动与Flash描述支持
软件环境方面,第一件事是确认驱动。Windows下如果设备管理器里看不到Xilinx USB Cable设备,HW Server就连不上JTAG调试器。Vitis或者Vivado安装的时候会附带Cable Drivers,也可以单独从Vivado安装目录下安装,装完记得重新插拔一次USB线。
第二件事是Vitis版本。建议至少用2020.1以上的版本,新版本对W25Q256FV这类大容量Quad SPI Flash的支持完整很多。老版本SDK在Flash Type下拉框里可能只给你几个老型号,找不到qspi-x4-single,强行选其他型号烧写极易出问题。
第三件事是确认Flash描述。打开Program Flash对话框时,如果Flash Type下拉列表里能找到qspi-x4-single,那没问题。如果找不到,说明工具安装目录里的flash device description文件缺了。处理方法有三个:升级Vitis;从新版本安装目录里拷贝对应的描述文件到老版本路径;或者应急选一个参数接近的型号,烧完务必重新上电验证。最后这条纯属应急,不推荐长期使用,因为不同Flash的擦除指令和地址模式命令有差异,临时凑合容易埋雷。
2.3 创建BOOT.BIN镜像
到了准备镜像的环节,新手最容易问:我直接烧应用程序的elf不行吗?不行。Zynq上电后BootROM不认识普通ELF文件,它只认带Boot Image Header的镜像格式。BOOT.BIN就是一个把FSBL、bitstream、应用程序打包在一起的容器,每个分区都记录了加载地址和长度,BootROM按表加载,缺一个环节启动就失败。
在Vitis里创建BOOT.BIN的操作如下:
- 菜单选择Xilinx → Create Boot Image。
- 选择Create new BIF file,给文件起个名字。
- 按顺序添加分区:第一个分区放FSBL的elf,必须放在0x0;第二个分区放bitstream(如果工程有PL逻辑);第三个分区放应用程序elf。
- 输出文件名写BOOT.BIN,点击Create Image生成。
如果用文本方式看生成的BIF文件,大概是这样的结构:
arch = zynq; fsbl = fsbl.elf; partition = system.bit; partition = app.elf;这里有个容易被忽略的坑:如果工程里生成了PL bitstream,但创建Boot Image时忘了加进去,烧写后PS部分能跑,PL逻辑起不来。别问我是怎么知道的,我第一次给一块带PL的板子烧写,调试了半小时才发现bitstream压根没打进去。
3. 手把手烧写流程实操
3.1 连接目标板并确认器件
准备工作做完,可以正式操作了。先把板卡上电,确认启动模式在JTAG,然后在Vitis菜单栏选择Xilinx → Hardware Manager,打开硬件管理器。点击Open Target,选择本地hw_server,正常情况下连接后能看到目标器件列表,比如xc7z020。这一步能确认JTAG链路通不通,如果这里就看不到芯片,直接跳到4.5节排查。
芯片能被识别是一个好信号,但不代表Flash通信没问题。烧写之前,工具会在后台初始化QSPI控制器并读取Flash ID。如果你在日志里看到Found flash: W25Q256FV类似的信息,说明从Zynq到Flash这一段的通路也是通的。看到这行字,基本上可以放心往下走。
3.2 打开Program Flash并设置参数
器件确认无误后,在Vitis菜单栏选择Xilinx → Program Flash,弹出烧写对话框。这里有四个字段需要认真填:
- Image File:选择刚才生成的BOOT.BIN。
- Offset:保持0x0。这个字段千万别乱改,Zynq BootROM上电后就是去Flash的0地址找Boot Header,写在别处等于没写。
- Flash Type:选择qspi-x4-single。W25Q256FV是一颗单芯片的Quad SPI Flash,如果硬件上只连了单根数据线,也可以选qspi-x1-single,但速度会慢不少。x4和dual的区别也要搞清楚,dual一般指两颗Flash并联,普通板子用不上。
- FSBL File:新版Vitis会自动带出来,如果界面有这个字段且是空的,手动选工程里生成的fsbl.elf。
设置完成后点击Program,烧写开始。整个流程是擦除、编程、校验三个阶段齐头并进的,工具会在日志里一步步打印进度。烧写过程中不要动USB线,不要按复位键,更不要直接断电。W25Q256FV整片擦除时间不短,你要是烧到一半断电,轻则烧写失败,重则Flash里原有的内容都没了。
3.3 烧写日志怎么看
烧写过程中Console窗口会打印类似这样的日志:
****** Program Flash ****** ... Opening target... Enumerating flash devices... Found flash: W25Q256FV Image File: /home/user/workspace/BOOT.BIN Offset: 0x0 Erasing... Programming... Flash programming completed successfully.几行关键信息需要会读。Found flash后面跟着的ID如果就是W25Q256FV,说明Flash识别正常;Erasing阶段工具在擦除扇区,如果Flash里已经写过内容,这一步会稍微久一些;Programming阶段在逐页写入,大镜像需要多等一会儿;最后出现Flash programming completed successfully,恭喜你,烧写成功。
如果你的日志停在某个阶段不动,或者出现Error、Warning字样,别慌,第4章整理了常见报错的排查方法,对着看就行。
3.4 切换到QSPI启动验证
烧写成功不等于万事大吉,最终还要验证从Flash启动是不是真的能跑起来。操作步骤:
- 在Vitis里断开硬件连接,或者直接给板卡断电。
- 把启动模式拨码开关从JTAG切到QSPI。Zynq常见的QSPI模式配置是MIO[5:2]=0100,具体以你板卡手册为准。
- 重新上电,打开串口终端,波特率看你工程代码里的配置,裸机程序大多数是115200。
- 观察串口输出。如果能看到FSBL的启动打印,然后是U-Boot或你的应用日志,说明固化成功。
如果上电后串口一点反应都没有,先别怀疑Flash没烧进去。按4.4节的顺序检查拨码、偏移、串口配置,再决定要不要重新烧一遍。我自己就干过烧写成功但串口助手的波特率配错,结果以为启动失败,折腾了一晚上的蠢事。
4. 高频报错排查实录
4.1 报错信息一:error: flash download failed - target dll has been cancelled
这个报错是在论坛里出现频率最高的Zynq烧写报错,没有之一。它直译过来是“目标DLL已被取消”,听起来像Windows DLL文件出了问题,实际上在Vitis/SDK的环境里,它表示上位机调试器与目标板之间的连接在烧写过程中被中断了。常见诱因有三个:
第一,hw_server进程不稳定。烧写到一半,hw_server崩溃或者被杀掉,连接直接断开。这种情况优先重启Vitis,重新启动hw_server再试。第二,USB链路问题。USB线质量差、接口接触不良、使用过长的延长线,在烧写大镜像时都容易导致连接中断。第三,目标板异常复位。板子的看门狗复位、电源抖动、有人手痒按了复位键,都会让JTAG链路断掉。
排查时按这个顺序走:换一根短而粗的USB线,直接插主机后置USB口;查看Windows设备管理器里JTAG设备有没有在烧写过程中消失;关闭杀毒软件等可能干扰后台进程的程序;检查板卡供电是否稳定。还有一个Windows特有的坑,USB控制器会默认开启选择性暂停节能,烧写到一半USB设备休眠,就会报dll cancelled。去电源管理里把USB选择性暂停设置关掉,这个问题能消掉一大半。
4.2 报错信息二:warning: failed to communicate with the flash chip, read/write operations wi...
这一条报错的关键词是“communicate with the flash chip”——JTAG链路是通的,目标芯片也连着,但FSBL尝试访问Flash时没有得到预期响应。碰到这个报错,先看能不能读到Flash ID:
- 读回来全是FF,大概率是Flash没上电,或者片选信号没拉对。用万用表量一下Flash供电引脚和片选,看有没有跳变。
- 读回来的ID不是0xEF4019,先检查Flash Type选没选对。比如板载是qspi-x4-single,你在工具里选了qspi-x4-dual,ID匹配不上就会报通信失败。
- ID正确但还是通信失败,检查WP和HOLD引脚。这两个引脚如果没拉好,Flash可能被写保护,或者命令时序被打断,报错表现就是读写操作不允许。
还有一类情况是SPI时钟频率过高。手工焊接的板子、飞线连接的Flash,或者走线过长导致信号质量差,SPI时钟跑太快容易通信失败。Vitis的烧写配置里可以尝试降低QSPI时钟频率,虽然烧得慢一点,但至少能稳定写完。
4.3 报错信息三:cannot load flash device description
这个报错说明Vitis安装目录里缺少对应Flash型号的描述文件,纯粹是工具支持列表的问题,不是你的板子坏了。解决思路有几种:升级Vitis到更新版本,新版本一般会补充更多Flash型号;从高版本安装目录拷贝flash描述文件到旧版本对应路径;应急情况下选一个参数接近的型号先烧,但烧完必须重新上电完整验证。
这条报错其实在提醒我们一个工作习惯:烧写前先检查工具里有没有你板载Flash的型号。W25Q256FV在新版Vitis里基本都收录了,但一些冷门Flash、或者很老的Vitis版本就容易触发这个问题。提前检查,比报错后再折腾更省时间。
4.4 报错信息四:烧写成功但上电启动不了
烧写报告completed successfully,但切到QSPI启动后板上没有任何反应。这个情况反而比直接报错更让人头疼,因为问题被藏在了运行阶段。我排查这类问题会按这个顺序走:
第一步,确认启动模式真的切到QSPI了。很多板子有多个拨码开关,经常有人只拨了一个,剩下的还留在JTAG模式。第二步,检查烧写时的Offset。如果填的不是0,BootROM去0地址找Boot Header会扑空。第三步,确认镜像大小。刚才说过W25Q256FV超过16MB的部分需要4字节地址模式,老一点的BootROM可能不认,所以BOOT.BIN要保证在Flash前16MB以内。第四步,串口配置有没有问题。波特率不对、串口接错,都可能导致你以为系统没启动。第五步,用JTAG模式启动一遍确认镜像本身没问题。
按这个顺序排查,大部分启动失败的问题都能定位。要特别提醒的是,不要一上来就怀疑Flash烧坏了。SPI NOR Flash即使写错内容也只是数据问题,重新擦除再写就行,别动不动就把芯片判死刑。
4.5 报错信息五:Vitis下载调试时识别不到芯片
这个问题和烧写Flash强相关,因为烧写前必须先把调试器连上。连接不上,后面什么都无从谈起。常见原因和检查方式我整理成了一张表:
| 检查项 | 操作 | 常见结论 |
|---|---|---|
| 驱动 | 在设备管理器里看有没有Xilinx USB Cable设备 | 缺驱动就装Vivado安装目录下的Cable Drivers |
| 电源 | 确认目标板供电正常,JTAG接口的Vref电压约3.3V | JTAG检测不到目标电压就会连接失败 |
| JTAG链路 | 在Vivado Hardware Manager里看链路上有没有器件 | 只有下载器没有Zynq,查TMS/TCK连接和线序 |
| 复位信号 | 按一下板卡复位键再重新Open Target | 有些板子需要先复位再握手 |
| PS复位 | 检查PS_POR_B上电复位是否释放 | 复位拉低时JTAG能识别但无法初始化 |
这里有个偏门但实际碰到过的情况:Zynq的PS_POR_B被外部电路一直拉低,JTAG能识别到下载器,但目标器件始终初始化不了。测一下复位引脚电平,往往能快刀斩乱麻。
4.6 报错速查表
把上面的报错汇总成一张速查表,方便你烧写的时候放在手边对照:
| 报错信息 | 最可能原因 | 第一处理方法 |
|---|---|---|
| flash download failed - target dll has been cancelled | JTAG连接中断、hw_server崩溃、USB节能 | 换USB线/口,关闭USB节能,重启hw_server |
| failed to communicate with the flash chip | Flash ID不匹配、WP/HOLD引脚异常、供电问题 | 读ID,查引脚,核对Flash Type |
| cannot load flash device description | 工具缺少Flash型号描述 | 升级Vitis或拷贝flash描述文件 |
| 烧写成功但启动不了 | 拨码没切、Offset错误、镜像>16MB | 按4.4节顺序排查 |
| 下载调试不识别芯片 | 驱动、电源、JTAG链路、复位 | 按4.5节表格逐项检查 |
5. 容易被忽略的细节、备选方案与我的实操心得
5.1 烧写前的备份习惯
很多开发板出厂时Flash里已经带了测试程序或者硬件检测镜像,烧写前先把原厂内容读出来备份,是个值得养成的好习惯。Vitis的Program Flash除了写,也提供读Flash的功能,可以在界面里指定读取大小和保存路径,读取速度虽然不快,但胜在方便。或者从U-Boot里用sf read命令也能把内容导出来。
这个习惯在开发阶段无所谓,但等到板子量产、维修、或者需要恢复出厂状态时,就知道备份有多重要了。我就见过有人把带校准数据的板载Flash整片刷掉,最后花了两天时间重新校准才写回来,教训挺深刻的。
5.2 没有DDR的板子怎么固化程序
前面说过,Vitis烧写必须有DDR,那没有DDR的板子怎么办?如果你的板子完全没有DDR,有以下几条路可以走:
第一,用外部SPI Flash编程器。把Flash拆下来放到编程器里烧写,或者用夹子直接夹在板载Flash上编程。这种方案不依赖Zynq能不能跑,适合空板、量产和维修。第二,如果板子有SD卡,可以从SD启动一个运行在OCM里的裸机程序,由这个程序通过QSPI驱动把镜像写进Flash。OCM只有256KB,程序的能力受限制,但只要镜像规划好,烧写是可行的。第三,最不推荐用飞线接树莓派这类方法去给Flash刷写,SPI时序和电平匹配很容易出问题,得不偿失。
5.3 几条实操心得
最后分享几条我在反复烧写W25Q256FV过程中沉淀下来的经验,这些细节在官方文档里基本不会写:
同一块板子连续烧写多次后,偶尔会报dll cancelled,这种时候不要怀疑板子坏了,拔掉USB线重插、重启一次hw_server,绝大多数都能解决。烧写大镜像的时候,不要开着大量占用USB带宽的软件,串口工具的自动重连、大文件同步这些都会干扰JTAG链路的稳定性。另外,烧写文件路径里千万不要有中文和空格,Vitis对中文路径的兼容性很一般,这个老毛病从SDK时代就有,我踩过不只一次。
最后一次烧写完,我习惯把启动模式拨回JTAG再做一次在线确认。这样做有两个好处:一是确认板子本身没被烧坏,二是下次开发调试时不用再折腾拨码开关。这个习惯保持了很久,帮我躲过了好几次低级错误。希望这篇文章能让你少走点弯路,烧写顺利。