ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA BSP是什么?详解Zynq嵌入式开发中BSP的作用与使用方法

FPGA BSP是什么?详解Zynq嵌入式开发中BSP的作用与使用方法 FPGA开发圈子里有个现象挺有意思很多人能搞定复杂的RTL逻辑什么高速接口、图像算法、时序收敛个个都是好手但一碰到SoC处理器比如Zynq上的软件开发就开始头疼。尤其是刚接触Zynq的工程师往往卡在第一步——点灯和串口打印怎么都要折腾半天DDR初始化、时钟配置、MIO分配、中断控制器这些底层的东西光看寄存器手册就得看一星期。等你终于跑通了发现时间全花在“让板子活过来”上了真正的应用逻辑一行还没写。这时候就该聊聊FPGA Board Support PackagesBSP了。简单说BSP就是把“让板子活过来”这件事打包成了一套标准化的软件层启动代码、驱动库、中断管理、存储映射全给你配好了。你只需要关心应用逻辑不用再跟硬件寄存器死磕。这篇文章我就从BSP的组成、工作机制、使用方法、定制思路到真实项目中的价值一层层拆开讲清楚希望能帮你少走点弯路。1. 先搞清楚FPGA BSP盒子里究竟装了什么很多人对BSP的理解比较模糊以为它就是一堆驱动代码。实际上一套合格的FPGA BSP是一个完整的软件栈从芯片上电到应用main()函数执行中间所有的“脏活累活”都被它承包了。我拆开给你看。1.1 启动三板斧从复位到main()之间发生了什么FPGA SoC芯片上电后处理器核心并不是直接跳进你的应用程序。它要先完成一系列初始化动作这过程就像电脑开机要先经历BIOS自检一样。以Xilinx Zynq平台为例启动流程分三个阶段BootROM、FSBLFirst Stage Boot Loader、应用启动。BootROM是芯片出厂固化的不可修改它负责加载FSBL。FSBL是BSP里最核心的启动代码它干的事情包括初始化PS端的DDR内存控制器把内存时序参数配好配置Cortex-A9核的基本状态包括MMU、Cache、异常向量表初始化UART作为调试串口读入PL端的bitstream如果配置了PL逻辑的话把FPGA逻辑加载进去将应用程序代码从启动介质QSPI Flash、SD卡、JTAG等拷贝到DDR中最后跳转到应用程序的入口也就是main()之前的_start也就是说你写的main()函数能正常跑起来是因为FSBL在背后把整个系统的“地基”已经搭好了。没有BSP这些启动代码你得自己写工程量相当可怕。1.2 驱动库和xparameters.h硬件信息的“物理实验报告”BSP里另一块重要组成是外设驱动。Zynq-7000系列PS端集成了UART、SPI、I2C、GPIO、Ethernet、USB、SDIO、CAN等一大堆外设BSP给每一个外设都提供了C语言驱动接口。跟驱动配套的是一个叫做xparameters.h的头文件。这个文件特别有意思它是工具根据你的硬件配置自动生成的硬件参数“物理实验报告”。比如你DDR挂了多大内存、UART用的是哪个控制器、中断号是什么、外设寄存器基地址在哪里全都以宏定义的形式写在里面。举个例子你使用UART时驱动函数内部要通过XPAR_AXI_UART16550_0_BASEADDR这个宏来定位寄存器地址。这个宏就是BSP自动生成的写应用代码时直接调用XUartPs_Send()这类API就行根本不用关心具体寄存器地址是多少。1.3 中间件与文件系统不会有人想从头写TCP/IP栈BSP不只是底层的寄存器驱动它还包括一些中间件组件。比如lwIP协议栈、XilFFs文件系统、XilFPGA配置库等。这些组件是经过裁剪、适配到目标硬件平台的你可以直接拿来用。举个例子你想在Zynq上跑一个简单的HTTP服务器传统做法需要自己移植一个TCP/IP协议栈还要处理网卡驱动的适配。用BSP就简单多了在BSP配置界面里勾选lwIP库驱动和协议栈的连接已经被处理好你直接调用lwip_init()和socket()API就能开始写业务逻辑。我见过很多工程师自己移植uIP或者精简版lwIP折腾了两三个星期最终效果还不如直接用BSP自带的lwIP稳定。在项目节奏快的今天这种重复造轮子的做法真的不划算。1.4 BSP和硬件设计文件是配套的不是通用的需要注意一个关键点BSP不是从网上随便下载一个就能用的。它是跟特定的硬件设计也就是Vivado工程绑定的。你在Vivado里配置了哪种DDR颗粒、选了哪个UART控制器、PL端放了什么IP核导出的BSP就是针对这份硬件配置定制的。这有点像手机ROM和机型的对应关系——小米的MIUI刷到三星手机上肯定跑不起来。所以每次修改了Vivado硬件设计都需要重新导出XSAeXport Software Architecture文件再刷新一次BSP保证软件和硬件的信息是同步的。2. 为什么BSP能砍掉大量重复劳动底层细节的“四层拦截”理解了BSP的组成下一个问题是它凭什么能简化应用程序开发我的体会是BSP从四个层面帮应用开发人员拦截掉了繁琐的底层细节让注意力能集中在业务逻辑上。2.1 第一层启动代码把“上电仪式”接管了没有BSP的时候你写的每个应用程序都要在启动阶段重复实现DDR初始化、Cache配置、异常向量表设置。这些代码对不同项目来说是高度相似的但又会因为硬件改动而变化——DDR型号换了时序参数就要改标准C库的启动文件也要跟着调整。BSP把这种“上电仪式”固定成了一个标准流程。你不需要知道DDR的tRCD、tRP这些时序参数具体是怎么算出来的只需要在Vivado里选对DDR颗粒型号工具会在生成BSP时自动匹配对应的初始化序列。这就像去酒店入住前台已经帮你办好了房卡和WiFi密码你只需要拿着房卡直接进房间就行不用自己找前台签一堆协议。对于应用开发来说这种替你把琐碎前置工作全做完的模式价值极大。2.2 第二层驱动封装把寄存器操作变成函数调用FPGA工程师写硬件逻辑时习惯了对着寄存器手册逐位操作。但在SoC上做应用开发如果还是这种思路效率会非常低。BSP提供的驱动库把寄存器操作封装成了语义清晰的函数。以GPIO为例你想控制一个LED不用去翻UG585手册查GPIO_DATA寄存器在哪一位只需要XGpioPs_Write(Gpio, LED_CHANNEL, LED_PIN_MASK, 1); // 点亮 XGpioPs_Write(Gpio, LED_CHANNEL, LED_PIN_MASK, 0); // 熄灭这种封装带来的好处是代码可读性极高后期维护和迁移都方便。而且驱动内部已经处理了读-改-写等保护性操作应用层不需要担心会误伤其他寄存器位。2.3 第三层中断与DMA的联动被统一管理中断处理是BSP简化开发最明显的地方之一。一个典型的中断流程包含初始化中断控制器、注册中断处理函数、使能特定中断源、在中断服务程序中清标志位。这套流程在BSP里被抽象成了标准API。以Xilinx SDK/Vitis中的GIC通用中断控制器为例你只需要XScuGic_Config *IntcConfig XScuGic_LookupConfig(INTC_DEVICE_ID); XScuGic_CfgInitialize(Intc, IntcConfig, IntcConfig-CpuBaseAddress); XScuGic_Connect(Intc, INTC_GPIO_INTERRUPT_ID, Gpio_Intr_Handler, Gpio); XScuGic_Enable(Intc, INTC_GPIO_INTERRUPT_ID);三行代码就把一个外设中断接到CPU上了。没有BSP你得手动操作GIC的寄存器组配置优先级、触发方式、中断使能位每个环节都可能出差错。有了BSP这套流程就是一个函数接一个函数的事。DMA方面也一样。Xilinx的AXI DMA IP核有几十个寄存器要管理手册看起来头疼。BSP提供的XAxiDma_*系列API把DMA的传输发起、状态查询、回调通知都封装好了应用层用起来跟调用memcpy一样简单。2.4 第四层地址映射与内存布局自动对齐SoC系统里存在多个地址空间PS端内部RAM、DDR地址空间、PL端外设寄存器空间。如果一个外设挂在总线上它的地址映射到底是多少手工去查地址分配表很容易出错出了问题往往还不好排查。BSP的链接脚本linker script会自动根据硬件配置把代码段、数据段、堆栈区放到正确的位置同时把所有外设的基地址以宏定义形式暴露出来。应用开发时不用关心某个IP核被分配到了哪个地址范围因为这些在XSA生成BSP时已经统一对齐了。2.5 一个比喻BSP不是偷懒是把可变部分从应用里剥出去我经常把BSP比作一套精装修的房子。你可以直接在客厅放沙发、电视、茶几而不需要先考虑水电管线怎么走、承重墙能不能拆。BSP就是已经铺好地暖、走好水电的“精装交付”应用开发人员只需要摆家具、做软装。这个比喻的关键在于BSP并不是替你偷懒而是把硬件相关、平台相关的可变部分从应用代码中彻底剥离。这样你的应用代码保持相对纯净换一块不同型号的板子时只需要更换BSP应用层的调整成本被压缩到最小。3. 手把手跑通一次基于BSP的应用开发光说原理不落地等于白讲。下面我带大家走一遍基于Xilinx Vitis的完整开发流程从Vivado硬件导出到BSP应用跑通。这套流程是当前FPGA SoC开发的主流范式。3.1 在Vivado里确定硬件配置导出XSA第一步不是写代码而是在Vivado里把硬件设计确定下来。你需要在Block Design中添加PSProcessing System模块配置DDR型号、UART、时钟、GPIO以及你需要的PL端IP核。硬件设计编译完成后需要导出硬件配置文件。流程是File - Export Hardware - Include bitstream生成一个.xsa文件。这个XSA文件包含了完整的硬件信息是生成BSP的“图纸”。提示如果你用了PL端的自定义IP导出硬件时一定要勾选Include bitstream否则后面BSP里可能无法生成对应的驱动和地址映射。3.2 创建Platform工程让工具自动生成BSP拿到XSA文件后打开Vitis IDE新版叫Vitis Unified IDE老版本是Xilinx SDK创建一个Platform工程点击File - New - Platform Project选择XSA文件路径设置工程名称点击Finish这样工具会基于XSA文件自动生成一个完整的BSP包括FSBL、驱动库、链接脚本等。你几乎不需要做任何手工配置就拥有一套基础的BSP了。生成的BSP里ps7_cortexa9_0/libsrc目录下就是所有可用驱动库的源码。你可以在Vitis的BSP Editor里查看和管理这些库也可以直接在工程里引用它们。3.3 第一个应用串口输出Hello from BSPBSP生成后创建Application工程。在Vitis里Application工程会和Platform工程关联编译时自动链接BSP库。主程序只需要几十行代码就能实现一个带完整初始化的串口输出#include xil_printf.h #include xparameters.h #include xuartps.h #define UART_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID XUartPs Uart; int main(void) { XUartPs_Config *Config; Config XUartPs_LookupConfig(UART_DEVICE_ID); XUartPs_CfgInitialize(Uart, Config, Config-BaseAddress); XUartPs_SetBaudRate(Uart, 115200); xil_printf(Hello from BSP!\n); return 0; }不要小看这几行代码。背后BSP已经在启动阶段完成了DDR初始化、MMU配置、Cache使能、异常向量表设置。你没有写过一行汇编没有配过一个寄存器串口就能正常输出。这在非BSP时代是不可想象的。3.4 加一个PL端中断的完整体验接下来加上一个PL端IP核的中断处理这会让你更直观地感受到BSP的威力。假设你在Vivado里添加了一个AXI GPIO IP并连接到了PS的中断引脚上。代码只需要在main里增加中断初始化注册一个回调函数static void GpioHandler(void *CallbackRef) { XGpioPs *Gpio (XGpioPs *)CallbackRef; u32 Value XGpioPs_Read(Gpio, CHANNEL1); xil_printf(GPIO interrupt, value %d\n, Value); } void SetupInterrupt(void) { XScuGic_Config *Cfg XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID); XScuGic_CfgInitialize(Gic, Cfg, Cfg-CpuBaseAddress); XScuGic_Connect(Gic, XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR, (Xil_ExceptionHandler)GpioHandler, Gpio); XScuGic_Enable(Gic, XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR); Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, Gic); Xil_ExceptionEnable(); }注意XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR这个宏它是BSP根据Vivado中的连接关系自动生成的。如果靠手工查硬件手册确定这个中断号至少得花半天时间核对。BSP直接把答案给你了。4. 定制BSP不是所有板子都能开箱即用基础BSP能帮你快速起步但真实项目中很少有“开箱即用、什么都不用改”的情况。定制BSP是FPGA工程师的必修课这里面有不少经验之谈。4.1 用BSP Settings改参数而不改源码BSP的配置界面BSP Settings提供了一堆可调参数我强烈建议你优先在这里改配置而不是直接去修改生成的源码。原因很简单BSP重新生成时你手工改的源码会被覆盖。常见的配置项包括标准输入输出设备stdin/stdout指向哪个串口堆栈大小、堆大小启动模式选择调试信息输出等级是否启用Cache、MMU中断控制器优先级设置举个例子如果你的系统有多个UARTUART0接调试UART1接外部设备你可以在BSP Settings里把stdout指定为UART1这样所有的printf输出都会走UART1应用代码完全不用改。4.2 新增外设驱动和库BSP生成时默认只包含部分基础库你有时需要给BSP添加额外的库。在Vitis里右键点击BSP工程选择“Board Support Package Settings”在主界面里找到“lwip141”或“xilffs”之类的库打勾确认后工具会自动下载库源码并编译到BSP中。这里有个细节要注意添加lwIP库时会弹出芯片配置界面选AxiEthernet还是PS端GEM版本选哪个这些必须跟你的硬件设计一致。配置不一致时虽然能编译过但网络通信会跑不起来而且错误信息往往让人摸不着头脑。4.3 引入FreeRTOS等操作系统BSP也支持跟实时操作系统RTOS配合。Xilinx官方提供了FreeRTOS的移植版在BSP Settings里勾选freertos10_xilinx即可。引入FreeRTOS后BSP的启动代码会自动完成RTOS内核的初始化你在main()里只需要创建任务并启动调度器int main(void) { xTaskCreate(NetTask, net, 2048, NULL, 1, NULL); xTaskCreate(ControlTask, ctrl, 1024, NULL, 2, NULL); vTaskStartScheduler(); return 0; }驱动库也能跟FreeRTOS配合比如串口驱动可以注册中断回调在中断服务程序里通过xSemaphoreGiveFromISR通知任务。这些整合代码BSP框架里已经提供了示例基本不需要自己从头写。4.4 自定义IP的驱动接入很多项目会写自定义的PL端IP核这些IP核出现在BSP里有两个途径一是IP核本身带有软件驱动XML描述文件.tcl或.mdd工具在生成BSP时会自动识别并生成对应的驱动二是没有驱动描述的自定义IP需要你自己写寄存器级访问代码。第一种情况是理想状态。如果你的自定义IP是用Vivado IP Packager封装的并且写了驱动描述文件BSP就会自动给它生成一个C语言驱动框架你只需要在驱动模板里填充具体的寄存器读写逻辑。第二种情况也常见。我自己比较推荐的做法是在BSP里增加一个通用的AXI总线访问接口层用Xil_In32、Xil_Out32这组宏直接操作自定义IP的寄存器。#define MYIP_BASEADDR XPAR_MYIP_0_BASEADDR void MyIP_SetGain(u32 Gain) { Xil_Out32(MYIP_BASEADDR 0x00, Gain); } u32 MyIP_GetStatus(void) { return Xil_In32(MYIP_BASEADDR 0x04); }虽然这样不够优雅但确实是快速接入自定义IP的有效方法。4.5 手工裁剪BSP以减小镜像体积BSP生成的代码包含了所有可用的驱动库但你的应用可能只用到了其中极小一部分。在资源紧张的嵌入式系统里裁剪BSP可以显著减小可执行文件体积。具体做法是在BSP Settings里禁用未使用的库或者在编译器优化选项里开启Size优化。不过我的建议是裁剪需谨慎确认没用到再删否则后面加功能时又要重新开启库并重新编译来回折腾。5. 真实项目里BSP帮我解决的几件大事理论讲完了拿几个具体场景说说BSP在真实项目中的价值。这些场景是从我这几年的项目经历中总结出来的也跟FPGA社区里大家讨论的高频话题高度重合。5.1 高速接口PCIE和SRIO的岁差问题FPGA工程师打交道比较多的高速串行接口里PCIE和SRIO是两大难点。难点不在于PHY层怎么跑通而在于事务层的交互协议。PCIE接口在Zynq UltraScale平台用的是XDMA IP核BSP提供了对应的驱动支持DMA传输、中断处理、AXI接口映射。用BSP开发PCIE应用时主机CPU枚举设备、配置BAR空间、发起DMA读写这些操作都被封装成了清晰的函数接口。我记得一个项目里主机需要每秒从FPGA读取100MB数据。用BSP的XDMA驱动我只需要设置好环形描述符和缓冲区地址驱动负责维护DMA描述符链中断处理保证每次传输完成都能及时通知应用层。如果从零实现这个光描述符环管理就够写几百行代码的。5.2 数据采集链JESD204B这类转换器接口最近FPGA社区里JESD204B的讨论热度很高很多工程师在做ADC/DAC和JESD204B接口的调试。JESD204B不仅链路建立过程复杂涉及代码组同步、多通道对齐、确定性延迟而且和时钟芯片的配置密切相关。BSP在这类项目中的作用主要在于JESD204B IP核的驱动由BSP统一管理配置链路参数、检查状态寄存器、处理中断都通过标准API进行。而应用程序只需要关心从接收FIFO里取数、处理或者往发送FIFO里写数。没有BSP的话JESD204B IP核的M_axis接口数据怎么搬、状态寄存器怎么轮询、出错怎么恢复这些全要应用层自己处理。我见过有的工程师花了整整一周时间才把链路跑稳定而用了BSP的同事同样功能三天就搞定了差距就在这。5.3 存储与搬运DDR3和DMA的联动DDR3/DDR4控制器的初始化在BSP里是自动完成的但在Xilinx的SoC上更重要的价值在于DMA和DDR的协同。典型的图像处理项目里PL端摄像头数据通过AXI VDMA写入DDRPS端从DDR读出来做显示或者算法处理。BSP提供了AXI VDMA的驱动应用层只需要配置好帧缓冲区地址和帧格式VDMA会自动把一帧图像从PL搬到DDR里。跑过这类项目的工程师都知道图像数据的帧同步、地址对齐、行距设置这些细节很容易出错。BSP驱动把这些细节都封装好了应用代码里就是简单的XAxiVdma_StartParking()和XAxiVdma_GetFrameBufferAddr()这几个调用。5.4 网络通信lwIP栈如何快速跑起来网络功能几乎是SoC的标配。Zynq的PS端自带千兆以太网MACGEMBSP提供的lwIP库实现了完整的协议栈。在BSP上启动网络功能你只需要创建platform组件时勾选lwIP然后在应用里初始化lwip_init(); struct netif *Netif platform_setup_netif();平台层已经处理了PHY芯片的复位、MDIO配置、MAC地址设置、DMA描述符初始化。应用层可以直接使用标准socket API编写网络服务。这个感受特别深我早年在无OS的环境下实现过一个简易以太网通信光是处理ARP协议和MAC地址过滤就花了不少时间。用BSP的lwIP后同样的功能基本是“开箱即食”。5.5 从Demo到产品BSP的边界在哪里说了这么多BSP的好处也得说说它的边界。BSP能大大加快开发速度但产品最终能否稳定运行还需要理解它背后的机制。比如BSP的驱动默认配置偏向于“通用可用”而不是“性能最优”。如果你的应用需要极致吞吐量可能需要对驱动参数做调优甚至绕过驱动直接操作部分寄存器。BSP帮你省掉了写驱动的时间但不代表你可以完全不理解驱动的工作方式。我见过一个案例某个网络应用吞吐量上不去排查到最后发现是DMA描述符数量配置太少长链路高带宽下描述符不够用导致丢包。这个参数在BSP里可以通过配置修改但如果完全不理解DMA环形缓冲机制可能就一直卡在那里。6. BSP开发避坑手册这些年我踩过的坑最后这部分我整理了实践中最常遇到的坑。这些坑我基本都踩过写出来希望能帮你省掉几天的排查时间。6.1 版本不一致Vivado、Vitis、BSP必须三合一Xilinx工具链的版本兼容性问题是我踩过最多的坑。XSA文件是用Vivado 2020.1导出的但你用Vitis 2022.1去创建Platform大概率会报错或者生成不完整的BSP。这不是开发者操作失误的问题而是工具链本身对版本匹配要求极其严格。我的建议是整个团队统一使用同一套Vivado/Vitis版本组合不要混搭。升级工具链时最好重新生成一次XSA并重建整个Platform工程而不是直接打开旧工程。注意老版本的BSP工程直接在新版本Vitis里打开有时能编译过但运行行为可能异常。这种隐蔽问题最坑人。6.2 不要改BSP生成代码要改配置新手最容易犯的错误是发现BSP生成的代码有点小问题就直接在BSP源码里修改。比如在xuartps.c里加了一段延时或者在xscugic.c里改了一个寄存器的数值。这个做法看起来很直接但有个致命问题就是BSP重新生成时会用干净的官方代码覆盖你的修改而且这个过程往往悄无声息。你辛苦调好的代码可能在一次重新生成后就无影无踪了。正确做法是能通过BSP Settings改参数解决的就用配置解决配置覆盖不了的问题通过修改硬件设计解决只有极少数情况才需要自己写驱动扩展文件比如通过xil_add_library挂载自定义的驱动文件。6.3 standalone模式下Cache一致性的坑Zynq的BSP提供两种运行模式standalone裸机和Linux。在standalone模式下很多人会忽略Cache一致性问题。具体场景是PL端通过DMA往DDR里写了数据PS端CPU去读发现数据是旧的。这是因为CPU的D-Cache里缓存了旧数据而DMA直接写进了DDR两者不一致。解决办法是在读取DMA写入的数据前执行Cache失效操作Xil_DCacheInvalidateRange(BufferAddr, BufferSize);或者在配置DMA缓冲区时特意把缓冲区映射为不缓存区域。BSP提供了Xil_SetTlbAttributes()这样的接口来配置地址空间的缓存属性。我见过不少工程师在这个坑里栽跟头调试了很久才找到问题。这个问题的根源在于对MMU和Cache机制理解不够而BSP默认不会帮你处理这个因为DMA缓冲区的分配和使用主要取决于应用场景不是BSP能一概而论的。6.4 printf重定向到不同的串口BSP默认会把printf输出重定向到PS端的UART0。如果你用的是PL端的AXI UART而且想用printf输出调试信息光改BSP设置还不够。你需要做两件事一是确保应用代码里包含了xil_printf.h而不是标准库的stdio.h二是手动实现一个输出函数比如outbyte()把字符发送到AXI UART。我踩过这个坑时也困惑了很久明明串口都配置好了但printf就是不输出。后来搞清楚是因为xil_printf和printf的底层实现不一样前者是Xilinx的轻量级实现后者依赖标准库的_write系统调用。这个细节在官方文档里写得不显眼但卡住了不少人。6.5 BSP适合验证但不代表零成本上手最后说个有点反直觉的经验BSP虽然简化了开发但不代表可以零基础直接上手。想真正用好BSP还是需要理解一些底层概念——中断是怎么路由的Cache是什么MMU页表是干什么的至少得知道它们在系统里扮演什么角色。我的建议是新入职的FPGA工程师或者转行做SoC开发的同学先花时间把BSP生成的FSBL源码通读一遍不需要读懂每一行但至少要清楚启动的流程节点。再花点时间读一下xparameters.h看你的硬件配置如何映射成了这些宏定义。有了这个基础遇到问题时才不会两眼一抹黑。BSP驱动的代码有很强的参考价值。比如你想知道某个外设的时序要求可以直接看BSP里的驱动实现它比数据手册更贴近实际应用。我自己有很多硬件理解都是在读BSP驱动源码时反向补上的。最后说几句实在话每次拿到一块新的FPGA开发板我第一件事不是急着点灯或者跑Demo而是先花半小时把官方BSP或者自己生成的BSP在板子上跑通串口输出一个版本信息。这一步的顺利程度很大程度上决定了后续开发的顺畅程度。BSP跑得稳后面加功能就是水到渠成的事BSP跑起来都别扭那这个板子后面大概率要反复折腾。如果你正好在做一个新的FPGA SoC项目别急着抄起代码就写应用。把时间花在理解、配置、验证BSP上这个前期投入是性价比最高的。毕竟基础打牢了楼才能建得高。
返回列表