FPGA 进阶 25:FPGA 上的嵌入式软件——裸机 BSP、设备树与 Linux 控制 PL
本文是
fpga/进阶层第 29 篇。Zynq 这类 SoC FPGA 一半在 PL 硬件、一半在 PS 软件。承接 Zynq 入门 和 DMA 数据交互,这篇讲裸机和嵌入式 Linux 两条软件路线,以及「软件怎么够到 PL 寄存器」的机制。
本文要回答的问题
- 裸机 BSP 是什么?怎么读写 PL 寄存器、配中断?
- 为什么寄存器指针必须加 volatile?
- Linux 设备树起什么作用?改 PL 为什么要同步设备树?
- 用户程序访问 PL 寄存器有哪几条路(/dev/mem、UIO、内核驱动)?
- 裸机和 Linux 怎么选?
一、两条软件路线
| 裸机 bare-metal | 嵌入式 Linux | |
|---|---|---|
| 环境 | Vitis 工程,BSP + 主循环/中断 | PetaLinux/内核 + 用户态程序 |
| 启动 | FSBL→elf | FSBL→U-Boot→内核→根文件系统 |
| 访问 PL | 直接 volatile 指针 MMIO | 驱动 ioremap / UIO / /dev/mem |
| 实时性 | 高、确定 | 需配置(PREEMPT_RT/核隔离) |
| 生态 | 无 OS、资源小 | 网络/文件系统/多任务/库齐全 |
| 适合 | 任务明确、低延迟控制 | 网络、存数据、复杂协议、交互 |
二、裸机:BSP + MMIO + 中断
Vitis 根据 Vivado 导出的硬件(.xsa)生成 BSP(板级支持包):含 xparameters.h(各 IP 基地址、中断号、时钟)、驱动库(XAxiDma、ScuGic 等)、启动代码。
#include "xparameters.h"
#include "xil_io.h"
#define MY_BASE XPAR_MYIP_0_S00_AXI_BASEADDR // 来自 Address Editor
#define REG_CTRL (*(volatile unsigned int*)(MY_BASE + 0x00))
#define REG_STAT (*(volatile unsigned int*)(MY_BASE + 0x04))
int main(void){
// ... 初始化 ScuGic 中断控制器、注册 DMA/用户中断 ...
REG_CTRL = 0x01; // 写寄存器启动 PL 硬件
while( (REG_STAT & 0x1)==0 ); // 轮询(或用中断等待)
// DMA 用 XAxiDma 库;buffer 记得 cache flush/invalidate(进阶23)
return 0;
}volatile必须加:寄存器不是普通内存、每次访问有硬件副作用,不加会被编译器优化删掉重复读写;- 中断用 BSP 的 ScuGic 注册回调;DMA 用 XAxiDma 库。
三、嵌入式 Linux:设备树 + 驱动
设备树(device tree) 是运行时描述硬件的数据结构(.dts→.dtb 随内核加载):告诉内核「某地址有个什么 IP、中断号、用哪个驱动」。PL 加了/改了 IP、地址中断变了,设备树必须同步,否则内核看不到设备、驱动不绑定。PetaLinux 能自动生成部分,自定义 IP 节点常手写:
myip@43c00000 {
compatible = "mycompany,myip-1.0";
reg = <0x43c00000 0x1000>; // 基地址 + 跨度
interrupts = <0 61 4>; // 中断号
};用户程序访问 PL 的三条路:

/dev/mem+ mmap:用户态 mmap PL 物理地址直接读写——最快验证,权限大、不处理中断;- UIO(Userspace I/O):设备树标
uio兼容,框架把寄存器 mmap 到用户态、中断经/dev/uioX通知——用户态处理寄存器+中断,轻量驱动捷径; - 正式 platform 驱动:内核态
ioremap、iowrite32/ioread32、request_irq、DMA 用dmaengine+dma_alloc_coherent,经字符设备/ioctl/sysfs 暴露——产品化走这条。
四、软硬件协同:改了硬件别忘了软件链路
PL 改动 → 重出 bitstream → 重新 Export Hardware(.xsa) → 裸机更新 BSP/xparameters、Linux 更新设备树,否则软件用的基地址/中断号和实际硬件对不上——这是 Zynq 最常见的「改了没效果/访问挂死」根因。
常见坑对照
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 寄存器指针没加 volatile | 读写被编译器优化 | 宏定义加 volatile |
| Linux 改了 PL 没更新设备树 | 内核看不到设备 | .dts 加/改节点 |
| 用户态直接用物理地址 | 访问不到/段错误 | mmap /dev/mem 或走 UIO/驱动 |
| DMA buffer 用普通内存 | cache 不一致 | dma_alloc_coherent(进阶23) |
| 改 PL 没重导出 xsa | BSP/设备树地址中断错 | 出 bitstream→导出→更新软件 |
| Linux 强求硬实时 | 延迟抖动 | PREEMPT_RT/核隔离,实时放 PL/裸机核 |
| 照抄 demo 基地址 | 访问错地址挂死 | 以本设计 xparameters/Address Editor 为准 |
相关与延伸
前置/配套:Zynq 入门(地址/启动)、DMA 数据交互(cache 一致性)、块设计 IPI(地址来源)、AXI-Lite 寄存器。 FPGA 设备驱动与 OS、软核处理器深水区见专家层。
一句话总结
Zynq 软件两条路:裸机和 Linux。 裸机用 Vitis 按硬件导出(.xsa)生成 BSP(xparameters.h 给基地址/中断号),主循环/中断里把 PL 寄存器当 volatile 指针 MMIO 读写(volatile 防优化、DMA 记得 cache flush/invalidate),简单确定、适合实时控制。Linux 下内核靠设备树(.dts→.dtb) 知道 PL 挂了哪些 IP、基地址/中断号/驱动——改了 PL 必须同步设备树;用户程序经 /dev/mem+mmap(快速验证)、UIO(轻量、中断到用户态)或正式 platform 驱动(ioremap/iowrite32/request_irq/dmaengine,产品化)访问 PL。选型:任务明确低延迟→裸机;要网络/文件系统/多任务→Linux(硬实时仍放 PL 或裸机核)。铁律:改 PL → 重出 bitstream → 重导出 xsa → 更新 BSP/设备树,否则软件地址中断对不上。