4、理解软件平台(BSP与FSBL)

好,咱们继续往下走。前面几章我们把硬件平台搭起来了,Vitis 工程也建好了。但有个问题——你写好的程序,怎么跑到板子上去?谁去初始化 DDR?谁去配置 PLL?谁去把程序从 Flash 搬到内存里?

嗯,这就是软件平台要干的事。说白了,就是 BSP 和 FSBL 这两个家伙。我刚开始接触 Zynq 的时候,也搞不清楚它们到底谁管谁。今天咱们就把它彻底理清楚。

4.1 BSP 到底是什么?

BSP,全称 Board Support Package,板级支持包。你可以把它理解成——硬件和操作系统之间的翻译官。

举个例子。你写了一个 printf,想通过串口打印。如果没有 BSP,你得自己去翻芯片手册,找到 UART 的寄存器地址,配置波特率,写数据寄存器……累不累?

有了 BSP,这些底层操作都被封装好了。你只需要调用 xil_printf(),它自动帮你搞定一切。

BSP 的核心内容:

  • 驱动库:Xilinx 官方提供的驱动,比如 GPIO、UART、I2C、SPI、DDR 控制器等
  • 中断管理:GIC(通用中断控制器)的配置和注册
  • 内存管理:DDR 的初始化参数、MMU 页表配置
  • 时钟与复位:PLL 配置、时钟树初始化
  • 启动代码:C 运行环境的初始化(.bss 清零、.data 拷贝等)

我个人习惯把 BSP 看作一个「硬件抽象层」。它让你写应用代码时,不用关心底层寄存器长什么样。你想想看,如果每个项目都要重新写一遍 UART 驱动,那得多痛苦?

4.2 FSBL 的启动流程

FSBL,First Stage Boot Loader,第一阶段引导加载程序。它是芯片上电后执行的第一个用户程序。

等等,你可能会问:上电后不是先执行 BootROM 吗?没错,BootROM 是固化在芯片里的,它负责加载 FSBL。FSBL 再加载你的应用程序。所以叫「第一阶段」——它是用户可控的第一个阶段。

我在项目中遇到过一个问题:FSBL 卡在 DDR 初始化上,死活过不去。后来发现是硬件原理图上 DDR 的地址线接反了……嗯,这种坑踩过一次就记住了。

FSBL 的启动流程,我用一张图来展示:

FSBL 启动流程图 ① 上电复位 / BootROM ② 从 Flash 加载 FSBL ③ FSBL 执行初始化 PLL → DDR → MIO/EMIO → 中断控制器 ④ 加载应用程序到 DDR ⑤ 跳转到应用程序入口 注:FSBL 执行完毕后,控制权移交给用户应用程序

流程其实不复杂,我拆开来讲:

  1. 上电复位:芯片内部的 BootROM 开始执行。它会根据启动模式引脚(比如 QSPI、SD、NAND)去读取 FSBL 镜像。
  2. 加载 FSBL:BootROM 把 FSBL 从 Flash 拷贝到 OCM(片上内存,On-Chip Memory)中。OCM 只有 256KB 左右,所以 FSBL 不能太大。
  3. FSBL 初始化:这是关键步骤。FSBL 会依次做这些事:
    • 配置 PLL,把 CPU 和总线时钟提上来
    • 初始化 DDR 控制器,让内存可用
    • 配置 MIO/EMIO,把外设引脚连好
    • 初始化 GIC,准备好中断系统
    • 设置 MMU 页表(如果使能了缓存)
  4. 加载应用程序:FSBL 从 Flash 中读取你的应用程序(ELF 或 BIN 文件),放到 DDR 的指定地址。
  5. 跳转执行:最后,FSBL 把 PC 指针指向应用程序的入口地址,然后——你的代码开始跑起来了。

小技巧:如果你用 Vitis 调试,可以在 FSBL 里加打印信息,观察每一步的执行时间。我曾经用这种方法定位过一个 DDR 时序问题——打印显示 DDR 初始化花了 200ms,明显偏长,后来发现是时钟配置错了。

4.3 如何配置 BSP?

配置 BSP 其实就是在 Vitis 里点几下鼠标,但背后的原理你得懂。

