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 的启动流程,我用一张图来展示:
流程其实不复杂,我拆开来讲:
- 上电复位:芯片内部的 BootROM 开始执行。它会根据启动模式引脚(比如 QSPI、SD、NAND)去读取 FSBL 镜像。
- 加载 FSBL:BootROM 把 FSBL 从 Flash 拷贝到 OCM(片上内存,On-Chip Memory)中。OCM 只有 256KB 左右,所以 FSBL 不能太大。
- FSBL 初始化:这是关键步骤。FSBL 会依次做这些事:
- 配置 PLL,把 CPU 和总线时钟提上来
- 初始化 DDR 控制器,让内存可用
- 配置 MIO/EMIO,把外设引脚连好
- 初始化 GIC,准备好中断系统
- 设置 MMU 页表(如果使能了缓存)
- 加载应用程序:FSBL 从 Flash 中读取你的应用程序(ELF 或 BIN 文件),放到 DDR 的指定地址。
- 跳转执行:最后,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,今天就聊这么多。这些东西看起来琐碎,但理解了之后,你会发现整个启动流程其实很清晰。下次遇到启动问题,你就知道该从哪里入手排查了。