☰
ZYNQ程序固化到Flash完整指南:启动流程、QSPI/NAND烧写与常见报错排查
2026/10/5 4:36:53 网站建设 项目流程

搞过ZYNQ的朋友应该都有这种体验:在Vivado里把硬件工程跑通了、SDK里应用程序也编译过了,仿真一切正常,结果到了要把程序固化到flash这一步,突然卡住。网上搜一圈,要么是零零散散的截图,要么是只说“点Program Flash就行”的教程,真到自己动手,各种报错扑面而来。这篇文章我就把自己实际烧写ZYNQ程序到flash的完整过程、踩过的坑、排查思路都整理出来,从启动流程到FSBL的作用,从QSPI和NAND的选择到具体的烧写命令,一次性讲透。

这里要说明一下,这篇文章面向的是所有用ZYNQ做开发的朋友,不管是刚上手的新手,还是被烧写问题折磨的老手,应该都能从中找到自己需要的东西。内容以Xilinx官方工具链(Vivado + SDK/Vitis)为主线,覆盖QSPI Flash和NAND Flash两种最常见的固化场景,同时也会提到SD卡启动的替代方案,以及在工程实践中经常碰到的几个典型报错。

1. 烧写之前必须搞清楚的启动流程

很多人在烧写flash这一步翻车,根源不是操作不对,而是对整个启动流程理解不够。ZYNQ的启动机制和单片机完全不一样,别拿STM32那套思路往上面套。

1.1 ZYNQ的启动镜像到底由什么组成

先看一个最核心的问题:ZYNQ从flash启动时,硬件上电后到底执行了什么?答案不是你的应用程序,而是一级引导程序,后面跟着FSBL、bitstream、SSBL(通常是U-Boot)和应用程序。

整个镜像文件被称为BOOT.bin,它的内部结构大致是:

  • 启动头(Boot Header):包含镜像描述信息、加密选项、校验值等,是BootROM解析镜像的入口。
  • FSBL(First Stage Boot Loader):由Xilinx提供模板,负责初始化DDR、时钟、MIO等,并加载后续镜像。
  • 硬件比特流(bitstream):如果PL端有逻辑,比特流由FSBL加载到PL。
  • 第二阶段引导程序或应用程序(U-Boot或裸机程序):最终交到用户程序执行。

上电时,ZYNQ片内的BootROM会先运行,根据MIO引脚的电平配置决定从哪个接口启动,比如QSPI、NAND、SD或者JTAG。BootROM把BOOT.bin开头的一段代码读入片上RAM,然后跳转执行,也就是进入FSBL流程。

所以你会看到SDK里烧写flash时,强制要求先选择一个FSBL文件,提示“A valid FSBL file is required for flash operation”。这不是Xilinx故意为难你,而是烧写工具必须用FSBL来完成flash的初始化和擦写操作。

1.2 QSPI、NAND和SD卡,启动介质怎么选

ZYNQ支持的启动介质主要是这几种:QSPI Flash、NAND Flash、SD卡,还有JTAG调试模式。选哪个,取决于你的应用场景。

QSPI NOR Flash是最常用的。它的优点在于接口简单、读取速度快、可靠性高,而且Xilinx的IP核支持得很完善。容量一般在16MB到128MB之间,对于中小型工程绰绰有余。大部分开发板,比如米尔、正点原子、黑金这些,板载的都是QSPI Flash。如果是裸机程序或者Linux系统比较精简,QSPI完全够用。

NAND Flash的优势在容量,动辄512MB甚至更大,适合存放大的文件系统、视频数据等。但NAND本身有坏块管理、ECC校验等一堆问题,而且ZYNQ支持的NAND型号有限,在Xilinx官方文档UG585里有详细的兼容列表。买芯片前一定要查一下型号在不在支持范围内,否则就算烧写成功也可能启动异常。

SD卡启动是另一条路,严格来说不算烧写flash,但也经常被用来运行Linux系统。SD卡容量大、方便更换,开发阶段尤其好用。很多基于ZYNQ的bootloader在线升级设计,就是SD卡启动Linux,配合应用程序去更新QSPI里的镜像。

我的建议是:产品量产用QSPI固化,开发调试用SD卡,NAND除非有容量刚需否则尽量避开。理由很简单,QSPI在工具链支持上最成熟,出问题的概率最低,这一点在后面的烧写步骤里会有体现。

2. 烧写方案选型与工具分析

