尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从近似0基础开始FPGA开发 -- part.5 Vitis工程搭建(Uart及GPIO)

从近似0基础开始FPGA开发 -- part.5 Vitis工程搭建(Uart及GPIO) Vitis工程的搭建接上文在准备好硬件.xsa文件后打开Vitis设置好嵌入式工程的存放路径。单击 Create Platform Project创建基于逻辑硬件平台的工程。在后续select hardware platform时添加准备好的.xsa文件。添加完成后可以在嵌入式工程页面看到对应的硬件平台。在硬件平台下可以看到对应工程下AXI总线上的各个逻辑模块的地址信息基地址和范围信息来自于Vivado设计时的Address Editor地址配置。在这里同样可以看到硬件工程下的全部IP模块信息XIlinx官方IP可以查询对应的寄存器偏移地址。右键硬件平台可以进行Build Project。该操作是利用 Vivado 导出的.xsa硬件描述文件Xilinx Support Archive来定义软件的运行环境随后生成对应的平台描述文件.xpfm并配置底层的编译工具。整个流程可以概括为导入硬件描述 - 生成平台抽象层 - 配置底层编译环境具体步骤如下加载 .xsa 文件Vitis 会读取由 Vivado 生成的.xsa文件其中包含了 FPGA 比特流、处理器 IP 以及自定义 IP 的寄存器地址映射。生成 .xpfm 平台文件构建过程中Vitis 会根据.xsa自动生成.xpfm格式的平台文件其中定义了该硬件平台可用的外设、时钟和内存布局供后续的应用工程引用。配置编译工具链与 BSPVitis 会针对平台中指定的处理器如 MicroBlaze 或 ARM Cortex-A调用底层的 GCC 编译器并生成对应的板级支持包BSP如库文件头文件 (xparameters.h) 以及必要的初始化文件如 FSBL。在Build Project完成后可以查看BSP文件。BSP文件的设置可以看到在BSP界面Vitis提供了官方模块的一些嵌入式例程供开发参考。在Modify BSP Setting下可以选择不同的参考库文件。以下图为例在没有勾选lwip依赖库的情况下是无法进行PS侧的网络通信开发的。切换到standalone子页这里的stdin和stdout为比较关键的配置项选择正确的、与物理连接相符的串口才能看到正常打印。下图中因为逻辑工程中只有一个usrt模块所以不存在选择问题。如果是ZYNQ架构的开发板同时板卡引出了PS-Uart及PL-Uart两组资源那么在这里选则了PS的串口外部接线就需要接到PS Uart的物理接口反之同理。在修改了BSP相关设置后Vitis会rebuild工程。至此外部的依赖环境配置完成可以正式开始嵌入式开发了。Uart串口打印新建hello world工程通过菜单栏新建嵌入式工程。以起步来说最合适的就是Vitis提供的hello world例程。该例程在嵌入式代码层面非常简单更注重的是打通了嵌入式代码在PS侧运行并调用PL侧逻辑资源实现串口输出的流程。工程的添加导入按照Vitis指引流程即可选择hello world例程。导入完成后可以看到添加了相应的功能.c代码及依赖库文件。串口线连接在串口线已连接板卡上电后打开上位机的设备管理器可以查看对应的串口号信息。如果插拔串口线设备管理器的端口子栏下没有看到变化那么建议查看一下是否有标感叹号的无法识别的设备检查并安装相应的设备驱动。Vitis界面切换至Debug模式打开串口Terminal添加串口时选择设备管理器中看到的串口号。对应的波特率、位宽、校验码对照逻辑IP的配置参数进行设置。成功连接后会有打印信息提示下图是因为Vitis已经连上了对应串口所以提示Already connected。这里需要注意提示串口已连接并不能说明选择的串口就是正确的后续如果看不到打印信息建议复查以下串口号是否正确。程序运行Debug Configuration下选择需要运行的debug工程。切换到第二页Target Setup因为嵌入式程序运行时依赖逻辑bit流的在这里可以选择本次调试需要烧录的比特流。Reset entire system建议勾上尤其是在单一硬件平台上存在多个可执行嵌入式工程时如果不进行系统复位在未作地址隔离的情况下不同嵌入式工程运行时会访问相同存储空间的地址导致报错。Program FPGA也建议勾上这样可以确保在每次调试时底层逻辑处于初始、可控状态。串口输出打印Hello World。Generate Linker Script*右键嵌入式工程可以查看工程的内存地址配置因为在工程中使能了I-Cache与D-Cache并挂载了外部DDR存储设备这里可以看到两块独立的存储空间。当前嵌入式工程的指令及数据均缓存载Bram区域Bram资源十分有限当嵌入式工程上了体量之后就会空间不足。因此以hello world工程为例将嵌入式代码的缓存空间迁移至DDR中。首先可以提供Vivado Hardware Manager查看一下MIG的运行状态这是Xilinx自带的对外部存储的一个自测工具显示CAL PASS即状态正常。将linker script下的存储地址均改为DDR后重新生成Linker Script即可。可以看到现在嵌入式工程的相关文件全部缓存在DDR空间下了。这一过程对嵌入式工程的功能并无影响只是演示一下开放了I-Cache及D-Cache后Vitis下对外部缓存空间的使用。GPIO功能调试在做了基础的串口打印信息之后可以进一步了解一下PL与PS侧的交互。这里提供一个验证思路外部硬件的按钮提供开关输入信号输入信号数字0-1电平给到逻辑PL侧通过AXI总线供嵌入式PS侧读取访问而后嵌入式PS侧又可以通过AXI总线写逻辑侧PL侧的寄存器再将逻辑侧的电平输出变化给到LED。这一整套流程即包含了PL与PS的交互过程。PL侧工程准备首先需要准备好可供PS调用的PL侧逻辑资源在现有的SoC工程上添加GPIO IP。在做设计时对于功能明确的GPIO可以在配置时就定义好输入输出这样可以简化嵌入式部分的工作当前为了尽量全面的展示嵌入式部分先不定义接口数据方向。GPIO Width根据开发板的资源可以动态调整例如只有1组按钮-LED那就对应Width配置为1。Default Value定义逻辑初始化之后的默认值配置为0则对应的引脚在load bit之后输出0电压1则是满幅Bank电压Enable Interrupt 使能中断信号便于在嵌入式工程演示中断。配置完成后引出到外部引脚中断信号挂到MicroBlaze记得在Address Editor下给GPIO模块分配地址。添加约束完成后生成bit流导出到.xsa文件。PS侧工程准备代码同步上传了绑定资源文件职责axi_gpio_intr.h/.cGPIO 中断服务的核心初始化 GPIO、注册中断处理函数、中断服务例程ISRsys_xintr.h/.c中断控制器INTC的初始化、启动和异常注册封装axi_gpio_test.c主程序入口协调初始化并运行主循环响应中断标志更新输出1. 系统初始化流程main() └── init_intr_sys() ├── XInit_Intr_System() // 初始化中断控制器INTC ├── Gpiopl_init() // 初始化 GPIO │ ├── 方向设置通道1为输入按键通道2为输出LED │ └── 输出初值 0 ├── Gpiopl_Setup_Intr_System() // 配置中断 │ ├── XIntc_Connect() // 连接 ISR 到中断 ID │ ├── XIntc_Enable() // 使能 INTC 中断源 │ ├── XGpio_InterruptEnable() // 使能 GPIO 通道1中断 │ └── XGpio_InterruptGlobalEnable() // 使能 GPIO 全局中断 └── XSetup_Intr_Exception() // 启动 INTC、注册异常入口、开启全局中断关键点中断 IDGPIO_INTR_ID定义为 1对应硬件连接的中断号GPIO 通道1 为 2-bit 输入按键通道2为 2-bit 输出LED/控制信号中断触发方式由逻辑工程中AXI Interrupt Controller配置决定2. 中断服务程序ISR逻辑void GpioplIntrHandler(void *Callback) { XGpio *GpioPtr (XGpio *)Callback; u32 IrqStatus; u32 raw_val; gpio_isr_count;//中断计数自增调试用 IrqStatus XGpio_InterruptGetStatus(GpioPtr);//读取 GPIO 中断状态寄存器 if (IrqStatus 0x01)//若通道1中断触发Bit01 { raw_val XGpio_DiscreteRead(GpioPtr, 1) 0x03;//读取通道1原始输入值2-bit gpio_val (~raw_val);//取反后存入 gpio_val用于输出 gpio_intr_flag 1;//设置 gpio_intr_flag 1通知主循环 } /* * 只清除 Channel 1 */ XGpio_InterruptClear(GpioPtr, 0x01); }ISR 只做最小必要工作读状态、存数据、置标志不执行耗时操作如打印通过全局标志gpio_intr_flag与主循环同步确保数据一致性3. 主循环业务逻辑while(1) { if (gpio_intr_flag) { gpio_intr_flag 0; XGpio_DiscreteWrite(Gpio, 2, gpio_val); // 输出反相后的值 xil_printf(...); // 打印调试信息 } }响应模式中断触发 → 置标志 → 主循环检测标志 → 执行输出和打印 → 清标志输出值取反实现按键按下输入低电平→ 输出高电平LED亮的典型映射这里的具体亮灭关系取决于开发板硬件4. 关键全局变量说明变量作用gpio_intr_flag通知主循环有新中断数据gpio_val待输出的 2-bit 值按键反相gpio_isr_countISR 调用次数调试用程序运行参考hello world部分烧写bit流并运行嵌入式程序。按钮控制LED灯亮灭并在串口侧可以看到打印信息。关于printf与xil_printf*在 Vitis MicroBlaze 裸机开发中printf()和xil_printf()看起来都是串口打印但底层机制和资源开销差别很大。项目printf()xil_printf()来源C 标准库 / newlibXilinx/AMD BSP代码体积较大小RAM/栈开销较大较小格式化能力完整精简%d✅✅%u✅✅%x✅✅%f通常支持❌/不建议%s✅✅浮点格式化✅通常不支持多线程/锁可能涉及简单执行时间长短ISR 中使用不推荐也不推荐但风险相对小MicroBlaze 裸机调试不太推荐大量使用更推荐为什么printf()特别容易影响 MicroBlazeprintf(******)看起来只有一句代码但 CPU 实际做的事情远不止一条 UART 写操作。从printf() ── 参数解析 ── format string解析 ── %x处理 ── 整数转换 ── buffer处理 ── stdout处理 ├── _write() ── UART驱动 ── 字符一个一个发送需要执行大量代码。更关键的是printf()可能需要大量栈空间尤其是当linker script下.text → DDR .data → DDR .bss → DDR heap → DDR stack → DDR于是printf() 下大量的 stack 操作就会导致频繁的DDR访问这对于时序敏感的中断行为无疑是致命的。总之就是debug的时候适量使用xil_printf()至于printf()那就尽量不要用了。在进行GPIO按钮的功能测试时如果在gpio的入口信号添加ila进行debug可以发现有时在按下一次按钮后对应的GPIO输入电平会多次变化。这便引出了数字-模拟直接的转换概念下一篇准备通过按钮的消抖来进一步展开FPGA设计中时序的概念。Fin
返回列表