ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ZYNQ启动固化深度解析:BOOT.bin结构与QSPI/SD双启动实战

ZYNQ启动固化深度解析:BOOT.bin结构与QSPI/SD双启动实战 1. 为什么ZYNQ固化不是“烧进去就完事”——从一个反复失败的QSPI启动说起去年冬天调试一块ZYNQ-7020核心板时我连续三天卡在同一个问题上BOOT.bin明明用Vivado Hardware Manager写进了QSPI Flash上电后PS端却毫无反应JTAG连上一看FSBL根本没跑起来。示波器抓CLK和MIO信号发现PS端连基本的时钟配置都没完成——这不是软件问题是启动链最底层的“信任锚点”出了裂痕。后来才发现问题出在BOOT.bin里那个被我随手复制粘贴、没验证签名的FSBL镜像它和硬件上实际烧写的QSPI Flash型号Winbond W25Q32JV的Page Size定义不匹配导致FSBL加载时读取地址越界整个启动流程在第127个字节就静默崩溃了。这件事让我彻底意识到ZYNQ的固化不是把几个文件打包扔进Flash那么简单它是一条精密咬合的机械传动链——从Vivado生成的bitstream、Petalinux编译出的FSBL/uboot/image.ub到BOOT.bin的二进制拼接顺序、QSPI Flash的物理扇区映射、SD卡FAT32分区的LBA对齐方式任何一个环节的微小偏差都会让整条链在某个齿轮处崩断。而网上那些“三步搞定ZYNQ固化”的教程往往只告诉你“dd ifBOOT.bin of/dev/mmcblk0”却从不解释为什么必须用bs1M seek1跳过第一个MB也不说明QSPI Flash里前64KB到底存着什么关键数据。这就像教人修发动机却不讲正时皮带张力——能转但随时可能爆缸。你手头那块ZYNQ开发板如果出现SD卡插上后系统识别为“无文件系统”、QSPI启动时串口完全没输出、或者JTAG能连上但FSBL卡在“Initializing PS”阶段大概率不是代码逻辑错了而是固化流程中某个隐性约束被忽略了。本文要拆解的就是这条启动链上所有看不见却致命的细节BOOT.bin如何被组装、QSPI与SD卡各自的启动边界在哪里、为什么Petalinux 2025.1生成的image.ub必须配合特定版本的boot.scr、以及当你的SD卡在ZYNQ上显示“没有文件”时真正该检查的三个物理层参数。这些内容不会出现在Xilinx官方UG585手册的目录页里但它们决定你今晚能不能回家。2. BOOT.bin不是压缩包而是启动指令的“物理排布图”很多人把BOOT.bin当成一个可执行文件其实它更像一张电路板的元器件布局图——每个字节的位置都对应着PS端启动时硬件自动读取的绝对地址。ZYNQ的BootROM在上电后会严格按照固定顺序从启动设备QSPI/SD卡的起始地址开始读取数据前768字节是Header接着是FSBL镜像然后是bitstream最后才是U-Boot和Linux内核。这个顺序不是软件约定而是由ZYNQ PS端硬件逻辑硬编码决定的。一旦错位BootROM读到错误位置的数据就会触发不可恢复的启动失败。2.1 Header结构启动链的“宪法性文件”BOOT.bin的Header部分0x0000–0x02FF包含12个32位字其中最关键的是Image Header Magic NumberOffset 0x000固定值0x584C4E58ASCII XLNXBootROM用它确认这是一个合法的ZYNQ启动镜像。如果这里被意外覆盖比如用普通文本编辑器打开保存整个镜像立即失效。Partition Header OffsetOffset 0x010指向第一个分区头的偏移量。注意这个值不是固定0x300而是动态计算的——它等于Header长度0x300加上所有前置分区如FSBL的长度再按128字节对齐。我曾因手动计算时忘了对齐导致U-Boot分区头被写到非对齐地址QSPI读取时因Flash页边界错乱而丢字节。Image LengthOffset 0x014整个BOOT.bin文件的总长度单位字节。BootROM用它判断是否读取完整。如果生成时漏掉某个分区比如忘记加image.ub这个字段值就会小于实际文件大小BootROM会在读取中途停止。提示不要用十六进制编辑器手动修改Header。Xilinx提供的bootgen工具会自动计算并填充所有字段。手动修改的唯一结果是制造一个无法启动的“砖块”。2.2 分区头Partition Header每个组件的“身份证”每个嵌入BOOT.bin的组件FSBL、bitstream、U-Boot、image.ub都有自己的分区头位于该组件数据之前。分区头结构如下偏移量字段名长度说明0x00Partition Header Magic4字节固定值0x584C4E58同主Header0x04Image Name16字节ASCII字符串如fsbl、bitstream、u-boot0x14Load Address4字节该组件加载到内存的物理地址如FSBL为0x00000000U-Boot为0x001000000x18Execution Address4字节执行入口地址通常等于Load Address0x1CPartition Attributes4字节位域Bit0Checksum Enable, Bit1No Load, Bit2No Exe, Bit3Check CRC32这里的关键陷阱在于Load Address。ZYNQ-7020的PS端DDR控制器初始化前只有OCMOn-Chip Memory256KB可用。FSBL必须加载到OCM0x00000000执行而U-Boot需要加载到DDR如0x00100000——但DDR初始化必须由FSBL完成。因此BOOT.bin里U-Boot分区的Load Address不能设为0x00100000否则BootROM会试图把U-Boot直接加载到未初始化的DDR结果就是总线错误。正确做法是U-Boot分区的Load Address设为OCM中的安全地址如0x00020000FSBL在初始化DDR后再将U-Boot从OCM拷贝到DDR并跳转执行。这个逻辑在FSBL源码的fsbl_hooks.c里实现但BOOT.bin生成时必须确保分区头地址与FSBL的搬运逻辑严格匹配。2.3 Petalinux 2025.1的BOOT.BIN生成链从source到binary的七道工序Petalinux 2025.1改变了传统流程引入了petalinux-package的自动化封装。其内部执行链如下FSBL生成petalinux-build -c bootloader调用xsdk编译FSBL输出images/linux/zynq_fsbl.elf。注意此FSBL已预置了QSPI Flash型号通过xparameters.h中的XPAR_QSPI_0_DEVICE_ID和时钟配置若硬件Flash型号变更如从Micron MT25QL01G换成Winbond W25Q32JV必须修改ps7_qspi_0IP核的Device ID参数并重新生成FSBL。bitstream生成petalinux-build -c device-tree后petalinux-package --boot会自动调用vivado -mode batch -source运行TCL脚本生成system.bit。关键点TCL脚本中set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]必须启用否则bitstream体积过大超出QSPI Flash的单个扇区容量64KB导致烧写失败。U-Boot与image.ub准备petalinux-build编译U-Boot后petalinux-package会将images/linux/u-boot.elf和images/linux/image.ub按顺序加入BOOT.bin。这里有个隐藏开关project-spec/configs/config中CONFIG_BOOT_IMAGE_TYPEBOOT启用标准BOOT.bin模式若设为BOOTFS则生成含FAT32分区的SD卡镜像此时BOOT.bin结构完全不同。boot.scr生成petalinux-package调用mkimage工具将project-spec/meta-user/recipes-bsp/bootscripts/files/boot.scr或默认模板编译为二进制boot.scr。此文件控制U-Boot启动后如何加载image.ub。2025.1版本要求boot.scr必须使用mkimage -A arm -T script -C none -n Zynq Boot Script -d boot.cmd boot.scr生成旧版mkimage -C gzip会导致U-Boot解析失败。最终封装petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga system.bit --u-boot --kernel命令触发bootgen。它读取project-spec/meta-user/recipes-bsp/boot-bin/files/system_top.bif文件按BIF中声明的顺序拼接所有组件。BIF文件示例the_ROM_image: { [bootloader]zynq_fsbl.elf [partitionbootimage]system.bit [partitionu-boot]u-boot.elf [partitionscript]boot.scr [partitionkernel]image.ub }注意[partitionxxx]标签名必须与U-Boot环境变量bootcmd中引用的分区名一致如fatload mmc 0:1 0x00100000 image.ub中的0:1指第一个分区image.ub需在该分区中。校验与对齐bootgen自动计算每个分区的CRC32并填入分区头同时确保所有分区起始地址按128字节对齐。若手动添加的文件尺寸未对齐bootgen会填充0xFF字节但这会改变后续分区的偏移量必须重新计算Header。输出验证生成的BOOT.bin需用xxd -l 1024 BOOT.bin | head -n 20检查前20行确认Header Magic584c4e58存在且各分区名如fsbl、bitstream以ASCII形式可见。这是最快速的合法性验证。3. QSPI与SD卡两种启动介质的物理层战争ZYNQ的启动模式由MIO[7:5]引脚电平决定但QSPI和SD卡的启动能力差异远不止于此。它们本质是两种不同的存储协议QSPI是高速串行Flash接口SD卡是基于CMD/DAT线的块设备协议。这种底层差异导致它们在启动流程中承担完全不同的角色也带来截然不同的故障模式。3.1 QSPI启动追求极致可靠性的“单片机模式”QSPI Flash如Winbond W25Q32JV的典型容量为4MB擦写寿命10万次但读取速度高达104MB/sQuad SPI模式。ZYNQ将其视为“扩展的ROM”BootROM直接通过QSPI控制器读取BOOT.bin无需额外驱动。这种模式的优势是启动极快500ms、抗干扰强差分信号、掉电不丢失。但代价是所有启动代码必须严格适配QSPI Flash的物理特性。关键约束一扇区擦除粒度决定BOOT.bin最大尺寸W25Q32JV的擦除单元是4KB扇区Sector和64KB块Block。BOOT.bin必须完全落在连续的扇区内因为烧写前必须先擦除目标扇区。若BOOT.bin大小为3.2MB3,355,443字节而QSPI Flash起始地址0x00000000所在扇区为0x00000000–0x00000FFF4KB显然不可能。实际可行方案是将BOOT.bin起始地址对齐到64KB边界0x00000000、0x00010000等并确保其长度≤64KB。超过64KB时必须跨多个块烧写此时vivado hw_program工具会自动分块处理但需确保每个块的擦除操作独立完成——若中间断电未完成擦除的块将处于不确定状态导致启动失败。关键约束二QSPI Flash型号必须与FSBL硬编码匹配FSBL在初始化QSPI控制器时会根据xparameters.h中XPAR_QSPI_0_DEVICE_ID调用对应的Flash驱动。不同厂商Flash的指令集有细微差异Winbond使用0x06指令解除写保护而Micron使用0x04读取ID的指令码也不同。若FSBL编译时指定Winbond但硬件焊接的是Micron FlashFSBL在QspiPs_PollStatus函数中会永远等待一个不存在的状态位串口输出停在“QSPI Initialization...”不动。解决方案不是改FSBL源码而是在Vivado Block Design中双击ps7_qspi_0IP核在Configuration页签选择正确的Flash Device型号然后重新生成HDL和SDK工程。关键约束三QSPI引脚的PCB走线质量直接影响启动成功率QSPI信号线IO0-IO3, SCLK, CS必须满足严格的阻抗控制50Ω±10%和等长要求长度差5mm。我曾遇到一块量产板QSPI启动失败率30%用矢量网络分析仪测量发现IO2走线比IO0长12mm导致Quad模式下采样相位偏移BootROM读取数据时出现随机比特错误。解决方法是在PCB Layout阶段对QSPI走线启用“Length Tuning”功能手动添加蛇形线补偿长度差同时在ps7_qspi_0IP核的Advanced Configuration中启用Enable Read Data Capture让ZYNQ硬件自动调整采样相位。3.2 SD卡启动灵活但脆弱的“通用存储模式”SD卡启动依赖U-Boot的MMC驱动BootROM先加载FSBLFSBL初始化PS端后加载U-BootU-Boot再通过fatload命令从SD卡FAT32分区读取image.ub。这种模式优势是容量大支持64GB SDXC、更新方便拔卡重刷但引入了多层软件栈故障点成倍增加。故障根源一“SD卡显示没有文件”的真实原因当ZYNQ串口输出** Unable to use mmc 0:1 for loading the file image.ub **时90%的情况并非SD卡真的没文件而是U-Boot无法识别FAT32分区。根本原因在于ZYNQ的SD卡控制器SDIO对FAT32的BPBBIOS Parameter Block结构有特殊要求。标准Windows格式化的FAT32其BPB中BytesPerSector字段为512SectorsPerCluster为8但ZYNQ U-Boot的fat_read_rootdir函数要求SectorsPerCluster必须为1即每簇1扇区。若用mkfs.fat -F32 /dev/mmcblk0p1格式化会生成符合要求的分区但Windows右键“格式化”默认使用8扇区/簇导致U-Boot解析FAT表时索引溢出报错“Invalid FAT entry”。故障根源二SD卡电路设计中的致命电容ZYNQ的SDIO接口推荐在CMD和DAT0-DAT3线上各串联一个10Ω电阻并在每根线对地加一个100nF电容X7R材质。这个电容的作用是滤除高频噪声但若选用Y5V材质电容其容值随温度变化剧烈-30%~80%在低温环境下容值衰减导致信号边沿变缓U-Boot的mmc_send_cmd超时失败。实测中一块在25℃正常工作的板子在-10℃环境下SD卡识别失败更换为X7R电容后恢复正常。故障根源三SD卡供电电压切换时序ZYNQ PS端支持1.8V和3.3V两种SD卡供电模式。启动时FSBL会先以3.3V初始化SDIO成功后切换到1.8V以降低功耗。但某些廉价SD卡尤其白牌卡不支持电压切换切换瞬间卡死。解决方案是在ps7_sdio_0IP核的Configuration中禁用Enable UHS-I Support强制使用3.3V模式或在FSBL的platform_config.c中注释掉Xil_Out32(0xF8000080, 0x1)SDIO电压切换寄存器写入。4. 双启动模式的实战配置让QSPI和SD卡成为互备的保险丝真正的工业级ZYNQ系统绝不会只依赖单一启动介质。双启动模式的核心思想是QSPI作为主启动通道SD卡作为应急恢复通道。当QSPI中BOOT.bin损坏时系统能自动降级到SD卡启动并在SD卡中运行一个轻量级工具将新的BOOT.bin重新烧写回QSPI。这需要硬件、BootROM、FSBL、U-Boot四层协同。4.1 硬件层MIO引脚的“启动策略开关”ZYNQ的启动模式由MIO[7:5]三个引脚电平组合决定。标准配置如下MIO[7]0, MIO[6]0, MIO[5]0→ JTAG启动调试用MIO[7]0, MIO[6]0, MIO[5]1→ QSPI启动默认主模式MIO[7]0, MIO[6]1, MIO[5]0→ SD卡启动备用模式双启动的关键是让系统能动态切换模式。我们采用“硬件看门狗MIO复位”的方案在PCB上增加一个GPIO控制的MOSFET用于短接MIO[5]到GND。正常启动时MIO[5]悬空上拉电阻系统从QSPI启动若QSPI启动失败如FSBL超时FSBL检测到Xil_In32(0xF8000000) 0x1PS端复位状态寄存器为0触发GPIO翻转MOSFET导通将MIO[5]拉低然后执行Xil_Out32(0xF8000004, 0x1)软复位PS端。复位后MIO[5]被硬件拉低系统从SD卡启动。4.2 FSBL层启动失败的黄金3秒检测FSBL的启动超时检测在xfsbl_main.c的Fsbl_MicroSecondDelay(3000000)处实现。但原生FSBL只检测FSBL自身加载失败我们需要扩展为检测整个启动链。在FsblHookBeforeHandoff函数中插入以下代码// 检测QSPI启动失败读取QSPI Flash前4字节若非XLNX则判定失败 u32 qspi_data; XQspiPs_Read(QspiInstance, 0x00000000, (u8*)qspi_data, 4); if (qspi_data ! 0x584C4E58) { // 触发MIO[5]切换 XGpioPs_WritePin(Gpio, MIO_5_PIN, 0); // 拉低MIO[5] Xil_udelay(1000); // 等待1ms // 软复位PS Xil_Out32(0xF8000004, 0x1); }此处MIO_5_PIN需在xparameters.h中定义为对应GPIO编号。注意此操作必须在FSBL完成DDR初始化后进行否则GPIO操作无效。4.3 U-Boot层SD卡上的QSPI烧写工具当系统从SD卡启动后U-Boot需提供qspi_write命令将SD卡中的boot_qspi.bin烧写到QSPI Flash。这需要在U-Boot配置中启用CONFIG_CMD_SF和CONFIG_SPI_FLASH_XILINX。关键步骤如下识别QSPI Flashsf probe 0:00:0表示QSPI控制器0CS0擦除目标区域sf erase 0x00000000 0x400000擦除前4MB写入新BOOT.binfatload mmc 0:1 0x00100000 boot_qspi.bin; sf write 0x00100000 0x00000000 ${filesize}注意sf write命令的第三个参数是QSPI Flash的目标地址必须与BOOT.bin中Header的Image Length字段一致。若写入地址偏移BootROM读取时会解析错误。4.4 SD卡分区的终极配置FAT32 EXT4双分区方案为兼顾U-Boot的FAT32兼容性和Linux的EXT4高效性我们采用双分区SD卡分区1FAT32100MB存放boot.scr、image.ub、boot_qspi.bin供U-Boot启动和烧写使用。分区2EXT4剩余空间挂载为/mnt/data存放应用程序、日志、配置文件。制作步骤fdisk /dev/mmcblk0创建两个分区n→p→1→Enter→100Mn→p→2→Enter→Entermkfs.fat -F32 /dev/mmcblk0p1严格使用-F32避免簇大小问题mkfs.ext4 /dev/mmcblk0p2将boot.scr、image.ub复制到FAT32分区boot_qspi.bin也放在此分区命名明确如recovery_boot.bin在project-spec/meta-user/recipes-core/images/petalinux-image-full.bbappend中添加IMAGE_INSTALL_append kernel-modules EXTRA_IMAGEDEPENDS virtual/kernel这样当系统从SD卡启动后Linux内核会自动挂载EXT4分区而U-Boot始终只访问FAT32分区互不干扰。5. 排查清单当启动失败时按此顺序检查可节省80%时间面对ZYNQ启动失败不要急于重刷BOOT.bin。按以下物理层→协议层→软件层的顺序排查能快速定位根因5.1 物理层检查5分钟检查项方法正常现象异常处理电源纹波示波器探头接PS端VCCPAUX1.8V和VCCO_03.3V引脚纹波50mVpp更换LDO电容增加10μF钽电容QSPI CLK信号示波器抓ps7_qspi_0的sclk引脚频率25MHz占空比50%检查Vivado中QSPI IP核的SCLK Frequency是否设为25MHzSD卡检测信号万用表测ps7_sdio_0的cd_n引脚对地电压插卡时为0V拔卡时为3.3V若始终高电平检查SD卡座的CD_N弹片是否接触不良5.2 协议层检查10分钟检查项方法正常现象异常处理QSPI Flash ID读取JTAG连接Vivado Hardware Manager中Program Device→右键QSPI Flash→Read ID返回0xEF4016Winbond W25Q32JV若返回0xFFFFFFQSPI线路开路若返回0x000000CS线未拉低SD卡CID读取U-Boot命令行输入mmcinfo显示Manufacturer ID: 0x1b,OEM: SD若显示no card present检查SDIO的cd_n和wp引脚电平BOOT.bin Header验证xxd -l 64 BOOT.bin前4字节为584c 4e58偏移0x10处为0000 0300分区头偏移若Header错误重新运行petalinux-package确认BIF文件路径正确5.3 软件层检查15分钟检查项方法正常现象异常处理FSBL串口输出连接UART0115200,8,N,1上电观察输出Xilinx Zynq First Stage Boot Loader后接Successfully initialized DDR若停在QSPI Initialization...检查xparameters.h中QSPI Device IDU-Boot启动日志FSBL完成后串口应切换到U-Boot提示符显示U-Boot 2025.01 (Jan 01 2025 - 00:00:00 0000)若无输出检查FSBL中Xil_Out32(0xE0001000, 0x1)UART0使能是否执行image.ub加载验证U-Boot命令行输入fatls mmc 0:1列出boot.scr、image.ub等文件若报错** Unable to use mmc 0:1 **用fdisk -l /dev/mmcblk0确认分区表mkfs.fat -F32 /dev/mmcblk0p1重格式化5.4 终极验证用JTAG绕过启动链直接加载当所有检查都通过仍无法启动时用JTAG强制加载验证是终极手段Vivado Hardware Manager连接板子Program Device→ 选择zynq_fsbl.elf→Program在Vitis中新建Application Project选择Hello World模板编译生成hello.elfRun As→Launch on Hardware (System Debugger)选择hello.elf若hello.elf能正常运行证明PS端硬件完好问题100%在BOOT.bin或启动配置若hello.elf也失败则检查JTAG接线或PS端供电。我在现场调试时曾用此方法在一小时内定位到一块PCB的VCCPAUX电源平面存在0.5Ω阻抗导致DDR初始化失败——这是任何软件日志都无法揭示的硬件缺陷。记住ZYNQ启动失败70%是硬件问题20%是配置问题只有10%是代码逻辑问题。把示波器和万用表当作你的第一调试工具比盯着串口日志有效得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进