搞清楚启动流程之后,接下来要选烧写方案。ZYNQ烧写flash的途径主要有三条:SDK/Vitis图形界面烧写、命令行工具烧写、第三方烧写器烧写。三者各有适用场景,不能一概而论。

2.1 SDK图形界面烧写和命令行烧写怎么取舍

图形界面烧写是入门首选。在Vitis(旧版SDK)里连接开发板,右键点击FSBL工程,选择Run As → Launch on Hardware,先把FSBL下载到板上跑起来,然后选择Xilinx → Program Flash,填写flash类型、镜像路径等参数,点Program就完事。

命令行烧写用的是program_flash工具,它支持更灵活的脚本化操作,适合产线批量烧写和自动化集成。比如可以写成一条命令:

program_flash -f BOOT.bin -offset 0 -flash_type qspi-x4-single -fsbl fsbl.elf -cable type xilinx_tcf url TCP:127.0.0.1:3121

这条命令的效果和图形界面是一样的,但可以放进CI脚本里,或者封装成产线工具,效率和可维护性都高得多。

另外还有一个更新一点的方案是用xsdb + programmer命令行。xsdb是Xilinx的调试服务器,配合tcf agent可以实现远程烧写,这在板卡不在手边的时候特别有用。

2.2 JTAG、串口、第三方烧写器到底有什么区别

烧写通道的选择同样关键。ZYNQ最常用的烧写通道是JTAG,因为JTAG直接链到CPU的调试访问端口(DAP),可以控制CPU执行任意代码,包括FSBL。通过JTAG加载FSBL后,FSBL再对接flash读写,这就是“JTAG烧写”的本质。

串口烧写则是另一种思路,它不着眼于JTAG链,而是通过UART把镜像传给一个已经运行起来的引导程序(比如U-Boot),由U-Boot把数据写入flash。这种方式在现场升级时用得比较多,因为现场不一定有JTAG调试器。

但你搜“串口烧写失败”会发现一堆帖子,原因主要集中在波特率不匹配、流控打开、镜像带校验头导致U-Boot拒绝写入等方面。我个人建议是能用JTAG就用JTAG,串口烧写适合用在产品已经交付后的现场升级场景。

第三方烧写器,比如之前相关搜索里出现的BeeProg2,属于通用编程器,直接夹在flash芯片引脚上烧。这种方案通常用于空板贴片前的预烧写,或者flash焊在板上但JTAG链路被占用的情况。用编程器烧写要特别注意芯片封装适配器和电压匹配,稍有疏忽就可能损伤芯片。

3. 详细实操:编译BOOT.bin并烧写QSPI Flash

现在进入正题,以QSPI Flash烧写为例,完整走一遍从构建镜像到烧写验证的流程。

3.1 从硬件工程到BOOT.bin的完整构建流程

第一步,在Vivado里完成硬件工程的综合、实现并导出硬件平台。注意勾选“Include bitstream”,这样导出的XSA文件里才会包含PL端配置。如果你只需要PS端跑裸机,不需要PL逻辑,那XSA里也可以没有bitstream,但FSBL会跳过PL初始化,这个没关系。

第二步,打开Vitis,基于XSA创建platform工程,然后创建一个FSBL工程。FSBL可以从Xilinx提供的模板生成,在Vitis里新建Application Project时,模板列表里找到“Zynq FSBL”,直接生成即可。

第三步,创建你的应用程序工程。如果是裸机程序,编译生成ELF;如果是Linux系统,这一步就要用PetaLinux生成U-Boot和image.ub,然后把FSBL、bitstream、U-Boot打包。

第四步,生成BOOT.bin。在Vitis的Xilinx菜单下选择Create Boot Image,界面里按顺序添加:

  • 分区1:FSBL,类型选bootloader,文件为fsbl.elf
  • 分区2:bitstream,文件为design_1_wrapper.bit
  • 分区3:应用程序或U-Boot,文件为app.elf或u-boot.elf

生成后得到一个BOOT.bin。如果用PetaLinux,可以用以下命令一条龙生成:

petalinux-package --boot --fsbl --fpga --u-boot --force

这条命令会自动把FSBL、比特流和U-Boot打包成BOOT.bin。

3.2 使用Vitis图形界面烧写QSPI Flash

烧写前先准备硬件环境:开发板通电,JTAG调试器(Digilent JTAG-HS2/3或Xilinx Platform Cable USB)连接到PC,板卡上启动模式跳线设为JTAG模式。注意,如果跳线设成了QSPI启动,JTAG链路可能被BootROM引导流程干扰,导致下载失败。