在 Vitis 中,BSP 的配置主要通过 Board Support Package Settings 界面完成。我习惯的步骤是这样的:

4.3.1 创建 BSP

在 Vitis 工程中,右键点击你的平台工程 → Board Support Package Settings。你会看到这个界面:

配置项 说明 我的建议
OS Type 选择操作系统类型 裸机选 standalone,Linux 选 linux
CPU 选择目标处理器 Zynq 选 ps7_cortexa9_0
stdin/stdout 标准输入输出设备 一般选 ps7_uart_1
Heap Size 堆大小 默认 64KB,如果用到 malloc 建议加大
Stack Size 栈大小 默认 1KB,多任务时建议 4KB 以上

4.3.2 添加驱动库

在 Drivers 选项卡里,你可以看到所有外设的驱动。默认情况下,Vitis 会根据硬件设计自动勾选需要的驱动。但有时候它也会漏掉——

我记得有一次,我用了 AXI GPIO,但 Vitis 没自动添加驱动。结果编译报错说找不到 xgpiops.h。后来手动勾上就好了。所以我的习惯是:每次生成 BSP 后,都去 Drivers 页面扫一眼,看看有没有遗漏。

常用驱动库列表:

  • xilffs:文件系统支持(读写 SD 卡、QSPI Flash 时需要)
  • xilrsa:加密库(安全启动时需要)
  • lwip211:轻量级 TCP/IP 协议栈(网络应用时需要)
  • freertos10_xilinx:FreeRTOS 实时操作系统

4.3.3 修改 BSP 参数

有些驱动需要额外配置参数。比如:

  • UART 驱动:可以修改波特率,默认 115200,我有时改成 921600 加快打印速度
  • QSPI 驱动:可以配置 SPI 时钟频率,默认 50MHz,如果 Flash 支持可以改到 80MHz
  • DDR 驱动:可以修改内存大小,如果硬件上只用了 512MB,这里就设成 512MB

注意:修改 BSP 参数后,一定要重新编译 BSP。否则修改不会生效。我见过有人改了参数但忘了 rebuild,结果调试了半天发现还是旧配置……

4.3.4 手动修改 BSP 源码

有时候,图形界面配置不够用。比如你想修改 FSBL 的启动延时,或者自定义一些初始化代码。这时候就需要直接改源码了。

BSP 的源码在 <工程目录>/<平台名>/ps7_cortexa9_0/standalone_domain/bsp/ 下面。关键文件有:

  • ps7_init.c:PS 端初始化代码,包括 PLL、DDR、MIO 配置
  • ps7_init.h:对应的头文件
  • xparameters.h:所有外设的基地址和参数定义
  • fsbl_hooks.c:FSBL 的钩子函数,可以在这里插入自定义代码

举个例子,如果你想在 FSBL 阶段点亮一个 LED,可以在 fsbl_hooks.c 的 FsblHookBeforeHandoff() 函数里加几行 GPIO 操作代码。这样在跳转到应用程序之前,LED 就会亮起来——算是一个直观的启动指示。

4.4 避坑指南

最后,分享几个我踩过的坑:

  • BSP 版本不匹配:Vitis 版本升级后,旧工程的 BSP 可能编译不过。我建议每次升级工具链后,重新生成 BSP,不要复用旧的。
  • FSBL 太大放不下:OCM 只有 256KB,如果 FSBL 编译出来超过这个大小,BootROM 加载会失败。解决办法是去掉不必要的驱动,或者优化代码。
  • DDR 初始化失败:最常见的原因是硬件设计问题,比如地址线接反、时序参数不对。我建议先用 Vitis 的 mem_test 例程验证 DDR 是否正常工作。
  • 中断配置遗漏:如果应用程序里用了中断,但 BSP 里没配 GIC,中断会一直挂起。检查 xparameters.h 里是否有 XPAR_SCUGIC_0_DEVICE_ID 的定义。

嗯,关于 BSP 和 FSBL,今天就聊这么多。这些东西看起来琐碎,但理解了之后,你会发现整个启动流程其实很清晰。下次遇到启动问题,你就知道该从哪里入手排查了。