得,又有人在群里问ZYNQ启动模式的问题了。每次看到新手在SD卡启动和QSPI Flash启动之间来回折腾,最后卡在“为啥我的板子就是不跑”这种问题上,我是真想隔着屏幕把配置文件给他甩过去。借着这个机会,我干脆把ZYNQ双启动模式的配置流程完整梳理一遍,从原理到实操再到排错,一步到位。这篇东西无论你是刚拿到开发板的纯小白,还是已经玩过一阵子但没系统搞过启动配置的工程师,都能直接照着抄作业。
ZYNQ这个芯片比较特殊,它是ARM Cortex-A9处理器和FPGA逻辑放在一颗芯片里的异构SoC。正因为这种架构,它的启动流程就比单纯的单片机或者FPGA都麻烦一些,既要管PS(Processing System)侧的ARM核启动,又要管PL(Programmable Logic)侧的FPGA配置。搞懂它的启动模式,是玩转ZYNQ的第一道关卡,也是后面做系统移植、跑Linux、搞裸机程序的基础。
1. 内容整体设计与思路拆解
1.1 核心需求解析
先搞清楚我们到底要解决什么问题。典型的ZYNQ开发场景里,工程师往往面临两种需求:
一是产品量产阶段,需要把最终固件固化到板载Flash里,上电就自动运行,不需要外部存储介质,这叫QSPI Flash启动。QSPI Flash焊接在PCB上,容量通常在16MB到128MB之间,优点是集成度高、可靠性好、上电启动快,适合固定功能的工业设备和仪器仪表。
二是开发调试阶段,Linux内核和根文件系统可能有好几百MB,QSPI Flash容量根本装不下。这时候就需要用SD卡来承载整个系统镜像,从SD卡启动,这叫SD卡启动。SD卡容量大、读写方便、修改系统只需要替换文件,非常适合频繁编译、烧写、测试的开发节奏。
双启动模式配置,就是把这套东西理解透了,让板子既能从Flash启动做产品验证,也能从SD卡启动做开发调试,两条路都走得通。这里要提醒一句:ZYNQ的启动模式是由硬件管脚决定的,软件只是配合,所以真正的双启动是指“理解两种模式、随时切换”,而不是同一时刻既从SD卡又从Flash启动。
1.2 方案选型与可行性分析
为什么选SD卡加QSPI Flash这套组合,而不是其他存储方案?这是由ZYNQ芯片本身的启动架构决定的。ZYNQ的BootROM固化在芯片内部,上电后第一件事就是读取Boot Mode引脚的电平状态,然后根据引脚配置决定从哪个设备加载FSBL(First Stage Bootloader)。BootROM支持的启动源包括QSPI Flash、SD卡(SD0/SD1)、NAND Flash、JTAG等,这几类里最适合双启动组合的就是QSPI和SD卡。
NAND Flash虽然容量比QSPI大,但接口速度慢,烧写麻烦,而且ZYNQ对NAND型号的支持比较挑剔。JTAG启动只适合调试,不能作为产品启动方式。相比之下,QSPI Flash接口简单、速度快、寿命长,SD卡通用性极强、容量几乎不受限制,两者在硬件资源上互不冲突,引脚也不共用,非常适合做成一个双启动系统。
硬件层面的选型确定后,软件层面的核心任务就清晰了:编译生成FSBL、生成BOOT.bin、配置SD卡分区格式、处理U-Boot环境变量、烧写QSPI Flash。这几步前后有严格的依赖关系,很考验对整个启动链路的理解。我后面会一步步拆开讲,每一步该做什么、为什么这么做、有什么坑,都给你交代清楚。
2. 核心细节解析与实操要点
2.1 Boot Mode引脚配置机制
ZYNQ的启动模式选择不是由软件配置的,而是由芯片上的Boot Mode引脚(MIO[6:2])在复位结束时的电平状态决定的。这些引脚属于MIO(Multiplexed I/O)的第0组,硬件设计时直接通过上下拉电阻接到板上的拨码开关上,或者直接固定电平。
常见的几种启动模式对应的引脚状态:
| 启动模式 | MIO[6] | MIO[5] | MIO[4] | MIO[3] | MIO[2] |
|---|---|---|---|---|---|
| JTAG | 0 | 0 | 0 | 0 | 0 |
| QSPI | 0 | 0 | 1 | 0 | 0 |
| SD卡(SD0) | 0 | 1 | 0 | 0 | 0 |
| SD卡(SD1) | 1 | 1 | 0 | 1 | 0 |
| NAND | 1 | 0 | 0 | 0 | 0 |
0表示低电平,1表示高电平。具体的电平组合以对应芯片型号的数据手册为准,不同系列的ZYNQ(如Z-7010、Z-7020、ZU系列)引脚定义会有差异,但基本原理一致。我强烈建议你拿到开发板之后,先找到板子的原理图,把Boot Mode对应的拨码开关位置确认好,再动手做后面的步骤。很多新手其实配置文件、烧写步骤全对,就是拨码开关拨错了位置导致启动失败。
这里有一个关键细节:Boot Mode引脚状态只在复位释放的时刻被采样一次。所以如果你在系统运行过程中拨动拨码开关,是没有任何效果的,必须按一下复位按键或者重新上电,新的模式才会生效。实测中经常有人拨了开关以后不按复位,对着串口终端干瞪眼,以为板子坏了,其实就差这一步。
2.2 FSBL与BOOT.bin的核心作用
从高层次看,ZYNQ的启动是一个“接力跑”:BootROM(芯片固化)→ FSBL(第一阶段引导加载程序)→ U-Boot(第二阶段引导加载程序)→ 内核/应用。
BootROM是芯片出厂时写死的,我们改不了,它的任务是初始化存储控制器(如SD控制器、QSPI控制器)、从选定的启动介质中读取FSBL、把FSBL加载到片上RAM(OCM)中并跳转执行。FSBL由Xilinx提供的工具生成,它的任务是初始化DDR内存控制器、把BOOT.bin里包含的FPGA bitstream加载到PL侧、然后跳转到U-Boot(或者裸机应用)继续执行。
BOOT.bin是一个容器文件,里面按照特定格式打包了FSBL、FPGA bitstream、以及SSBL(如U-Boot)。它排在最前面的永远是FSBL的镜像,BootROM也是根据这个结构去解析的。理解这个容器结构对排查启动问题非常重要——如果你看到的报错是"Invalid boot image"之类的,说明BOOT.bin的生成或者烧写出了问题,而没有报错直接死掉,往往是FSBL本身没跑起来。
2.3 双启动方案中的分区与布局策略
SD卡启动方式和QSPI Flash启动方式,在文件系统的布局上有本质区别。
QSPI Flash的存储介质是线性地址空间,CPU可以直接映射访问,所以BOOT.bin、内核镜像、设备树、根文件系统都可以按照固定偏移烧写到Flash的不同区域,由U-Boot通过地址去加载。QSPI Flash的容量一般是16MB到128MB,除去BOOT.bin占用的几MB空间,剩余的空间如果塞不下完整的Linux根文件系统(一般需要几十MB到几百MB),就需要用只读的ramdisk或者最小化根文件系统方案。
SD卡的布局则自由得多,因为SD卡有完整的块设备和文件系统支持。标准做法是把SD卡分成两个分区:第一个分区格式化为FAT32,大小几百MB,存放BOOT.bin、boot.scr、image.ub等启动相关的文件;第二个分区格式化为ext4,是整个Linux的根文件系统。U-Boot从FAT32分区读取内核和设备树,挂载ext4分区作为根文件系统。
这套布局方案是Xilinx官方推荐的,也是petalinux工具链默认生成的镜像布局。好处是调试阶段只需要替换SD卡里的文件就能完成系统更新,根文件系统的读写也方便。缺点也比较明显:SD卡的文件系统如果损坏,整个系统就起不来了,所以在工业环境中,SD卡方案往往只用于开发调试,量产还是得回到QSPI Flash方案。
3. 实操过程与核心环节实现
3.1 环境准备与工具链构建
开始动手之前,先把环境搭好。Xilinx的开发工具链是Vivado + Vitis(老版本叫SDK),如果你的目标是跑Linux,那就还需要PetaLinux工具链。我用的是Vivado 2024.1 + PetaLinux 2025.1的组合,下面讲到的操作在2020版本以上的工具链基本都通用。
安装完Vivado之后,确保环境变量已经加载好:
source /opt/Xilinx/Vivado/2024.1/settings64.sh source /opt/Xilinx/Vitis/2024.1/settings64.shPetaLinux如果需要:
source /opt/Xilinx/petalinux/2025.1/settings.sh然后准备一块ZYNQ开发板,一个8GB以上的SD卡,一根Micro USB转串口线,一个用于读取和烧写SD卡的USB读卡器,以及电脑端可以访问开发板串口终端的软件(我用的是Minicom,Windows下用MobaXterm也可以)。
3.2 QSPI Flash启动配置全流程
3.2.1 生成FSBL、bitstream和BOOT.bin
在Vivado中完成硬件工程综合、布局布线并导出硬件描述文件(XSA文件)之后,就可以开始生成启动镜像了。如果只用裸机程序,直接在Vitis中创建FSBL工程和应用程序工程,编译后选择“Create Boot Image”生成BOOT.bin。如果使用PetaLinux,则是在PetaLinux工程里执行:
petalinux-package --boot --fsbl --fpga --u-boot --force这个命令会把PetaLinux自动生成的FSBL、FPGA的bitstream文件、U-Boot一起打包成BOOT.bin。生成的文件位于images/linux/目录下,大小一般在几MB到十几MB之间。
另一个需要确认的文件是boot.scr,这是U-Boot的启动脚本。在PetaLinux中默认已经生成,它的内容是告诉U-Boot在启动时去哪里找内核和设备树:
fatload mmc 0:1 0x2000000 image.ub bootm 0x2000000这段脚本的意思是:从SD卡的第一个分区的FAT文件系统中,把名为image.ub的文件加载到内存地址0x2000000处,然后跳转到该地址执行启动。image.ub是一个复合镜像,里面打包了Linux内核镜像和设备树。
3.2.2 使用Vitis烧写QSPI Flash
烧写QSPI Flash有两种常用方式。第一种是直接在Vitis中烧写,这种方式非常简单,适合新手。先把开发板配置为JTAG启动模式,连接好JTAG下载器,在Vitis中进入“Xilinx → Program Flash”菜单,BRAM Config选择“Fallback”,Image File选择刚才生成的BOOT.bin,Flash Type选择你板子上实际的QSPI Flash型号(常见的有S25FL256S、N25Q256A、IS25LP256等,这个信息在板子的原理图或者元器件清单上能找到),然后在Operation中选择“Program”,点击Program按钮等待烧写完成。
第二种方式是通过U-Boot烧写,这个我在实际项目中也经常用到。其流程是先让板子从SD卡启动进入U-Boot命令行,然后把BOOT.bin放到SD卡的FAT32分区中,在U-Boot里执行:
sf probe fatload mmc 0:1 0x3000000 BOOT.bin sf erase 0x0 0x1000000 sf write 0x3000000 0x0 0x1000000这三条命令的意思是:初始化QSPI Flash控制器,从SD卡加载BOOT.bin到内存0x3000000,然后擦除Flash的整个16MB区域,再把内存中的数据写入Flash。这种方式在量产时可以配合脚本批量烧写,效率比Vitis图形界面高不少。
3.2.3 验证QSPI启动结果
烧写完成后,把开发板的Boot Mode拨码开关拨到QSPI启动模式(对应MIO[6:2]为00100),按一下复位按键。打开串口终端,如果一切正常,你会看到U-Boot的启动日志,然后授入Linux内核启动,最终出现登录提示符。如果没有出现任何串口输出,优先检查拨码开关位置是不是确确实实拨到了QSPI;其次检查串口线连接的是不是UART0对应的那个串口(ZYNQ开发板通常有多个串口引脚,容易接错)。
3.3 SD卡启动配置全流程
3.3.1 制作SD卡启动盘
制作SD卡启动盘的第一步是分区和格式化。在Linux系统下操作最方便,如果用的是Windows,可以用Rufus或者balenaEtcher实现类似功能,但格式以Linux下为准。
把SD卡插入读卡器连接到电脑,先确定设备名称:
lsblk假设设备名是/dev/sdb,用fdisk分区:
sudo fdisk /dev/sdb在fdisk交互界面中,依次输入:d(删除已有分区,反复执行直到全部删除),n(新建分区),p(主分区),回车使用默认的起始扇区,+500M设置第一个分区大小为500MB,t(更改分区类型),c(设置为FAT32 / W95 FAT32 LBA类型)。然后再次输入n创建第二个分区,直接回车使用全部剩余空间,t设置第二个分区类型为83(Linux),最后输入w保存。
分区完成后格式化:
sudo mkfs.vfat -F 32 -n BOOT /dev/sdb1 sudo mkfs.ext4 -L ROOTFS /dev/sdb2把BOOT.bin、boot.scr、image.ub拷贝到第一个FAT32分区:
sudo mount /dev/sdb1 /mnt/boot sudo cp images/linux/BOOT.BIN /mnt/boot/BOOT.bin sudo cp images/linux/boot.scr /mnt/boot/boot.scr sudo cp images/linux/image.ub /mnt/boot/image.ub sudo umount /mnt/boot把根文件系统解压到第二个ext4分区:
sudo mount /dev/sdb2 /mnt/rootfs sudo tar -xzf images/linux/rootfs.tar.gz -C /mnt/rootfs sudo umount /mnt/rootfs3.3.2 避坑指南:SD卡分区常见误区
关于SD卡启动,有不少新手遇到过“显示没有文件”或者“无法挂载分区”这类问题。原因大部分出在分区操作上。
第一个坑是分区类型没有设置正确。BOOT分区必须是FAT32文件系统,而且分区类型标识要设置成c(W95 FAT32 LBA),否则U-Boot的fatload命令无法识别文件系统。用lsblk -f查看分区信息,确认FSTYPE列显示的是vfat或者ext4,不能是空的。
第二个坑是FAT32分区容量太大。FAT32文件系统在大多数Linux发行版中用mkfs.vfat格式化时,有分区大小限制,如果分区大于32GB,格式化会失败或者格式化成FAT16/其他格式。因为第一个分区只有几百MB,这个问题不太容易踩到,但是在做单分区的大容量SD卡时就会暴露。
第三个坑是BOOT.bin文件名的大小写。U-Boot的fatload命令是对文件名大小写敏感的,如果BOOT.bin被拷贝成了boot.bin,U-Boot就会报Unable to read file boot.bin。这是新手最常犯的错误之一,我见过不下五次。
3.3.3 U-Boot环境变量与启动参数配置
SD卡启动上电后,U-Boot会先被FSBL加载执行,然后U-Boot读取SD卡第一个分区中的boot.scr,按照里面的内容去加载内核和设备树。如果你在启动时看到U-Boot报错Invalid boot script或者No boot script found,说明boot.scr缺失或者格式有问题。
boot.scr是由boot.cmd文件通过mkimage工具生成的,如果你需要自定义启动参数(比如指定根文件系统所在分区),可以修改boot.cmd再重新生成。boot.cmd的一个典型内容:
setenv bootargs 'console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootwait' fatload mmc 0:1 0x2000000 image.ub bootm 0x2000000生成boot.scr的命令:
mkimage -A arm -T script -C none -n "Boot Script" -d boot.cmd boot.scr这里的root=/dev/mmcblk0p2表示根文件系统在SD卡的第二个分区(mmcblk0对应SD卡,p2代表第二个分区)。如果根文件系统在QSPI Flash的某个偏移地址,这里就要改成root=/dev/mtdblockX或者用initramfs方案,这也是两种启动模式差异最大的地方之一。
3.3.4 验证SD卡启动结果
把拨码开关拨到SD卡启动模式(SD0对应01000),按复位键。串口终端上应该先出现BootROM的初始化日志,然后进入U-Boot命令行界面,自动执行boot.scr中的指令,加载内核和设备树,启动到Linux登录提示符。看到Starting kernel ...之后,一般等3到8秒就能看到Linux的启动日志了。
如果启动卡在Starting kernel ...之后没有反应,最常见的原因是设备树里PL侧的驱动和实际硬件不匹配,导致内核在初始化FATAL错误,这种情况需要回头检查设备树并重新编译image.ub。
3.4 双启动模式切换与优先级管控
配置好两种启动方式之后,切换模式的操作就是非常机械的事了:拨码开关切到对应模式,按复位键即可。但实际项目中还有几个细节值得注意。
开发阶段建议把默认模式设为SD卡启动,因为开发中要频繁修改内核和文件系统,SD卡模式修改文件最灵活,也不需要反复擦写Flash。当开发完成后、准备交付测试时,再切到QSPI Flash模式,验证固化系统的运行稳定性。
在生产阶段,如果产品需要支持远程升级,经常采用的做法是:量产时烧写QSPI Flash作为主启动介质,同时在SD卡预留一个升级分区。系统正常运行时从Flash启动,检测到SD卡上有升级标志文件后,把新的镜像写入Flash,然后重启。这样既兼顾了Flash启动的可靠性,又实现了现场维护的便利性。这算是双启动模式的一个进阶玩法,等基础配置熟练之后可以尝试。
4. 常见问题与排查技巧实录
4.1 启动模式不生效排查
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 切换拨码开关后启动模式没变 | 没有按复位键 | 切换后务必按复位键或重新上电 |
| 配置到SD启动但串口无输出 | Boot Mode引脚被外设占用 | 检查硬件设计,确认Boot Mode引脚没有复用冲突 |
| 配置到QSPI启动但串口无输出 | Flash型号选择错误 | Vitis中重新选择正确的Flash型号并烧写 |
| 上电后串口只有一堆乱码 | 串口波特率不对 | 确认终端波特率设为115200,与U-Boot配置一致 |
4.2 QSPI Flash烧写失败问题
烧写QSPI Flash的典型报错是a valid fsbl file is required for flash operation。这个报错字面意思是“需要有效的FSBL文件”,但实际上它还有个隐性的触发条件:Vitis在烧写Flash之前要求BRAM Config设置为“Fallback”模式,这样烧写工具才会先把FSBL加载到BRAM中运行,然后通过FSBL去驱动QSPI控制器完成烧写。如果你选择的是“None”模式,Vitis就无法加载FSBL,就会报这个错。
解决办法:在Program Flash对话框中,把BRAM Config从None改成Fallback,选择好FSBL文件,然后再执行烧写。如果还报错,检查FSBL工程是否使用的是和当前硬件一致的XSA文件,FSBL版本不匹配也会导致烧写失败。
4.3 启动失败与日志分析
做ZYNQ启动调试,看串口日志是最基本的功夫。日志从BootROM开始输出,你可以从中判断启动流程走到了哪一步。整理一个启动日志排查的思路:
如果串口完全没有输出:先确认供电、时钟、复位电路正常,再检查Boot Mode引脚电平是否确实处于预期状态。用万用表量一下Boot Mode引脚的电压,验证拨码开关是不是真的把对应引脚拉到了正确电平。
如果串口输出停在FSBL相关报错,比如FSBL status = 0x...后面卡住,一般是FSBL初始化DDR失败。常见原因是硬件上的DDR型号和Vivado中的DDR控制器配置不一致,或者FSBL使用的XSA文件与实际板卡型号不符。
如果U-Boot打印完版本信息就停住,不执行boot.scr,排查boot.scr文件,或者尝试在U-Boot命令行手动输入:
fatload mmc 0:1 0x2000000 image.ub bootm 0x2000000手动方式能跑起来,说明boot.scr有问题,重新生成它。
如果内核开始启动但中途崩溃,报Kernel panic - not syncing: VFS: Unable to mount root fs,这是根文件系统挂载失败。先确认bootargs里的root参数指向了正确的设备节点(/dev/mmcblk0p2),再确认该分区中确实有可用的根文件系统。用串口进入U-Boot命令行,输入mmc list和mmc part查看SD卡分区情况,确认mmcblk0p2存在。
4.4 硬件相关的常见问题
ZYNQ开发板的SD卡和Flash接口在硬件设计上也有一些容易出问题的点。SD卡电路要特别注意上拉电阻和去耦电容,SDIO信号线的上拉电阻一般在10K到47K之间,如果信号完整性不良,高速读写时会随机失败。QSPI Flash的片选、时钟、数据线在PCB走线时要尽量等长,避免高频反射导致通信不稳定。如果遇到Flash烧写一半报错、或者偶发启动失败,硬件上优先检查这两个地方。
对纯软件方向的新手,可能没条件重新打板,但开发板上这些都已经是定好的。遇到硬件相关的问题,确认开发板硬件正常即可,最直接的方法是找同型号的开发板做交叉验证——如果别人的板子能跑你的镜像而你的不行,那基本确定是板子硬件层面的问题了。
5. 实操心得补充
文章写到这儿,我回顾了一下这些年做ZYNQ项目踩过的坑,有几个体会特别想分享。
第一,一定要养成修改硬件配置后重新生成全套镜像的习惯。很多疑难杂症的背后都是“改了XSA文件忘了重新生成BOOT.bin”这种低级问题。XSA文件里包含FPGA bitstream和FSBL的参数,只要硬件设计有变化,必须重新走一遍petalinux-build和petalinux-package的完整流程。
第二,调试阶段尽量保留JTAG启动模式作为兜底。就算你调好了SD卡和QSPI启动,也把JTAG作为一种备用手段。当板子启动完全没反应、怀疑Flash内容损坏时,切到JTAG模式可以重新烧写和调试,是最重要的“救命稻草”。
第三,建议保存一份完整的工程流程图。每个版本的BOOT.bin对应哪个Vivado工程、哪个PetaLinux配置、哪个XSA文件,这些信息不能靠记忆管理。我曾经过了一个月回来看项目,对着两个版本的镜像想不起来哪个是量产版本、哪个是测试版本,最后只能通过烧录对比来验证,白白浪费了大半天时间。这是血泪教训。
如果你是按部就班照着我上面写的步骤来操作,走到这一步应该已经能熟练切换SD卡和QSPI Flash两种启动方式了。接下来可以考虑自己定制U-Boot环境、调整内核设备树、把QSPI Flash剩余空间利用起来放用户数据,这些都是水到渠成的下一步。祝大家都能少踩坑,一次点亮。