你天天调用的 XUartPs_SendByte,不是什么黑盒函数,就是一行往固定地址写字节的 C 代码。整个 BSP 的源码,全在这一个仓库里。
01为什么第二期写它
因为在 PYNQ 那层"魔法"之下,藏着的东西就是这个仓库。
第一篇讲 PYNQ 时我说过,它让你不用写 C。但只要你往下走一步——换块板子、加个中断、把某个外设初始化改快一点——你就必须回到 C 这一层。而这一层最大的问题不是难,是不透明。
Vitis 点几下鼠标,bsp 目录就出现了,几千行代码,目录还挺长。大部分人从此只敢改 helloworld.c,别的一概不碰。遇到"串口收不到数据""中断进不去""定时器不准",只能去论坛搜,搜到一段看不懂的代码贴进去试。
其实这套东西一点不神秘。它就是一个普通的 C 库:一堆驱动源文件、一张配置表、一组寄存器地址宏。你完全可以脱离 Vitis,用 make 加一个 arm-none-eabi-gcc 编出来。
02它到底是什么
https://github.com/Xilinx/embeddedsw
三个目录,分工非常清楚:
| 目录 | 放什么 | 你会不会碰 |
|---|---|---|
lib/bsp/standalone/src/ |
启动代码、异常向量、xil_printf、xil_io |
几乎不碰,排障必看 |
lib/sw_services/ |
独立软件服务:定时器、电源管理、mailbox | 偶尔看 |
XilinxProcessorIPLib/drivers/ |
每个 IP 一个目录的驱动 | 天天用,最该看 |
真正的主角是第三行。里面每个 IP 一个文件夹,比如 uartps(PS 端 UART)、scugic(中断控制器)、axidma(DMA)、emacps(以太网)、gpiops(PS 的 GPIO)。
每个驱动目录里,文件名几乎是固定的套路:
•xuartps.c —— 函数实现
•xuartps_hw.h —— 寄存器偏移和位定义
•xuartps_g.c —— 配置表:每个实例的基地址、时钟频率、ID
•xuartps_sinit.c —— 按 ID 查配置
•xuartps_intr.c —— 中断处理
看懂这五个文件的关系,整个 BSP 就通了。
03值得读的三处
▍xuartps_g.c —— 你要找的"那个地址"在这里
xparameters.h 里那些 XPAR_XUARTPS_0_BASEADDR 不是凭空来的,它们是从 _g.c 这张表里生成的。
以 Zynq-7000 为例,UART0 的基地址是 0xE0000000,UART1 是 0xE0001000。这两个数字来自《Zynq-7000 TRM》的 memory map,Vivado 生成 BSP 时把 PS 配置写进这张表,xparameters.h 再把表里的值变成宏。
所以下次你怀疑"地址对不对",别猜。打开 xuartps_g.c,看 XUartPs_ConfigTable[] 第一项的 BaseAddress,那就是真的。注意它是生成的,改了会被 Vitis 覆盖,要改配置请去改 .xsa。
▍xuartps_hw.h —— 寄存器级的所有细节
这里定义了 XUARTPS_FIFO_OFFSET、XUARTPS_SR_OFFSET、XUARTPS_CR_OFFSET 这些偏移量,还有每一位的含义,比如 XUARTPS_SR_TXEMPTY(发送 FIFO 空)、XUARTPS_CR_TX_EN(发送使能)。
你想知道"为什么我发了数据但串口助手没显示",从这里开始查最快:先看 SR 的 TXEMPTY 位起没起,再看 CR 的使能位。比在论坛问三轮快得多。
▍lib/bsp/standalone/src/ —— 上电后最先跑的那几百行
boot.S 建立栈和异常向量,xil-crt0.S 跳到 main。中间有个很多人不知道的环节:Xil_ExceptionInit() 必须在用中断前调用,否则中断一来就飞。
还有 xil_printf.c。它和标准 printf 不是一回事——不支持浮点、格式串支持很有限、缓冲区很小。你在裸机里 printf("%f", x) 输出一串乱码,不是串口坏了,是这个函数不支持。
04上手:脱离 Vitis 编一个裸机程序
这是我觉得最值得做的一次练习,做完你对 BSP 的敬畏会消失。
git clone https://github.com/Xilinx/embeddedsw
cd embeddedsw
git checkout v2023.2 # 和你的 Vivado 版本对齐
# 只需要三样:启动代码、UART 驱动、一个 main
arm-none-eabi-gcc -mcpu=cortex-a9 -mfpu=vfpv3
-I lib/bsp/standalone/src/arm/cortexa9
-I XilinxProcessorIPLib/drivers/uartps/src
main.c lib/bsp/standalone/src/arm/cortexa9/gcc/startup.S
XilinxProcessorIPLib/drivers/uartps/src/xuartps.c
XilinxProcessorIPLib/drivers/uartps/src/xuartps_g.c
-T lscript.ld -o demo.elf
编完 arm-none-eabi-objcopy -O binary demo.elf demo.bin,用 bootgen 打成 BOOT.BIN,丢进 SD 卡。
这条路跑通一次,你就再不会怕"BSP 生成失败"——里面根本没有魔法。
05三个坑
一、重新生成 BSP 会覆盖你的修改。所有改在 bsp/ 里的代码都是临时的。要定制,用 _config 宏,或者把驱动目录复制出来改。
二、xparameters.h 里的宏名带实例后缀。XPAR_XUARTPS_0_DEVICE_ID 和 XPAR_XUARTPS_1_DEVICE_ID 是两个不同实例,混用会打到错误的串口,而且不报错。
三、中断不生效,九成是这三个原因之一:GIC 没初始化(XScuGic_CfgInitialize 之前必须 XScuGic_LookupConfig)、中断优先级掩码没设、handler 里没清中断标志。第三条最坑——不清标志,中断只进一次。
06它不适合谁
不适合只想"学硬件"的人。
这个仓库是纯软件视角:它假设硬件已经配好了,只告诉你"怎么操作"。你要想搞明白 AXI 时序、时钟域、复位顺序,得去看 PL 那边的文档。
但如果你想知道"为什么板子行为和我的代码不一致",这里最快——最终执行你意图的,就是这几千行 C。
07写在最后
BSP 不是厂商给你的恩惠,它是你自己代码的一部分,只不过别人先写好了。
把它当源码读,而不是当黑盒用,你对整块芯片的掌控感会立刻不一样。
37