打开Vitis,连接目标板。可以先运行一个空的FSBL到板上,确保JTAG链路和DDR初始化正常。然后在菜单栏选择Xilinx → Program Flash,弹窗里这样配置:

  • Image File:选择BOOT.bin路径
  • Offset:填0x0
  • Flash Type:选qspi-x4-single或qspi-x1-single,依据原理图上的连接方式
  • FSBL File:选择fsbl.elf
  • 勾选Verify after flash

点击Program,工具会自动通过JTAG把FSBL加载到片上RAM运行,然后FSBL初始化QSPI控制器,执行擦除、编程、校验。QSPI Flash容量不大,比如16MB,整个流程通常在一两分钟内完成。如果镜像比较大,比如几十MB,时间会明显变长,这时候不要误以为卡死了,看右下角log进度就行。

烧写完成后,把启动模式跳线改到QSPI,重新上电。如果一切正常,程序会自己跑起来。如果板子没有反应,优先检查BOOT.bin里的FSBL分区是不是放在第一个,以及启动模式引脚的电平组合是否和板卡手册一致。

3.3 命令行烧写与u-boot下烧写NAND Flash

QSPI用图形界面够了,但NAND Flash的烧写情况复杂一些,因为NAND有坏块、页大小、OOB区等概念,Xilinx的图形界面支持度不如QSPI好。到这一步我建议转用命令行工具或者U-Boot来烧写。

用program_flash命令烧写NAND的典型用法:

program_flash -f BOOT.bin -offset 0x0 -flash_type nand-x8 -fsbl fsbl.elf -verify

需要注意flash_type参数必须与硬件实际使用的NAND颗粒规格一致,x8还是x16,是否带ECC,这些参数在UG585或者Vitis文档里有明确说明。烧写NAND时,FSBL里也需要正确配置NAND驱动,否则擦写会失败。

另一种常见做法是通过U-Boot烧写。先让板子从SD卡启动进入U-Boot,然后用tftp把镜像下载到DDR,再用nand erase、nand write命令写入。流程如下:

setenv ipaddr 192.168.1.100 setenv serverip 192.168.1.10 tftp 0x3000000 BOOT.bin nand erase 0x0 0x800000 nand write 0x3000000 0x0 0x800000

这种方式的好处是不依赖JTAG调试器,现场只需要网线和串口线就能完成升级。这个思路其实就是很多“基于ZYNQ的bootloader在线升级设计”的核心——系统运行起来之后,通过自定义应用程序读取新镜像,然后调用flash驱动完成在线更新。这里有个大坑:写入长度必须是页大小的整数倍,NAND的页大小常见是2KB或4KB,算错的话末尾数据会丢失。

3.4 制作SD卡启动盘并合理规避flash烧写风险

有时候flash烧写频繁出错,或者你只是想快速验证Linux系统,我会建议干脆先用SD卡启动。SD卡启动的镜像文件不是BOOT.bin,而是需要一张包含BOOT.bin(由FSBL+bitstream+U-Boot组成)和image.ub的SD卡。

制作SD卡启动盘的一般步骤:

  1. 用分区工具把SD卡分成两个分区:第一个分区格式化为FAT32,放BOOT.bin和boot.scr;第二个分区格式化为ext4,放image.ub和根文件系统。
  2. 将PetaLinux生成的BOOT.bin、boot.scr、image.ub复制到对应分区。
  3. 插入SD卡,开发板跳线设为SD启动,上电。

SD卡启动非常适合开发阶段反复调试,不会磨损板载flash,也不怕烧错变砖。很多ZYNQ在线升级方案,就是利用SD卡启动Linux,在Linux里跑一个升级服务,接收新的固件包,然后把固件写入QSPI Flash,实现不拆机升级。这种方案绕开了直接烧flash的风险,升级失败还可以回退到SD卡系统,容错性很好。

4. 常见问题与排查技巧实录

这部分我直接整理成速查表的形式,每个问题都附上排查思路和解决方法。下面这些报错,基本覆盖了90%以上的人会遇到的场景。

4.1 Error: Flash Download Failed - Target DLL has been cancelled

这个报错太经典了,几乎每个用Vitis烧写的人都会碰上一次。它的含义是烧写过程中,目标DLL被中止,通常是JTAG链路出现异常,或者FSBL运行崩溃导致烧写进程中断。

