ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Ultra96实战:Zynq UltraScale+ 的PetaLinux构建与FPGA开发全攻略

2026/8/26 10:31:30 拓冰建站 浏览量
Ultra96实战:Zynq UltraScale+ 的PetaLinux构建与FPGA开发全攻略 从上一篇写完到现在我手上这块 Ultra96 已经连续跑了几个月中间换了三版 FPGA 逻辑、重做过两轮 PetaLinux 根文件系统也踩了不少文档里根本不会写的坑。Part 1 主要把开箱、硬件资源、基础启动流程过了一遍这一篇我打算聊点更接近实战的东西软件栈到底怎么串起来的、PetaLinux 怎么从零构建一个能用的系统、PL 端逻辑怎么跟 Linux 应用层配合以及我在实际调试时遇到过的问题和解决套路。如果你手上正好有 Ultra96或者准备用 Zynq UltraScale 系列做产品原型这篇应该能帮你少走不少弯路。先说清楚这篇文章不适合完全没接触过 Zynq 的新手至少你要知道 PL 和 PS 分别是什么、Vivado 能干嘛、Linux 基础命令能上手。如果你只玩过树莓派刚拿到 Ultra96 还没跑通 Hello World建议先把官方 PetaLinux BSP 烧进 SD 卡跑一遍再回来看这篇不然很多概念会有点飘。1. 从烧录到启动Ultra96 的完整启动链路很多人在 Ultra96 上卡住的第一道坎不是代码写错而是根本没搞明白启动过程。板子上电到 Linux 出现登录提示符中间其实经过了五六个阶段任何一个环节配置不对表现都是“看起来没反应”但排查方向完全不同。1.1 启动的前几秒到底发生了什么Ultra96 用的是 Xilinx Zynq UltraScale ZU3EG这颗芯片的启动流程比传统单片机复杂得多。上电后首先是片内 BootROM 运行它固定存在于芯片内部不可修改负责读取 Boot Mode 引脚的电平状态然后决定从哪个设备加载下一级启动镜像。Ultra96 支持从 SD 卡、QSPI Flash、JTAG 启动板子上有一个拨码开关专门切这个。BootROM 加载的是 FSBLFirst Stage Boot Loader也就是第一级引导程序。FSBL 不是 Xilinx 写死的而是由你在 Vivado 里导出的硬件描述文件XSA生成出来的它知道 DDR 控制器的初始化参数、PS 端时钟配置、MIO 分配等等。FSBL 干的事包括初始化 DDR、配置 clocks、把 PMU Firmware 加载到对应处理器然后跳转到 ATFARM Trusted Firmware或 U-Boot。从 FSBL 到 U-Boot 之间还有一个容易被忽略的角色PMU Firmware。Zynq UltraScale 有一个独立的 PMU 处理器Platform Management Unit专门负责电源管理、错误管理、功耗监控。这个固件必须在启动早期就加载否则后面很多外设和电源域会出现诡异问题。PetaLinux 构建系统会自动把 PMU Firmware 打进镜像里但如果你用纯手工流程很容易漏掉这一步表现为启动卡死在 DDR 初始化之后。U-Boot 起来之后才轮到 Linux 内核。它从 SD 卡的 BOOT 分区读取 image.ub 或者分别读取内核、设备树、根文件系统然后跳转执行。这里有个很多新手都搞混的点Ultra96 默认的 SD 卡分区方案里第一个分区是 FAT 格式的 BOOT 分区第二个分区才是 EXT4 的根文件系统。1.2 Boot 模式开关和 SD 启动的关系Ultra96 板子上 Boot Mode 拨码开关的位置不同批次可能略有差异但逻辑都一样拨到对应位置就选择从对应介质启动。我遇到过几个朋友拿到板子第一件事就是烧 SD 卡插上去发现串口没输出查了半天才发现拨码开关还在 QSPI 模式系统一直在读 Flash 里的旧镜像。这里给个特别实际的建议拿到板子先把拨码开关的丝印位置拍照存档然后每种模式都试一遍记录对应的启动行为。因为 Ultra96 板卡的文档虽然写了开关定义但实际丝印很小光线不好的时候根本看不清。我自己的习惯是 SD 启动时把开关拨到靠近 SD 卡槽那一侧具体丝印可能因版本而异以官方原理图为准。1.3 定制启动流程时需要动哪些东西如果你只是跑官方 BSP那启动链路基本不用管但一旦你要定制硬件、改 DDR 参数、加 PL 逻辑就得重新生成 FSBL 和设备树。最常见的做法是在 Vivado 里完成硬件设计并导出 XSA然后交给 PetaLinux 生成 FSBL、ATF、U-Boot 和设备树。另外一个容易被忽略的是 U-Boot 环境变量。Ultra96 的 PetaLinux BSP 默认把环境变量存在 SD 卡的 FAT 分区里文件名叫 uEnv.txt。这个文件里可以覆盖 bootargs、指定内核镜像路径、设置 IP 地址等等。我经常用它来临时改内核启动参数比如加 earlycon 调试输出或者指定不同的 root 设备比重新编译 U-Boot 快得多。注意如果在 U-Boot 里改了 bootargs 但重启后被重置检查一下 uEnv.txt 是不是被 U-Boot 重新加载覆盖了。默认配置下uEnv.txt 里的 bootargs 优先级很高你改了板级默认值但没改 uEnv.txt那还是以文件里的为准。2. PetaLinux 构建实战从 XSA 到可启动 SD 卡PetaLinux 是 Xilinx 提供的一整套嵌入式 Linux 构建系统底层基于 Yocto。用它的好处是能直接对接 Vivado 导出的 XSA自动生成 FSBL、ATF、U-Boot、内核和设备树省去大量手工配置。用它的坏处是第一次构建时间非常长而且对网络依赖很大不做好准备工作很容易中途失败。2.1 环境准备和版本对齐是最大的坑我在 Ultra96 上踩过最疼的一个坑就是 Vivado 版本和 PetaLinux 版本不匹配。PetaLinux 2019.2 对应 Vivado 2019.2PetaLinux 2020.2 对应 Vivado 2020.2跨版本导入 XSA 极大概率会报错而且报错信息很多时候看不太懂比如缺少某个组件或者无法识别硬件描述。Xilinx 官方文档明确要求版本一一对应但实际项目里因为分工原因经常出现硬件工程师用新版本软件工程师用旧版本的情况就会踩这个坑。所以第一步先确认你的工具链版本最好在虚拟机里固定一套环境。我自己用的是 Ubuntu 18.04 配 PetaLinux 2019.2因为那个版本相对稳定社区资料也多。新版本虽然支持更新的内核但如果你不需要那些特性没必要追新稳定压倒一切。然后就是安装依赖。PetaLinux 安装脚本虽然会检查部分依赖但不会全部覆盖缺少一些包会在构建中途才报错。我整理了一份常用依赖列表包括 build-essential、git、chrpath、diffstat、texinfo、libssl-dev 这些装完之后再跑安装脚本能省掉很多来回折腾的时间。小提示磁盘空间至少预留 100GB最好用 SSD。第一次构建 PetaLinux 工程即使是 Ultra96 这种入门级 MPSoC编译整个 rootfs 也会产生大量中间文件机械硬盘会慢到让你怀疑人生。2.2 创建工程并导入 XSA 的完整流程硬件工程师在 Vivado 里做完 Block Design综合、实现、生成比特流然后 File Export Hardware勾选包含比特流导出一个 XSA 文件。这个文件是你做嵌入式 Linux 的源头里面包含硬件配置、地址映射、外设信息相当于一份机器可读的硬件说明书。拿到 XSA 之后PetaLinux 这边的操作命令其实不多但每一条都很关键# 创建带模板的 PetaLinux 工程 petalinux-create -t project -n ultra96_linux --template zynqMP # 进入工程目录 cd ultra96_linux # 导入硬件描述文件 petalinux-config --get-hw-description/path/to/xsa -x注意-x参数的作用是执行默认的配置同步把硬件信息同步到工程里。如果没有加这个参数有时会出现设备树和硬件不匹配的情况启动的时候外设找不到。导入完成后可以运行petalinux-config进入图形化配置界面这里能设置启动方式、根文件系统类型、U-Boot 配置、内核配置入口等。默认配置已经能启动但有几个项我强烈建议按自己的需求修改主机的 IP 地址和子网掩码方便后面 NFS 调试、串口参数默认 115200 8N1、根文件系统路径。2.3 内核配置和根文件系统的裁剪内核配置用petalinux-config -c kernel进入 menuconfig 界面本质上和标准 Linux 内核配置一样。但有个细节要注意PetaLinux 生成的默认配置已经包含了 Ultra96 板级的大部分驱动不要轻易去掉你不知道作用的选项有些驱动看起来没用实际是设备树里某个节点依赖的。根文件系统配置用petalinux-config -c rootfs默认是 PetaLinux 自己的一套基于 RAMFS 的 initramfs但更推荐改成 SD 卡上的 EXT4 文件系统。因为 initramfs 是内存文件系统断电丢数据不适合保存配置和日志。我一般配置成 rootfs 类型为 EXT4然后在 SD 卡上准备好对应的分区。如果你需要定制软件包比如加入 ssh、vim、Python 等在 rootfs 配置界面里有软件包列表勾选即可。但是要注意加入的软件包越多构建时间越长启动时占用的空间也越大。对于原型验证我建议只加一个 openssh-server 和一个 python3其他的后面用 opkg 在线装。2.4 构建和打包镜像的完整清单配置完成后执行构建命令# 编译整个工程 petalinux-build # 生成 BOOT.BIN petalinux-package --boot --fsbl images/linux/zynqmp_fsbl.elf --pmufw images/linux/pmufw.elf --atf images/linux/bl31.elf --u-boot images/linux/u-boot.elf构建完成后在images/linux目录下会生成一堆文件。你需要的是 BOOT.BIN、image.ub 和 boot.scr。其中 BOOT.BIN 是 FSBL PMU ATF U-Boot 打包在一起的启动镜像image.ub 是内核 设备树 根文件系统打包在一起的 FIT 镜像boot.scr 则是 U-Boot 的启动脚本。拷贝到 SD 卡的顺序是第一个 FAT 分区放 BOOT.BIN、image.ub、boot.scr第二个 EXT4 分区存放根文件系统解压后的内容。根文件系统怎么获得PetaLinux 构建完成后images/linux/rootfs.ext4就是已经做好的文件系统镜像用 dd 写入 SD 卡第二分区或者挂载后解压拷贝都可以。一个值得注意的点U-Boot 默认读取 boot.scr 来执行启动命令但如果你用的是默认配置生成的 BOOT.BIN它内部可能已经嵌入了启动环境变量boot.scr 优先级反而没你想象的高。如果改了 boot.scr 没生效检查一下 U-Boot 环境变量里的 bootcmd 是不是被覆盖了。2.5 PetaLinux 构建中常见的几个坑构建过程中最常见的失败原因是网络问题。Yocto 会从网上下载各种源码包如果网络不稳定经常在某个包的下载环节卡住。解决办法是提前配置好 sstate-cache或者使用国内镜像源。我遇到过第二次构建比第一次还慢的情况原因是 Yocto 的临时文件没有正确复用。后来发现是改了配置项之后Yocto 会重新编译相关组件但不一定会清理旧的临时文件。这时候最有效的方法是删掉 build 目录重新构建虽然耗时但干净利落。还有一个坑是 QEMU 仿真验证。PetaLinux 在构建完成后会尝试用 QEMU 启动镜像验证但这个步骤在无图形界面的服务器上经常失败报各种图形库错误。如果你只是要生成镜像不需要仿真验证可以在petalinux-build后用petalinux-boot --qemu跳过验证直接进入打包阶段或者设置环境变量跳过 QEMU 检查。3. PL 端开发用 FPGA 给 Ultra96 扩展专属硬件Ultra96 和普通 ARM 开发板最大的区别就是它有一块可编程逻辑。这意味着你不只能跑现成的 Linux还能在 FPGA 里实现自己的硬件加速器、自定义外设、高速数据采集通道然后让 CPU 通过 AXI 总线访问这些逻辑。这一章节我重点讲清楚从 Vivado 到 Linux 应用层的完整链路。3.1 为什么要用 PL 而不是纯 PS在实际项目里PL 的用途一般集中在三类场景。第一类是高速接口比如 MIPI CSI 摄像头、DisplayPort、SFP 光口这类 SoC 的 PS 端没有直连或者带宽不够的接口需要用 FPGA 的逻辑做协议转换。第二类是数据流加速比如图像处理、DSP 算法、硬件编解码在 CPU 上跑可能是几十帧每秒用 PL 做流水线可以达到几百上千帧每秒。第三类是实时控制比如电机驱动、PWM 输出、自定义通信协议硬实时要求高用 Linux 软件来做延迟不可控放在 FPGA 里才有确定性。Ultra96 上的 ZU3EG 在 PL 端有大约 154K 个逻辑单元对于很多原型验证来说完全够用。但要注意PL 的资源和 PS 的 DDR 带宽是共享的设计时要评估好数据通路的带宽需求。3.2 Vivado 里的最小块设计长什么样一个最小可用的 Ultra96 PL 设计至少需要包含以下几个部分Zynq UltraScale PS 模块、AXI 互联、一个自定义 IP、以及必要的时钟和复位。在 Vivado 里创建 Block Design 后添加 Zynq UltraScale MPSoC IP然后运行 Block Automation它会自动帮你配置好 Ultra96 对应的 PS 端默认设置包括 DDR、UART、SD、USB、以太网等。这一步比较傻瓜化但有个关键点默认配置里 PL 的时钟可能需要你自己设置。Ultra96 的 PS 参考时钟一般是 33.33MHz有些版本是 52MHzBlock Automation 通常能识别但如果你用的是自定义板卡需要手动核对。添加自定义 IP 的方式有两种直接写 RTL 然后用 IP Packager 封装或者在 Block Design 里直接用 RTL 模块。我建议用 IP Packager 的方式因为你可以在 IP 里定制 AXI 接口配置Vivado 会自动生成 AXI 接口逻辑。连接关系上自定义 IP 一般通过 AXI_GP 口或 AXI_HP 口连接到 PS。AXI_GP 是通用目的接口带宽较低但配置简单适合控制类寄存器AXI_HP 是高带宽接口连接 DDR 控制器适合大数据量传输。第一版设计建议先用 AXI_GP 验证逻辑跑通了再优化到 HP。3.3 Linux 下加载比特流的两种方式PL 设计完成后Vivado 会生成比特流文件。在开发阶段最简单的方式是用 JTAG 下载然后在 PetaLinux 启动的时候让 FPGA Manager 加载同一个比特流。但实际部署时更常见的是在 Linux 运行时加载比特流这样不需要重新启动系统就能更新 FPGA 逻辑。PetaLinux 默认支持 FPGA Manager 框架设备树里有对应的节点。运行时加载的流程是# 将比特流文件拷贝到开发板 scp design.bit rootultra96:/lib/firmware/ # 查看 FPGA Manager 设备 ls /sys/class/fpga_manager/ # 加载比特流 echo design.bit /sys/class/fpga_manager/fpga0/firmware这里的原理是 Linux 内核通过 FPGA Manager 框架调用底层驱动将比特流写入到 Zynq UltraScale 的配置寄存器PCAP/DEVCFG完成 PL 的编程。加载完成后PL 端逻辑立即生效对应的设备树节点如果定义了内核会尝试探测驱动。另一种方式是在 U-Boot 里加载比特流适合需要 PL 端逻辑在 Linux 启动前就就绪的场景比如你想要 U-Boot 或内核早期阶段就能访问 PL 里的外设。做法是把比特流打包进 BOOT.BIN在生成 BOOT.BIN 时用--fpga参数指定比特流文件。这种方式更稳妥但是修改 PL 逻辑需要重新做 BOOT.BIN 并重启系统。3.4 应用程序如何访问自定义 IP 的寄存器PL 端自定义 IP 和 Linux 应用层之间的交互核心是寄存器读写。Vivado IP Packager 会为你的 IP 生成一组 AXI 寄存器默认包含若干个 32 位寄存器你可以定义它们的含义。Linux 侧访问这些寄存器有几种方式最简单的就是设备树配置后用 UIOUserspace I/O框架。UIO 的好处是不用写内核驱动应用层直接通过 mmap 映射寄存器地址空间读写就像操作本地内存一样。设备树里需要添加类似这样的节点my_ip_uio: my-ipa0000000 { compatible generic-uio; reg 0x0 0xa0000000 0x0 0x10000; };然后在应用层用 open 打开/dev/uio0通过mmap映射物理地址到用户空间虚拟地址接下来就可以直接读写寄存器了。这种方法在做原型验证时效率极高不用每改一次逻辑就编译一次驱动。但 UIO 的缺点是没有中断处理框架如果 IP 需要产生中断通知 CPU你还得额外处理中断事件通过 read 阻塞等待内核通知。对于简单的状态轮询直接读寄存器就够用了。3.5 调试 PL 逻辑的常用手段PL 逻辑出问题的时候最直接的排查手段是 Vivado 里的 ILAIntegrated Logic Analyzer核。在设计里插入 ILA 后重新综合实现生成比特流然后通过 JTAG 连接在 Vivado Hardware Manager 里实时抓取内部信号。这个方案在 FPGA 开发里是基本功但在嵌入式 Linux 场景下容易被忽略因为很多人觉得 JTAG 太麻烦。另一个很实用的调试方法是把 PL 端的状态信息通过 AXI 寄存器暴露给 CPU。比如做一个 32 位的状态寄存器把关键信号、数据计数、错误标志映射进去Linux 应用层定期读取打印。这样不用接 JTAG 就能在运行时观察逻辑状态特别适合定位那种“开发环境里正常一跑真机就出问题”的情况。如果 PL 和 PS 之间的 AXI 通信异常先查地址映射是否正确。Vivado 的 Address Editor 里能看到每个 IP 被分配的地址把这个地址和设备树里的 reg 属性对应起来。还有就是要确认 AXI 时钟已经正确供给到 PL 逻辑Ultra96 的 PL 端逻辑如果没有收到时钟寄存器读出来全是 0xDEADBEEF 之类的小概率值或者直接超时。4. 接口实测与性能调优把 Ultra96 真正用起来开发板最终是要跑东西的不是用来炫参数的。这一节我把 Ultra96 上经常用到的几个接口都实际测了一遍包括网络、USB、显示、温度等记录了一些真实数据和使用心得。4.1 千兆以太网和 USB 3.0 的实际表现Ultra96 板载的千兆以太网用的是 Realtek RTL8211F PHY通过 RGMII 接口连接到 PS 端。默认驱动下iperf 测速能跑到 900Mbps 以上接近千兆线的理论极限。如果你测出来只有几百兆先检查网线是不是六类线再检查有没有开流控之类的节能选项。实测命令很简单# 开发板作为服务端 iperf3 -s # PC 作为客户端 iperf3 -c 板子IP -t 30 -i 1跑出来的结果在 900Mbps 左右属于正常水平。我见过一个诡异的现象网络在低负载时正常一跑大流量就丢包严重后来发现是散热问题芯片过热导致 MAC 侧降频或缓冲队列溢出加了散热片之后恢复稳定。USB 3.0 接口测速用外接 SSD 实测顺序读写在 300MB/s 左右达不到 USB 3.0 理论峰值但考虑到 SoC 的 USB 控制器性能和文件系统开销这个数算正常。如果你是拿来做高速数据存储的建议用 ext4 文件系统而不是 FATFAT 在大文件连续读写时效率低很多。4.2 DP 显示和 MIPI CSI 摄像头Ultra96 板载的 DisplayPort 输出在 Linux 下的驱动默认是开启的但需要有对应的 DRM/KMS 驱动和图形栈配合。如果你只是跑一个轻量级 Qt 应用做界面显示用 PetaLinux 自带的 DRM 驱动就够了不需要额外装 X11。MIPI CSI 摄像头的接入稍微麻烦一点因为 Zynq UltraScale 的 MIPI CSI 接收是 PL 端实现的不是 PS 内置的。这意味着你需要在 Vivado 里添加 MIPI CSI-2 RX 控制器 IP把它跟 VDMAVideo Direct Memory Access连起来再跑到 Linux 里用 V4L2 框架对接。这个链路涉及的环节多任何一环没配好都会导致出不了图。我用的是 Sony IMX214 模组Vivado 里加上 MIPI CSI-2 RX 后还需要在设备树里配置好传感器地址、时钟频率和 lane 数。V4L2 侧用 media-ctl 工具配置 pipeline完整流程跑通之后用 gst-launch 或者 ffmpeg 就能采集图像视频流。这里要提醒的是MIPI CSI 的时钟异常是最常见的无图原因先检查传感器和接收端是否有相同的参考时钟。4.3 CPU 频率、温度和散热管理ZU3EG 的 CPU 默认最高可以跑到 1.5GHz但实际运行中温度墙会限制性能发挥。PetaLinux 默认启用 CPUFreq 驱动支持调速器切换。查看当前频率和调速器cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor默认一般是 performance 或者 ondemand。我实测 ondemand 在低负载时能明显降低功耗和温度但延迟敏感的实时应用建议用 performance否则 CPU 频率切换会造成微秒到毫秒级的抖动。温度方面Ultra96 板卡没有主动散热被动散热片只在长时间高负载时效果有限。我跑 PL 逻辑满载 4 核 CPU 全开时温度能冲到 85 摄氏度。超过 90 度芯片会自动降频保护性能下降非常明显。有条件建议加装一个小风扇主动散热或者至少保证通风不要放进封闭的塑料外壳里跑长时间任务。查看温度cat /sys/class/thermal/thermal_zone0/temp4.4 PL 和 DDR 之间的带宽估算当你开始做 PL 端数据加速的时候必然会关心一个问题PL 和 DDR 之间到底能跑多快。ZU3EG 的 PS 端 DDR4 有 64 位总线频率通常在 2400MHz 左右理论带宽大约 19.2GB/s。但这是理论值实际到 PL 端的数据带宽取决于你用的是 AXI 哪个接口。AXI_HP 接口理论最高带宽约 128-bit 300MHz也就是 4.8GB/s 左右但实际传输要打折扣因为 DDR 控制器要处理刷新、总线仲裁、PL 与 PS 的并发访问。如果只是用 AXI_GP 接口做小数据量控制带宽只有 600MB/s 左右做数据流传输肯定不够用。实测中我用 AXI_HP 做了一个简单的 DMA 搬运从 PL 端写入 DDR带宽能到 3.5GB/s 左右已经比较可观了。如果你的设计带宽上不去检查 AXI 数据位宽是否设置成最大AXI 时钟是否拉到最高以及 DMA 描述符是否有太多的开销。在设计数据通路时要避免 PL 端频繁小粒度访问 DDR尽量用 AXI Burst 连续传输效率会高非常多。如果数据是流式的考虑用 AXI-Stream 接口配合 DMA IP避免经过 AXI-Lite 的握手开销。5. 调试经验与踩坑记录Part 2 最值得看的实战环节这部分我从实际项目里挑了几个典型的调试案例每个都是真实发生过的排查思路和坑点都整理出来希望你能直接拿来对照。5.1 启动卡死先定位卡在哪个阶段启动卡死的排查核心是先搞清楚卡在哪个阶段。串口输出是关键正常启动时 BootROM 后会有 FSBL 的打印然后是 U-Boot 的打印最后是内核的打印。如果串口完全没有输出大概率是 BootROM 没找到启动介质第一时间检查拨码开关和 SD 卡格式。如果 FSBL 打了几个字符就停了可能是 DDR 初始化失败或者 PMU Firmware 有问题用 JTAG 连接 XSDB 查看当前 PC 指针位置能看到卡在哪个函数里。如果 U-Boot 打印到了“Starting kernel ...”说明内核已经加载但启动失败加 earlycon 参数看内核打印基本就能定位到是设备树不匹配还是驱动 panic。我自己遇到过最诡异的一个现象串口打印到了内核的启动 logo然后过几分钟自动重启。最后排查发现是电源管理相关驱动没初始化好PMU 固件和内核版本不匹配导致电压调节异常重新生成 PMU Firmware 后解决。5.2 U-Boot 环境变量损坏的修复U-Boot 环境变量存在 SD 卡 FAT 分区的特定区域如果异常断电或者误操作写入可能导致 U-Boot 启动报环境变量校验错误然后回退到默认环境。表现是启动后网口 IP 变了或者之前的 bootargs 设置全部丢失。修复方式很简单删除 SD 卡上的 uEnv.txt如果是外部存储的环境变量文件或者在 U-Boot 命令行里执行env default -a再saveenv。如果环境变量文件损坏到 U-Boot 直接卡住还有一个办法把 BOOT.BIN 用 QSPI 模式引导一次U-Boot 起来后切换到 SD 环境再保存但这种操作容易把问题弄复杂我一般直接重新烧 SD 卡。5.3 PetaLinux 构建时 QEMU 验证失败我在多次 PetaLinux 构建后都遇到 QEMU 验证失败的问题尤其是没有图形化界面的服务器环境。错误信息通常和 libGL/gtk 相关或者直接报无法启动图形界面。这类问题的本质是 PetaLinux 的 QEMU 验证需要 X 显示环境但服务器没有。其实这个 QEMU 验证不是必须的它只是用来确认生成的镜像能在模拟环境里启动。如果你只是要生成 SD 卡镜像可以直接跳过。做法是在构建后不执行 QEMU 验证直接打包 BOOT.BIN。我建议如果你要测 QEMU在本地开发机上做不要在构建服务器上做否则白白浪费时间调试图形库依赖。5.4 I2C 通信失败的经验总结Ultra96 上有很多 I2C 设备比如电源管理芯片、EEPROM、温度传感器。I2C 调试失败的常见原因有几个地址不对、电平不匹配、上拉电阻缺失、总线被占用。最常见的是地址问题。同一颗芯片可能因为地址引脚配的不同具体 I2C 地址也不同。用i2cdetect -y -r bus扫描实际地址比自己查数据手册猜地址快得多。如果扫描不到设备先用万用表量一下 SCL 和 SDA 的电平正常空闲状态应该是高电平。如果有一根线被拉低说明总线被某个设备卡死了检查是不是有设备地址冲突或者复位引脚没释放。在 Ultra96 上另一个容易忽视的是 PS 端 I2C MIO 复用的电平。如果外设是 1.8V 电平而你的扩展板是 3.3V之间没做电平转换通信大概率不稳定。5.5 常见问题速查表问题现象可能原因排查顺序串口无输出Boot模式不对、SD卡格式错误查拨码开关重新格式化SD卡启动卡在FSBLDDR配置错误、PMU固件缺失检查XSA生成过程JTAG看现场U-Boot启动后命令不执行bootcmd被修改、boot.scr缺失检查uEnv.txt和boot.scr内核启动到一半panic设备树和硬件不匹配检查设备树中PL节点地址和Vivado分配是否一致PL加载后应用层访问超时AXI地址映射错误、时钟没供上核对地址编辑器、检查时钟配置网络不定时丢包散热不充分、PHY配置错误加散热片、检查RGMII延时配置I2C扫描不到设备地址错误、总线被锁死、电平不匹配i2cdetect扫描、万用表量电平CPU温度过高散热不足、调速器配置加风扇、改用兼顾功耗的调速器关于调试工具我再多提一句Ultra96 有板载 JTAG 接口配合 Xilinx SmartLynq 或者 Digilent JTAG-HS3 使用。但如果你像我一样经常出差不想背设备实在不行用串口加 ILA 也能解决大部分问题。JTAG 是万金油但每次连接、上传比特流、抓信号的时间成本不低在快速迭代阶段反而不如串口日志加状态寄存器来得高效。做 Ultra96 项目这段时间我最大的体会是这块板子其实是把两个世界拼在了一起。一边是嵌入式的 Linux 生态一边是 FPGA 的硬件逻辑两边有各自成熟的调试套路但真正难的是在两者交界处找到问题。很多坑你光看文档是看不出来的只有真在板子上跑过一遍才会意识到工具链版本一致性、启动顺序、地址映射这些细节到底有多重要。最后再分享一个小技巧在开始一个新功能之前先用串口把完整启动日志存一份然后在 Vivado 里把地址映射表导出来存好。等到后面出了问题这两份资料能帮你省下很多排查时间。如果你也正在 Ultra96 或者 Zynq UltraScale 上做开发欢迎在评论里交流你碰到的奇怪问题我看到了会尽量回复。