ZCU102裸机工程:Vitis平台搭建与BOOT.BIN脱机启动

发布时间:2026/11/3 23:23:11
ZCU102裸机工程:Vitis平台搭建与BOOT.BIN脱机启动 1. 先把“裸机”这件事的边界划清楚ZCU102 上的代码到底跑在哪颗核上第一次在 ZCU102 上做裸机工程的人十有八九会卡在一个很朴素的问题上我按下“运行”代码到底跑到哪里去了因为这块板子和常见的单片机开发板完全是两个世界。一颗 Zynq UltraScale 器件里PS 侧有四颗 Cortex-A53、两颗 Cortex-R5F还有一颗负责电源与复位管理的 PMU外面才是 PL 逻辑和 DDR4。你在 Vitis 里点一个 Debug背后牵动的是复位序列、时钟配置、DDR 初始化、BSP 生成、链接脚本落地址这一整串动作。Bare-Metal裸机工程的本质是在没有操作系统调度的情况下把这段启动链条上的每一环都交给你自己掌控。这篇内容面向的是这样一类读者手上有 ZCU102 开发板装了 Vitis想跑通一个不依赖 Linux 的裸机工程可能是做板级自检可能是给 PL 侧的加速器写 PS 端的控制程序也可能是为了验证某个外设的时序。我会把从“空 workspace”到“上电脱机启动”的完整路径拆开讲包括每一步为什么这么做、哪些选项改错了会出什么样的怪现象。工具版本以 Vitis 2021.1 之后引入 Platform Component 概念的那套流程为主同时在关键差异处标注新老 Unified IDE 流程的区别因为这两套界面在 2023.2 之后并存很容易让人对着教程找不到按钮。1.1 上电之后到 main 之前硬件替你做了哪些事很多人把裸机工程理解成“没有 OS 的 C 程序”这个理解只对了一半。在 ZCU102 上你的main()能被执行前提是有好几个阶段的代码先跑完了。芯片上电后PMU ROM 先启动加载 PMU 固件紧接着 BootROM 根据启动模式引脚SW6 拨码开关决定从哪里读第一段代码。如果你是 JTAG 调试模式Vitis 的调试器会通过psu_init那一套寄存器序列把 PLL、DDR 控制器、MIO 引脚复用全部配好这个序列来自你导出 XSA 时 Vivado 生成的初始化配置。这就解释了一个非常典型的现象同一个 ELF在 JTAG 下跑得好好的烧成 BOOT.BIN 从 SD 卡启动就是黑屏。原因往往是 JTAG 模式下是调试器帮你做了 DDR 和时钟初始化而脱机启动时这些事必须由 FSBLFirst Stage Boot Loader来做。FSBL 是 Xilinx 提供的二级启动程序它内部会调用psu_init然后按 bif 文件的描述加载后续镜像。所以严格意义上说一个“脱机能跑”的裸机工程 FSBL 你的应用 ELF可选再加 bitstream而“JTAG 能跑”的裸机工程只需要你的应用 ELF。R5 的情况又不一样。R5 有自己独立的 TCMATCM/BTCM代码可以直接链接到 TCM 里跑完全不碰 DDR也不需要 DDR 初始化。这是排查硬件问题时的黄金入口当你怀疑 DDR 配置有问题时先写一个跑在 R5 TCM 上的最小程序点灯或者打串口能跑通就说明时钟和引脚基本没问题问题集中在 DDR 这一段。我在实际项目里养成的习惯是新板子到手第一件事就是建一个 R5 的 TCM 裸机工程作为“硬件是否活着”的探针。1.2 四种可选运行环境以及为什么裸机在联调阶段依然不可替代在 ZCU102 上跑代码至少有四种环境可选A53 上的 standalone裸机、A53 上的 Linux、R5 上的 standalone、以及 R5 上的 FreeRTOS。选哪个不是偏好问题是需求倒推的问题。运行环境启动依赖典型用途主要代价A53 standaloneFSBL 或 JTAG 初始化 DDRPL 加速器控制、板级自检、算法验证无虚拟内存、无文件系统、调试手段原始A53 LinuxFSBL ATF U-Boot 镜像应用层业务、网络、存储启动慢实时性无法保证R5 standalone可只依赖 TCM甚至不需要 DDR硬实时控制、安全监控、协处理主频低默认 ~500MHz资源受限R5 FreeRTOS同 R5 standalone多任务实时控制多一层调度开销与调试复杂度裸机的不可替代性主要体现在三个场景。第一是时序敏感A53 上跑 Linux 时一个用户态线程被调走几十微秒是家常便饭如果你的 PL 侧逻辑需要一个稳定的周期触发信号用裸机写个定时器中断比在 Linux 里调nanosleep靠谱得多。第二是启动早期有些板级配置必须在 Linux 起来之前完成比如某些外设的上电时序、时钟切换。第三是定位问题当 Linux 起不来你怀疑是 DDR 参数不对或者 PL 配置失败时一个裸机的内存读写测试程序能几分钟内告诉你答案。反过来说如果你的应用需要文件系统、网络协议栈、多进程隔离那就别硬扛裸机。我见过有人为了省事在裸机上自己写了一个简易的 FAT 解析器最后维护成本远超直接上 Linux。判断标准很简单如果需求里出现“多个不相关的任务要并发”裸机就该让位了。2. 平台工程是整个裸机工程的地基XSA 的来历与 Vitis 版本的匹配Vitis 从 2019.2 开始把“平台Platform”这个概念提到了核心位置这个设计带来的最大变化是应用工程不再直接关联硬件而是关联一个平台工程平台工程再去关联 XSA。这个多出来的一层是新手最容易糊弄过去、后面又反复遭罪的地方。因为 BSP 里的xparameters.h、psu_init.c、链接脚本的内存区域全部是从平台工程里推导出来的。平台工程一旦和实际硬件对不上后面所有代码都在错误的前提上运行。2.1 XSA 不是凭空来的从 Vivado 导出到 Vitis 导入的完整性检查XSAXilinx Support Archive是一个 zip 包里面装着硬件描述、PS 配置、PL 的 IP 信息以及可选的 bitstream。它的来源必须是 Vivado。哪怕你完全不用 PL只做纯 PS 的裸机开发也需要先在 Vivado 里建一个以 ZCU102 为目标的工程跑到“Generate Output Products”和“Create HDL Wrapper”之后导出 XSA。这里有个细节很多人不知道导出 XSA 时可以勾选“Include bitstream”。如果勾了XSA 会大很多但后续在 Vitis 里可以直接把它打包进 BOOT.BIN如果不勾PL 部分就是个空壳你后面要用 PL 就得回头重新导出。我的建议是只要工程里存在任何 PL 逻辑就一律带上 bitstream代价只是几十 MB 的磁盘空间。导出的 XSA 拿到 Vitis 里导入后第一件事是检查三样东西是否和你的预期一致PS 的 UART 用的是哪一路、DDR 的容量与位宽、PL 的时钟频率。检查方法很直接在平台工程里打开 BSP 的xparameters.h搜XPAR_XUARTPS_0_BASEADDR看基地址是不是你打算用的那一路搜XPAR_PSU_DDR_0_S_AXI_BASEADDR和对应的HIGHADDR看 DDR 的地址范围对不对。ZCU102 板载 4GB DDR4正常应该看到从0x00000000到0x7FFFFFFF或者分成几段。如果这里看到的范围明显偏小八成是 Vivado 里 PS 配置页的 DDR 型号选错了。还有一个隐蔽的坑XSA 重新导出之后Vitis 里的平台工程不会自动更新。你必须在平台工程上右键选“Update Hardware Specification”再重新 Build 平台。少做这一步你的 BSP 还是旧的症状是明明在 Vivado 里加了一个 AXI GPIOxparameters.h里却死活搜不到它的宏定义。这个坑我踩过至少三次每次都要浪费半小时才想起来。2.2 版本、器件与 workspace 路径三个最容易在第一步就翻车的地方Vitis 对版本匹配的要求相当严格。Vivado 导出的 XSA 和 Vitis 的版本必须一致2021.2 导出的 XSA 拿 2022.1 的 Vitis 打开有时候能过有时候会报一个含糊不清的错误然后 BSP 生成失败。稳妥做法是 Vivado 和 Vitis 装同一个版本号工程目录按版本分开存。器件支持是第二个坑。如果你装的是 WebPACK 版本的 Vitis器件库可能不包含 ZCU102 上的 XCZU9EG。表现是新建平台工程时选不到对应的 part或者在 Build 阶段报“part not supported”。这个不是配置问题是安装包本身的问题只能换完整版安装。第三个坑是 workspace 路径。Vitis 底层大量调用 make 和 tcl路径里带空格、中文、或者过长会出现各种莫名其妙的构建失败报错信息还经常指向一个不存在的文件。我现在的固定做法是workspace 建在磁盘根目录下一个纯英文短路径里比如D:\ws_zcu102工程名也不要用中文。这条经验听起来很土但它是排查“为什么别人能编译我不能”这类问题时第一个应该排除的因素。3. 从空 workspace 到一个会打印字符的裸机工程经典流程逐屏拆解前面铺垫了这么多现在真正动手。这一节按 Vitis 2021.x 到 2022.x 的经典流程走界面叫法可能略有出入但逻辑是一致的先有平台组件再有应用组件。目标很明确——串口终端里能看到一行字。3.1 建平台组件与建应用组件两个动作各自的产出物打开 Vitis选一个 workspace然后File → New → Platform Project。给平台起个名字比如pf_zcu102下一步选择“Create from hardware specification (XSA)”指向你从 Vivado 导出的那个.xsa文件。操作系统选standalone处理器先选psu_cortexa53_0。这一套选完点 FinishVitis 会在后台展开 XSA、生成平台工程、并自动编译出一个 BSP。这个平台工程的产出物有三类值得你逐个点开看一眼一是ps7_cortexa53_0目录下的 BSP 源码其实就是一堆.c和.h包括psu_init.c二是export目录下的平台导出文件三是平台级的lscript.ld模板。很多人在后面遇到链接错误根因就在这个模板上。平台编译通过之后File → New → Application Project。这里要选刚才建的平台处理器选psu_cortexa53_0OS 选standalone语言选 C 或者 C然后最关键的一步模板选择。列表里有 Hello World、Empty Application、Peripheral Tests、Memory Tests 等等。第一次跑通建议选Hello World因为它已经帮你把串口初始化和printf重定向都写好了等你熟悉流程之后日常开发应该选Empty Application自己控制初始化顺序。Hello World 模板生成的helloworld.c里有个细节值得注意它调用的是print()而不是标准printf()。print()是 Xilinx 提供的一个极简输出函数底层直接调outbyte()不依赖 newlib 的缓冲和堆。这意味着它在资源紧张时更可靠但也意味着它不支持格式化浮点。如果你需要%f就得换标准printf代价是要把堆栈调大。3.2 Domain 配置里的六个关键选项每一项改错会怎样应用工程建好之后双击platform下面的 Domain名字通常叫standalone_psu_cortexa53_0会打开一个配置页面。这个页面是裸机工程的“总开关”我把它拆成六项来看Processor决定代码跑在哪颗核。A53 有 0~3 四颗R5 有 0~1 两颗。A53 的 0 号核是启动核一般首选它。R5 的 0 号核可以配成 lockstep 或者 split 模式这个在 Vivado 的 PS 配置里定Vitis 里改不了。OSstandalone就是裸机freertos10_xilinx是 RTOS。两者切换会重新生成 BSP代码里的main写法完全不同别随手切。stdout / stdin这是串口能不能出字的关键。stdout 必须设成你实际接线的那一路 UARTZCU102 上通常是psu_uart_0。设错了不会报错程序正常跑就是一个字符都看不到。stdin 一般设同一个 UART这样scanf之类才有输入。Baud Rate默认 115200。这个值必须和你的串口终端一致同时也必须和 PS 配置里那一路 UART 的时钟分频结果能对上否则实际波特率会有偏差表现为终端里出现乱码而不是完全无输出。Heap / Stack Size默认 heap 是 0x20008KBstack 是 0x2000。一旦你用标准printf、malloc、或者递归稍深的函数这两个值就不够用了。症状非常典型程序跑到某个函数就静默死掉或者printf输出一半就卡住。Extra Compiler Flags需要开浮点、调整优化等级的时候填这里。A53 支持硬件浮点编译时加上-mfpucrypto-neon-fp-armv8 -mfloat-abihard之类的参数能显著提升浮点性能但要注意 ABI 一致性别和预编译库混用。我给一组自己常用的起步参数供参考配置项默认值建议起步值调整理由stdoutpsu_uart_0psu_uart_0保持默认但要和物理接线核对stdinpsu_uart_0psu_uart_0需要交互输入时才用heap0x20000x10000标准 printf 和动态分配都需要stack0x20000x10000深层调用和大型局部数组Optimization-O2-O2调试阶段可用 -O0 便于单步提示改完 Domain 配置之后必须重新 Build 平台和应用只 Build 应用是不生效的。Vitis 有时候不会主动提示你重建平台这是长期存在的一个小毛病。3.3 第一次跑起来JTAG 下载、复位顺序与串口终端的设置连接上 JTAG 和 USB 串口线之后先在电脑上确认串口号。ZCU102 板载的 USB 转串口桥接器一次性暴露多个虚拟串口按板卡手册的说明PS 的 UART0 对应其中一个固定通道。开终端软件波特率 115200、8 数据位、无校验、1 停止位、无流控。然后在 Vitis 里右键应用工程Debug As → Launch Hardware。这一步 Vitis 会自动做几件事连上 JTAG、执行系统复位、下载psu_init寄存器序列、把 ELF 加载到链接脚本指定的地址、把 PC 指到入口点。第一次运行时它会问你要不要切到 Debug 视图选是。如果你不想用 IDE用命令行也行XSCT 脚本如下connect targets -set -filter {name ~ PSU} rst -system after 3000 targets -set -filter {name ~ APU*} loadhw -hw D:/ws_zcu102/pf_zcu102/export/pf_zcu102/hw/pf_zcu102.xsa -regs targets -set -filter {name ~ psu_cortexa53_0} rst -processor dow D:/ws_zcu102/app_hello/Debug/app_hello.elf conrst -system这个动作非常关键。A53 是有缓存和 MMU 的如果你之前在这颗核上跑过 Linux 或者别的程序缓存里可能残留脏数据直接dow下去会出现“代码明明改了但行为没变”的灵异现象。养成习惯每次重新下载前先rst -system而不是只rst -processor。跑通之后终端里应该出现类似Hello World的输出以及一行Successfully ran Hello World application。看到这两行说明工具链、平台、BSP、串口这条链路全通了。接下来才是真正写代码的阶段。4. 串口没输出、程序跑飞、printf 卡死三条高频故障的排查链路裸机开发最难受的地方在于没有操作系统给你兜底。程序跑飞了就是跑飞了没有任何日志。所以排查必须靠结构化的方法从上到下逐层验证而不是瞎改代码。下面这三条链路是我这些年反复用到的。4.1 串口一个字节都没有从终端到 BSP 的自上而下验证故障现象Debug 启动成功控制台没有任何报错但串口终端一片空白。第一步查终端侧。串口有没有被别的程序占用换一个终端软件试试波特率是不是设成了 9600这些基础项别嫌烦我遇到过同事排查两小时最后发现是另一个串口助手进程没关。第二步查物理连接。ZCU102 上有多个 USB 口JTAG 和 UART 是分开的插错口不会有任何提示。用一个已知能工作的程序比如官方例程验证串口通道本身。第三步查 BSP 配置。打开xparameters.h确认XPAR_XUARTPS_0_DEVICE_ID存在确认stdout绑定的设备号和你接的物理口一致。再打开 BSP 生成的xparameters_ps.h看 UART 的基地址是不是0xFF000000UART0或者0xFF010000UART1。这两路在板子上是不同引脚接错了就是没输出。第四步查时序配置。如果基地址对、终端对但出来的是乱码问题在波特率分频。ZCU102 的 UART 时钟默认来自 PS 的 IO PLL如果 Vivado 里改了参考时钟或者分频实际波特率会偏离。这时候用示波器或者逻辑分析仪抓一下 TX 线的波形量一下单个 bit 的宽度能立刻算出实际波特率。第五步如果以上全对还没输出就要怀疑程序根本没跑到打印那一行。在main的第一行加一个 GPIO 翻转用 LED 或者示波器看比串口更早一步告诉你代码有没有执行。4.2 程序在 main 之前就挂了启动文件、链接脚本与内存映射的核对故障现象Debug 提示下载成功但一 resume 就停在某个异常向量里或者 PC 直接跑飞。这种问题十有八九出在链接脚本和内存映射上。打开应用工程里的lscript.ld看三个区域.text放在哪、.data放在哪、.stack和.heap放在哪。A53 的裸机默认把代码放到 DDR 的起始位置也就是0x00000000附近。如果你的 XSA 里 DDR 配置不对或者 DDR 根本没初始化比如你用 JTAG 下载但没跑psu_init那么代码被“写进”了一个不存在的内存PC 一跳过去就异常。核对方法在 Vitis 的 Debug 视图里看 Memory 窗口地址填0x0看能不能读出你 ELF 里的机器码。读出来全是0x00或者读不到就是内存没起来。这时候改用 R5 的 TCM 链接脚本试一下TCM 不需要任何初始化就能访问如果 TCM 上能跑问题就锁定在 DDR。还有一种情况是栈溢出。A53 的默认栈只有 8KB如果你的代码里有个char buf[16384]的局部数组一进函数就把栈踩穿了表现是函数返回时跳到垃圾地址。这类问题在-O0编译下容易出现因为-O2可能把数组优化掉。排查办法是在链接脚本里把 stack 调大然后重新验证同时养成习惯大数组一律用static修饰或者放全局区。异常向量也是一个信号。A53 的裸机程序开头有一段boot.S里面设置了向量表。如果你的程序进了DataAbort或者PrefetchAbort说明访问了非法地址。Vitis 调试器里能读到ESR_EL3之类的寄存器具体值对应的异常类型查架构手册就有。我的经验是先看 PC 停在哪再往回推是哪个指针出了问题比盯着寄存器值猜要快。4.3 printf 能用但会让系统变慢或卡住newlib 与 xil_printf 的取舍标准printf在裸机上的行为和在 PC 上完全不同。它依赖 newlib需要堆、需要_sbrk、需要fputc的重定向还会在内部加锁哪怕是单线程。这些开销在调试阶段无所谓但在一个有时序要求的循环里调用printf可能直接把你的周期打乱。我用过的一个实测对比在 A53 上跑一个 10 万次的循环循环体里分别调用xil_printf和printf打印一个整数。xil_printf那次大约几百毫秒完成printf那次要好几秒差距在一个数量级以上。原因是printf每次调用都要做格式解析、缓冲区管理而且默认的串口输出是阻塞的每个字符都要等 FIFO 有空位。所以我的用法是分场景调试期用printf方便格式丰富一旦进入性能敏感的地方全部换成xil_printf或者干脆自己写一个极简的uart_send_str()只发字符串不解析格式。如果要打印十六进制数值自己写个to_hex()转换比调printf(%x)快得多。还有一个坑printf卡死但程序没崩。这种情况往往是堆不够malloc在 newlib 内部返回了 NULL而printf没有检查继续往下写就踩到非法地址。解决办法不是简单调大 heap而是先跑一遍官方提供的 Memory Tests 模板确认内存读写正常再逐步调大 heap 到够用为止。8KB 起步16KB 比较稳64KB 基本不会出问题。5. 把它做成能反复用的东西链接脚本、缓存一致性与双核分工跑通 Hello World 只是起点。真正在一个项目里用裸机你会很快遇到这三个问题代码放哪、数据一致怎么保证、多颗核怎么协作。这一节讲的是从一个“能跑”的工程到一个“能交付”的工程之间要补的东西。5.1 链接脚本三段式TCM、OCM、DDR 的取舍与实测ZCU102 上可供裸机使用的内存有好几块各有各的脾气内存区域地址范围容量特点R5 ATCM0x00000000R5 视角128KB极快无缓存仅 R5 可访问R5 BTCM0x00020000R5 视角128KB同上OCM0xFFFC0000256KB片上 SRAMA53 和 R5 都能访问DDR0x00000000A53 视角最大 4GB容量大但有缓存一致性问题A53 裸机默认把代码放 DDR这是合理的因为 A53 的 TCM 概念不适用A53 没有 TCM只有 L1/L2 缓存。R5 裸机默认放 TCM速度极快且确定性好但 128KB 很容易不够。我的实际做法是分两段把中断向量表、中断服务函数、以及所有对时序敏感的小函数放到 TCM 或者 OCM把大块的数据缓冲区和主逻辑放 DDR。实现方式是在链接脚本里定义自定义段MEMORY { psu_ddr_0 : ORIGIN 0x00100000, LENGTH 0x7FF00000 psu_ocm_ram_0 : ORIGIN 0xFFFC0000, LENGTH 0x40000 } SECTIONS { .vectors : { *(.vectors) } psu_ocm_ram_0 .fast_code : { *(.fast_code) } psu_ocm_ram_0 .text : { *(.text) } psu_ddr_0 }配合代码里的__attribute__((section(.fast_code)))就能把指定函数钉在 OCM 里。实测下来OCM 里的中断响应比 DDR 里稳定得多因为 DDR 存在刷新和行激活的不确定性最坏情况下的延迟可能是平均值的数倍。要注意一点OCM 只有 256KB而且是 A53 和 R5 共享的。如果你打算用它做双核通信的共享缓冲区就得预留出来别被代码段吃光。我一般把 OCM 切成两半低 128KB 放代码高 128KB 留给共享内存。5.2 与 PL 交互时的缓存坑flush/invalidate 到底该放哪裸机工程和 PL 打交道的方式无非两种寄存器访问AXI-Lite和 DMAAXI HP/HPC。前者简单后者是缓存一致性问题的重灾区。原理很直白A53 写数据时先写进缓存DDR 里还是旧值。这时候你启动 PL 侧的 DMA 去读 DDR读到的就是旧数据。反过来DMA 把数据写进 DDRA53 读的时候如果命中了缓存里的旧副本读到的也是旧数据。两种方向对应两个操作写之前flush把缓存刷到内存读之前invalidate让缓存失效强制从内存重新读。Xilinx 的 BSP 提供了对应函数#include xil_cache.h /* 送数据给 PL 之前把缓存刷到 DDR */ Xil_DCacheFlushRange((UINTPTR)tx_buf, TX_LEN); /* 从 PL 收数据之后让缓存失效避免读到旧副本 */ Xil_DCacheInvalidateRange((UINTPTR)rx_buf, RX_LEN);有两个细节必须注意。第一这两个函数的参数是地址和长度地址必须是缓存行对齐的。A53 的缓存行是 64 字节如果你的缓冲区起始地址没对齐函数内部会做对齐处理但可能把相邻的变量一起刷掉或者失效掉导致别处的数据莫名其妙变旧。标准做法是用__attribute__((aligned(64)))修饰所有参与 DMA 的缓冲区。第二只刷数据缓存不够的时候还要考虑指令缓存。如果你把代码动态复制到某块内存然后跳过去执行比如从 SD 卡加载一段程序必须先Xil_DCacheFlushRange再Xil_ICacheInvalidateRange两步缺一不可。这个坑我在做固件升级功能时踩过代码复制过去但 I-Cache 里还是旧的跳过去直接跑飞。5.3 APU RPU 同时跑裸机OpenAMP 之前先做最小验证ZCU102 的一个常见用法是 A53 跑主控逻辑R5 跑实时任务或者安全监控。这两颗核各自有独立的裸机工程通过共享内存通信。官方提供的方案是 OpenAMP但那套东西上手成本不低建议先用最朴素的方式验证通道。最小验证方案在 OCM 里划一块地址比如0xFFFC0000 0x20000A53 往这个地址写一个魔数R5 轮询读这个地址读到就翻转一个 GPIO。这个方案不涉及中断、不涉及 RPMsg、不涉及任何框架纯粹验证两颗核都能访问同一块物理内存。A53 侧的写法#define SHM_BASE 0xFFFE0000U volatile uint32_t *shm (volatile uint32_t *)SHM_BASE; *shm 0x5A5A1234U; Xil_DCacheFlushRange((UINTPTR)shm, 64);R5 侧的写法volatile uint32_t *shm (volatile uint32_t *)0xFFFE0000U; while (*shm ! 0x5A5A1234U) { /* 空转等待 */ } XGpioPs_WritePin(gpio, LED_PIN, 1);跑通这个之后再往上叠中断通知、再换 OpenAMP出问题的时候你就知道是框架的问题还是硬件通道的问题。这个分层验证的思路比一上来就啃完整框架要省时间得多。另外提醒一句R5 默认是 lockstep 模式两颗 R5 核是同步执行同一份代码的你没法让它们跑不同的程序。要在 Vivado 的 PS 配置里把 RPU 改成 split 模式才能分别加载两个 ELF。这个配置一旦定下来Vitis 里就改不了必须回 Vivado 重新导出 XSA。6. 从 JTAG 走到脱机上电BOOT.BIN 的组成与烧写验证Debug 环境里的一切都跑通之后最后一步是脱机启动。这一步是把工程变成“插电就能跑”的成品也是问题最容易集中爆发的地方因为之前所有被调试器悄悄替你做的事现在都要写进镜像里。6.1 bif 文件里放什么、按什么顺序放BOOT.BIN 的生成靠 bootgen输入是一个 bif 文件。一个最小的纯裸机 bif 长这样the_ROM_image: { [bootloader, destination_cpua53-0] fsbl.elf [destination_cpua53-0] app_hello.elf }第一行是 FSBL标记为bootloader它会被 BootROM 加载并执行负责初始化 DDR、配置 PL、然后加载后面的分区。第二行是你的应用被加载到 DDR 然后跳转执行。如果工程里有 PL 逻辑中间要加一行[destination_devicepl] design_wrapper.bit顺序很重要bitstream 必须在应用之前加载否则应用一上来就访问 PL 寄存器会读到一个未配置的逻辑。另外 FSBL 加载 bitstream 需要一点时间取决于 bit 文件大小FSBL 源码里有对应的打印调试的时候可以从串口看到进度。生成命令bootgen -image boot.bif -arch zynquplus -o BOOT.BIN -w on-arch zynquplus指定器件架构ZCU102 用的是 Zynq UltraScale这个参数不能写错。-w on表示覆盖已存在的输出文件不加的话第二次生成会报错。FSBL 的 ELF 从哪来在 Vitis 里新建一个应用工程模板选Zynq MP FSBL平台选同一个编译出来就是。注意 FSBL 工程的 BSP 配置要和应用工程一致同一个平台、同样的 stdout否则串口上的启动信息会看不到。6.2 拨码开关与存储介质上电不成功的定位方法ZCU102 上的 SW6 拨码开关决定启动介质。常见的几种设置JTAG 模式、SD 卡模式、QSPI 模式。具体的开关组合板卡手册上有表格我这里只讲定位思路。上电完全没反应先看电源指示灯再看串口有没有 FSBL 的任何输出。FSBL 一启动就会打一大段信息如果连这个都没有说明 BootROM 没找到镜像或者镜像格式不对。检查点BOOT.BIN 是不是放在 SD 卡的第一个 FAT 分区根目录、拨码开关是不是真的在 SD 模式、SD 卡本身是不是兼容有些大容量卡需要特定格式。FSBL 有输出但加载应用后卡住这种情况通常是应用本身的问题比如链接地址和 FSBL 加载地址不一致或者应用一上来就访问了未初始化的外设。定位办法是看 FSBL 的打印它会告诉你加载了哪个分区、跳到哪个地址。如果跳转地址和你lscript.ld里的.text起始地址对不上那就是分区描述写错了。应用跑起来了但串口没输出JTAG 下能跑脱机没输出八成是 BSP 里的 UART 初始化依赖了某个 JTAG 模式下的默认状态。检查 FSBL 里用的 UART 和应用的 UART 是不是同一路如果不是应用启动时那一路的时钟可能没被使能需要手动在代码里加时钟配置。间歇性启动失败电源或者 DDR 时序余量不足的典型表现。先换一个质量好一点的电源适配器试再用官方的 DDR 内存测试跑一遍长时间压力测试。这类问题在实验室常温下不容易复现但到现场就出来了值得在开发阶段多花点时间验证。7. 我个人在 ZCU102 裸机工程上的一点体会做了这么多次我现在建裸机工程的顺序已经固定下来了先拿 R5 的 TCM 工程当探针确认板子活着再建 A53 的 DDR 工程验证串口和中断然后才接入 PL。这个顺序看起来慢实际上比一上来就搞复杂工程要快因为每一步都只有一个变量。还有一个习惯是每改一次 XSA我一定先跑一遍 Hello World确认平台更新成功了再往下写业务代码。BSP 没重新生成导致的 bug 是最难查的因为代码看起来完全正确。至于 heap 和 stack 那两个参数我现在的默认值已经是 64KB 了反正 DDR 有的是空间没必要为省这点内存给自己找麻烦。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询