排查顺序按下面的来:

  • 检查JTAG连接:确认调试器被PC识别,并检查板卡JTAG链路是否完整,部分开发板JTAG和QSPI/其他外设共用引脚,看板卡手册确认是否有跳线冲突。
  • 检查启动模式:烧写时跳线务必设在JTAG模式,如果设成了QSPI或SD启动,BootROM的启动流程可能干扰FSBL的执行。
  • 检查FSBL是否选对:Program Flash对话框里FSBL File一栏,必须指向与当前硬件匹配的FSBL。如果FSBL是别的板子的,初始化DDR或QSPI时就会崩,然后报这个错。
  • 检查电源稳定性:ZYNQ对电源纹波比较敏感,尤其是DDR供电异常时,FSBL初始化DDR会卡住,表现就是Target DLL cancelled。

4.2 A valid FSBL file is required for flash operation

这个提示是Vitis在Program Flash时弹出的,很多新手会问“我明明选了BOOT.bin,为什么还要FSBL?”原因前面讲过——BOOT.bin里的FSBL在烧写过程中不一定会被重新执行,而Program Flash功能需要FSBL来初始化flash控制器并执行驱动。

注意BOOT.bin里已经有FSBL了,但你仍然需要在Program Flash对话框里单独指定fsbl.elf。这是工具的设计要求,不是可以省掉的选项。如果找不到fsbl.elf,回到platform工程旁边的FSBL工程里找Debug或Release目录。

4.3 Can't perform JTAG flash, because OpenOCD server is not running

这个报错常见于新版Vitis或使用第三方调试器(如SEGGER J-Link、OpenOCD)的环境。OpenOCD本身是一个开源的调试工具,Vitis工程里如果检测不到调试服务器,就会报这个错。

解决思路有两个方向。一是用Xilinx官方调试器(Digilent JTAG-HS系列等),此时Vitis会启动自带的hw_server和target manager,不会依赖OpenOCD;二是你确实要用OpenOCD,那么先启动OpenOCD服务:

openocd -f interface/ftdi/jtagkey2.cfg -f target/zynq.cfg

确保OpenOCD进程保持运行,再回到Vitis或命令行执行烧写。这个场景在Linux主机上比较常见,Windows下多数还是用官方驱动居多。

4.4 串口烧写失败的问题排查

串口烧写失败的原因,我在实际支持中见到的可以归为三类。

波特率不匹配:U-Boot默认波特率常见为115200,如果你串口终端软件那边设成9600或其他值,看着就是乱码,收到的数据全是坏的。

镜像格式不对:U-Boot烧写时通常要求镜像带头部信息,比如mkimage生成的镜像。如果直接把BOOT.bin通过串口发给U-Boot,U-Boot会拒收,因为它期待的可能是它认识的image格式。

文件传输协议问题:串口烧写常用ymodem或xmodem协议。有些终端工具对超大文件支持不好,传输到一半就断。尽量把镜像控制在几MB以内,或者换一个成熟的终端工具。

如果条件允许,串口烧写只作为备用方案。优先JTAG,其次是U-Boot+网口(tftp),最后才是串口。

4.5 烧写完成后板子无反应

这个问题的原因就要结合启动流程来查了。

先看电源指示灯和时钟是否正常。ZYNQ板卡上一般有PS_CLK,用示波器量一下,如果时钟没有起振,PS端根本没跑起来。

再确认启动模式引脚设置。ZYNQ的启动模式由MIO[5:2]的电平决定,每一种组合对应一种启动介质,比如0110对应QSPI。如果跳线设置和实际烧录介质不一致,BootROM读了错误的介质,自然启动不了。

还有一种情况是BOOT.bin中分区顺序错了。FSBL必须排在第一个分区,如果bitstream排在了第一个,BootROM会把它当成FSBL执行,结果当然是异常。

最后检查是否烧到了正确的offset。QSPI Flash一般从0地址开始,但有些板卡设计会在flash开头留一段空间给别的用途,比如存放保护配置或BootROM参数。这种情况下,BOOT.bin的offset不一定是0,要按板卡手册来。

4.6 NAND Flash型号兼容性排查

前面多次提到,Xilinx对ZYNQ支持的NAND Flash型号有明确限制。这个限制不是芯片引脚兼容的问题,而是ZYNQ内部NAND控制器驱动所支持的页大小、块大小、ECC算法有限制。所以就算你找了一颗物理上兼容的NAND,但不在Xilinx支持列表里,FSBL初始化时也可能读取不到正确的芯片参数,导致擦写失败。

UG585里有一张NAND Flash支持列表的表格,选型时直接对照。如果不确定手上的颗粒是否支持,最稳妥的方式是用SDK里的QSPI/NAND驱动自带的Flash ID查询功能,把芯片ID读出来比对。这个问题在NAND方案里非常普遍,建议提前规避。

5. 实际项目中烧写方案的落地经验

前面聊了具体操作,最后这部分我想分享一些项目层面经验,尤其是烧写和多分区布局、在线升级搭配相关的思路。

5.1 flash分区规划与启动方案设计

产品开发到后期,一定会遇到flash分区规划的问题。比如QSPI 16MB,不能把BOOT.bin从头占到尾,因为还要留空间给应用程序升级。常规的做法是做一个分区表,举例如下:

分区名称起始地址大小存放内容
Bootloader区0x0000001MBBOOT.bin(FSBL+bitstream+U-Boot)
内核区0x1000004MBimage.ub
文件系统区0x5000008MBrootfs/image
用户数据区0xD00000剩余应用程序、配置、日志

这样划分的好处是,升级时只需要更新其中一个分区,不需要整片擦除。配合在线升级设计,应用程序收到新包后写入用户数据区,然后重启切换启动指针,既降低了升级风险,也缩短了升级时间。

如果你用的是裸机程序,同样可以分两个APP区做A/B升级。写一个简单的引导逻辑,在FSBL或用户引导程序里判断哪个分区的版本号更高,就从哪个分区启动。这个方案我在好几个产品里用过,稳定可靠,而且实现起来并不复杂。

5.2 在线升级降级与回滚策略

基于ZYNQ的bootloader在线升级设计,在实际产品里通常还要考虑回滚。最直接的办法是保留出厂固件分区,升级时先擦除临时分区,写入新固件,校验通过后再把启动标志指向新分区。如果校验失败或运行异常,看门狗会触发回滚,启动到旧版本。

这个思路听着简单,真正落地时有一堆细节。比如flash的写入尽量按扇区对齐,否则擦除操作会波及邻近数据;校验采用CRC32或SHA256,不能只靠长度判断;升级过程中的意外断电,必须有应对措施,通常是在flash头部存一个升级状态标记,引导程序判断标记来决定是否继续升级。

我在实际项目中的经验是:任何flash写操作,都先备份旧镜像、再写新镜像、最后更新标志位,顺序不能搞反。否则一旦断电落在“新镜像只写了一半、标志位已经更新”的状态,系统就起不来了。

5.3 产线批量烧写的效率提升办法

如果你的产品要小批量试产,一个个开Vitis点Program Flash显然不现实。针对产线,我有几个建议。

第一,用program_flash命令行工具封装一个批处理脚本,只传镜像路径和flash型号,操作员双击执行。这样即使不是研发人员也能完成烧写。

第二,JTAG链上可以串联多块板卡,一次烧写多块。前提是每块板卡的JTAG链IDCODE要能区分,脚本里针对不同位置的目标执行烧写即可。

第三,如果是裸板没有JTAG座子或者不想接调试器,可以采用“预烧写”模式,也就是贴片前用编程器把flash芯片烧好,再贴到板上。这样速度快、成本低,缺点是一旦硬件改版,flash里的程序要重新烧。很多量大的产品都是这么干的。

写在最后

ZYNQ烧写程序到flash,看起来只是一个简单的操作,背后涉及启动流程、FSBL机制、flash选型、工具链使用和可靠性设计。我自己在第一次烧写QSPI时,也被“Target DLL has been cancelled”折腾了一整天,最后发现只是跳线帽没接对。后来项目做多了,才慢慢意识到,烧写这件事本身不难,难的是对整个启动链路有清晰的认知。

如果你正在被烧写问题困扰,我的建议是不要急着点按钮,先花半天时间把启动流程读一遍,把BOOT.bin里每个分区的作用搞清楚。接下来再对照这篇文章的排查清单一项项过,大部分问题都能解决。如果还不行,就用最笨的办法:先确保JTAG能连上、FSBL能跑,再一步步增加环节,定位问题会快得多。

最后再分享一个小技巧:烧写QSPI之前,先把整片flash的读保护、写保护状态检查一遍,很多板卡出厂时flash被软件保护了,擦除命令根本不生效,表现就是烧写时一直报错。用烧写工具先执行一次blank check或chip erase,往往能解决一半的“莫名其妙”现象。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询