ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA软核最小系统:MicroBlaze+IIC+UART+LED完整搭建指南

FPGA软核最小系统:MicroBlaze+IIC+UART+LED完整搭建指南 简介面向FPGA开发者的完整MicroBlaze软核处理器工程源码包基于Vivado 2021.1与Vitis 2021.1构建。工程实现了AXI兼容IIC控制器用于IMX327图像传感器通信、UART控制器用于状态监控与调试以及LED状态指示控制是FPGA嵌入式系统设计、I2C外设驱动及软硬件协同开发的实战参考。压缩包共1976个文件约75.17MB涵盖C/H源码294个h、165个c、XDC约束、XCI IP核、VHDL/Verilog设计文件、Vitis工程文件及bit/elf固件等结构完整覆盖从硬件配置到软件编程的完整流程。已有581人学习下载。通过该工程读者可掌握MicroBlaze处理器搭建、AXI总线接口IP封装、I2C时序控制、UART调试及GPIO控制LED的完整设计方法快速上手Xilinx FPGA软核开发。 这个标题看着不大起眼但实际做起来就是一套“FPGA软核最小系统”的标准骨架在Vivado 2021.1里搭好MicroBlaze硬件工程把IIC控制器、UART控制器、LED控制这三个外设通过AXI总线挂到CPU上生成比特流后再到Vitis 2021.1里编写C代码把外设驱动起来。做完这一套基本等于把Xilinx嵌入式开发的完整链路走通了一半。这篇就当是给后来者的一份“从零复现笔记”照着做能少踩不少坑。我默认你已经装好了Vivado和Vitis 2021.1而且对Verilog和C语言都有最基础的了解。如果你连Vivado安装都还在卡license建议先把环境折腾明白再回来看这篇因为这里的每一步操作都是建立在工具链能正常干活的前提上的。1. 整体设计与思路拆解1.1 为什么要用MicroBlaze而不是硬核ARM很多初学者一上来就会问既然Zynq有硬核ARM为什么还要折腾MicroBlaze这种软核我个人的理解是MicroBlaze最大的优势在于“裁剪自由”。想用几个外设就挂几个外设不需要的外设一概不生成占用的LUT和BRAM可以压得很低。而Zynq这种硬核SoC上电第一步就要跑FSBL、配置DDR、加载U-Boot流程重得多。对于IIC扫个传感器、UART打个日志、GPIO闪个灯这类轻量应用MicroBlaze反而是最合适的选择。而且MicroBlaze有个特别实用的点它的外设接口全部走AXI总线所以你在硬件工程里加IP就像搭积木。今天想多加一路PWM在Block Design里拖一个AXI Timer进来连好时钟和中断地址空间里就会自动分配一段。软件里直接查地址表就能操作寄存器组整个开发模式高度统一非常“不费脑子”。1.2 IP选型IIC、UART、LED分别用哪个核Vivado IP Catalog里有很多名字看起来差不多的外设选错能把你折磨到怀疑人生这里直接说结论IIC控制器选AXI IIC Bus Interface它的IP核名字就是axi_iic。这个核支持主机模式也支持从机模式我们通常只用主机模式去读传感器或者EEPROM。它的寄存器接口是AXI从口内部帮你完成了IIC协议的时序生成软件里只需要往寄存器写数据、读状态就行不用自己用GPIO去模拟IIC波形。UART控制器选AXI UART Lite或者AXI UART16550。我建议用AXI UART Lite因为它的寄存器结构简单没有FIFO深度的概念对日志打印这种低频场景完全够用。UART16550虽然兼容老PC串口但配置项多留着以后想折腾驱动时再说。LED控制这个不用单独造轮子直接拖一个AXI GPIO把GPIO配置成All Outputs数据宽度设为4后续把输出引脚连到板卡上的LED灯就行。只要框图里能看到gpio_rtl_0_tri_o[3:0]这样的端口说明生成成功。这三个IP选完以后你就会发现它们有一个共同点都是AXI从设备。Master就一个——MicroBlaze处理器。这样整个系统的控制流非常清晰CPU一根总线管所有外设。1.3 存储和时钟策略MicroBlaze需要一个地方跑代码。最简单的做法是直接用BRAM做一个“片上存储器控制器”Local Memory Bus把代码放在FPGA内部的BRAM里。这个方案的优势是流程短不依赖DDR颗粒上电就能跑。缺点是BRAM容量有限适合程序不大的情况。如果你后面要跑到带操作系统甚至LwIP协议栈的规模建议还是扩DDR但入门阶段用BRAM完全够。时钟方面如果你的板卡是单端时钟输入比如50MHz或者100MHz的晶振进去以后先用Clocking Wizard生成MicroBlaze所需的时钟频率。我个人习惯给MicroBlaze跑100MHzAXI外设也跑100MHz只要不跨异步时钟域调试会省心很多。IIC的速率是IP内部分频出来的跟总线时钟无关比如100MHz总线时钟下你也可以把IIC配成400kHz的快速模式。2. Vivado 2021.1硬件工程搭建的实操笔记2.1 创建工程和Block Design在第1.1、1.2节确定好方案后打开Vivado 2021.1新建RTL Project选择好芯片型号后在工程里新建一个Block Design名字我建议叫mb_system。接下来就是往这个Block Design里拖IPMicroBlaze (处理器核心)AXI IICAXI UART LiteAXI GPIO (接LED)AXI Interconnect (自动添加)Processor System Reset (复位管理)Clocking Wizard (时钟生成)这里有个小细节MicroBlaze添加完成后双击配置需要勾选Debug Module不然等会儿在Vitis里调试时没有调试接口。另外勾选Interrupt Controller可以让你后面接入IIC中断时更方便不勾的话只能轮询当然轮询也能用看个人取舍。2.2 IP连接和地址分配拖好IP以后不要急着连线先用Run Block Automation画面上方会出现一条黄色横幅让Vivado自动完成处理器侧的基础连接。这个操作会自动生成AXI Interconnect并连接时钟和复位网络非常省事。自动连接完成后手动把以下端口接出来axi_iic_0的iic_rtl接口右键Make External会生成iic_rtl_0端口axi_uartlite_0的uart端口同样Make External生成uart_0端口axi_gpio_0的gpio_rtl端口Make External生成gpio_rtl_0端口Clocking Wizard的clk_in1外接时钟输入生成sys_clock端口Processor System Reset的ext_reset_in接一个外部复位输入生成reset端口连线完成后点击Address Editor标签页给每个AXI从设备分配地址。一般情况下Vivado会自动分配比如IIC分配到0x40800000UART分配到0x40600000GPIO分配到0x40000000。这些地址后面写C代码时要用到最好截图记下来。然后执行Validate Design这一步会检查IP连接、地址冲突等常见错误。2.3 综合、实现和生成比特流所有检查无误后右键Block Design -Generate Output Products然后等它生成IP的仿真文件和综合网表。接着右键Create HDL Wrapper选择Let Vivado manage wrapper and auto-update让Vivado自动为你的Block Design生成顶层Verilog文件。做完以上操作回到工程顶层直接点击Run Synthesis。综合完成后点Run Implementation实现完成后记得打开Implemented Design看一眼有没有严重时序问题。MicroBlaze系统100MHz跑几个简单的AXI外设正常情况下时序都很稳。最后Generate Bitstream生成完成以后不要关界面。到这里Vivado 2021.1那半边的工作就算完工了。最重要的一步来了点击File - Export Hardware勾选Include bitstream导出XSA文件。这个XSA把硬件描述和比特流打包在了一起是Vitis工程的地基。3. Vitis 2021.1软件开发的核心代码流程3.1 创建平台工程和应用工程启动Vitis 2021.1工作空间随便选一个干净的目录。在Xilinx - Create Platform Project里选择之前导出的XSA文件Vitis会自动生成一个包含硬件平台信息的工程。平台工程建立好以后再新建Application Project模板可以选Empty Application (C)编译环境默认就行。Vitis 2021.1的BSP会自动把MicroBlaze的BSP包生成好底层驱动库已经排好顺序我们在应用代码里只需要#include xparameters.h、#include xiic.h、#include xuartlite.h、#include xgpio.h这些头文件。xparameters.h里就是刚刚在Vivado里分配的地址和中断ID比如XPAR_AXI_IIC_0_BASEADDR这些宏。3.2 UART初始化先把日志通道打通调整个系统之前我强烈建议先把UART跑通。没有打印你后面IIC卡住的时候根本不知道卡在哪儿。UART Lite的初始化非常简单#include xuartlite.h #include xparameters.h XUartLite Uart; static char log_buf[256]; int uart_init(void) { int st XUartLite_Initialize(Uart, XPAR_AXI_UARTLITE_0_DEVICE_ID); if (st ! XST_SUCCESS) return -1; return XUartLite_SelfTest(Uart); }之后封装一个日志打印函数底层用XUartLite_Send逐个发字节void log_printf(const char *fmt, ...) { va_list arg; va_start(arg, fmt); int len vsnprintf(log_buf, sizeof(log_buf), fmt, arg); va_end(arg); XUartLite_Send(Uart, (u8 *)log_buf, len); }这里有一个坑XUartLite_Send默认是阻塞发送假如你设置的波特率跟终端软件对不上发出来全是乱码或者干脆卡死到Send函数里回不来。所以第一步先确认Vivado里UART Lite IP配置的波特率常用的就是1152008位数据无校验1位停止位。3.3 IIC控制器驱动以读取温度传感器为例IIC控制器的硬件逻辑相对复杂好在Xilinx官方驱动已经把接口封得很规整了。核心对象是XIic。初始化代码大致如下#include xiic.h #include xparameters.h XIic Iic; #define IIC_DEVICE_ID XPAR_IIC_0_DEVICE_ID #define IIC_FREQ 100000 // 100kHz int iic_init(void) { int st XIic_Initialize(Iic, IIC_DEVICE_ID); if (st ! XST_SUCCESS) return -1; return XIic_SetBusSpeed(Iic, IIC_FREQ); }这里要注意XIic_SetBusSpeed并不是强制的IP默认的时钟极性是标准模式速度一般默认100kHz不过明确设置一下总没有坏处。接下来读写一字节数据。例如读一个IIC传感器一般流程是先发送寄存器地址再开启读操作。int iic_read_reg(u8 dev_addr, u8 reg_addr, u8 *buf, int len) { u8 send_buf[2]; send_buf[0] reg_addr; int st XIic_Start(Iic); if (st ! XST_SUCCESS) return -1; st XIic_Send(Iic, dev_addr, send_buf, 1, XIIC_REPEATED_START); if (st ! 1) { XIic_Stop(Iic); return -1; } st XIic_Recv(Iic, dev_addr, buf, len, XIIC_STOP); if (st ! len) { XIic_Stop(Iic); return -2; } XIic_Stop(Iic); return 0; }有人可能有疑问XIic_Send的dev_addr是7位地址还是8位地址Xilinx的驱动传的是8位地址也就是把7位地址左移一位后再加上读写位。比如你设备手册上写着7位地址是0x48那么这里传(0x48 1)。如果你直接把7位地址塞进去驱动虽然不会报错但IIC总线上产生的地址肯定是错的设备根本不会ACK。这是我第一次用这个驱动时踩的最蠢的坑。3.4 GPIO控制LED把效果亮出来LED控制用AXI GPIO是最直观的。初始化如下#include xgpio.h XGpio Gpio; #define LED_DEVICE_ID XPAR_GPIO_0_DEVICE_ID #define LED_CHANNEL 1 int led_init(void) { return XGpio_Initialize(Gpio, LED_DEVICE_ID); }初始化完之后要想让某个灯亮或灭直接操作函数void led_write(u32 value) { XGpio_DiscreteWrite(Gpio, LED_CHANNEL, value); }真值表逻辑很简单但我在实际调试时经常忽略一个问题板卡的LED是高电平亮还是低电平亮。如果板卡原理图里LED是灌电流接法那就得把输出值取反否则写1灯灭写0灯亮看着很反直觉。拿到原理图先搜一下LED指示灯的接法比在代码里瞎猜快得多。3.5 主函数里的联动逻辑等到三块功能都通了以后可以在主循环里做一个完整的演示每100ms通过IIC读取一次传感器数值通过UART打印出来同时根据数值阈值翻转某个LED。主函数框架如下int main(void) { uart_init(); log_printf(-UART OK-\r\n); if (iic_init() ! 0) { log_printf(-IIC FAIL-\r\n); return -1; } log_printf(-IIC OK-\r\n); led_init(); led_write(0x0F); log_printf(-LED OK-\r\n); while (1) { u8 temp[2]; if (iic_read_reg(0x48, 0x00, temp, 2) 0) { int t (temp[0] 8 | temp[1]) 4; log_printf(temp %d\r\n, t); led_write((t 25) ? 0x0F : 0x00); } for (volatile int i 0; i 10000000; i); } return 0; }这段代码相当于一个完整的“数据采集-数据输出-状态显示”链路。调试时最怕的就是三个外设都有问题打印却看不到任何东西建议按照“UART - LED - IIC”的顺序逐个点亮。4. 常见问题与排查技巧实录我把实际调试中遇到的高频问题整理成了一张表后期你按照这张表去查能省掉大半时间现象可能原因解决思路上电后串口完全没输出UART Lite的IP波特率配置与终端不一致或XUartLite_Initialize传入的设备ID不对核对IP配置和xparameters.h中的设备ID用示波器量TX引脚串口输出乱码波特率偏差大常见于时钟频率配置错误检查Clocking Wizard输出时钟是否确实到达UART IPVivado里重新分配频率后重新生成XSAIIC读写超时或卡死IIC设备地址用了7位而不是8位总线上没有上拉电阻SCL/SDA接反打印设备地址确认左移一位测量上拉电平核对原理图LED有tri_o端口但软件写不亮忘记调用XGpio_InitializeGPIO通道号写错输出极性搞反初始化后先写0xFFFFFFFF观察确认极性再改Vitis工程编译时报设备ID不存在XSA没有更新或者应用工程没关联到最新平台工程重新Export HardwareRebuild平台工程最后Clean应用工程打断点调试MicroBlaze时一直进不了main可能是Debug Module没勾选或JTAG链路不稳定Vivado里勾选Debug Module并重新生成比特流检查JTAG连接其中IIC总线上拉电阻是新手最常见的坑。IIC标准规定SCL和SDA需要有上拉电阻一般1k到10k都能工作常见尝选用4.7k。如果直接用杜邦线接传感器板线比较长时还要注意寄生电容这时候上拉电阻取过大容易导致边沿太缓IIC时序就不符合协议了。轻则速率上不去重则设备根本无响应。你把温度传感器或者OLED通过20cm杜邦线接到FPGA开发板IIC传输出错的概率会直线上升这是物理层面的问题不是软件能解决的。还有一个典型的Vivado工程问题就是“Bitstream生成失败”。MicroBlaze系统里如果顶层例化有误常报的错误是[DRC NSTD-1] Unspecified I/O Standard。这种多半是sys_clock、reset这种外部端口没有绑定到物理引脚也没有设置IO Standard。解决方法是新建一个XDC约束文件把你需要外接的管脚一一对应绑好。比如set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [get_ports sys_clock] set_property -dict {PACKAGE_PIN C2 IOSTANDARD LVCMOS33} [get_ports reset] set_property -dict {PACKAGE_PIN D4 IOSTANDARD LVCMOS33} [get_ports {gpio_rtl_0_tri_o[0]}]这里的管脚号一定得对着你自己板子的原理图查不同开发板差别很大别有侥幸心理。5. 关于固化与工程备份的建议工程能在JTAG里跑起来只是第一步真正要脱离电脑运行就得谈固化。MicroBlaze的固化比Zynq硬核要简单不少因为它不需要FSBL直接把比特流和应用程序合并成一个文件烧写到SPI Flash或者QSPI Flash里就行。以常用的QSPI Flash为例流程是先在Vitis里点击Xilinx - Program Flash加载你应用编译出来的elf文件和硬件工程的bit文件生成BOOT_BIN时注意把BIF文件里的[bootloader]留空。因为MicroBlaze软核本身没有BootROM它的第一条指令就是放在Flash启动地址上的。烧写完成后设置板卡的启动模式引脚为Flash启动上电就能运行。这里有个容易忽略的点固化到Flash之后如果在Vitis里重新编译了应用程序需要重新生成新的BIN文件并再次烧写Flash编程工具默认不会自动合并你应用里的代码。我有一回就是更新了算法却忘了重新生成boot image改了半天串口输出没变化白折腾两个小时。另外工程的备份我建议别只存Vivado工程文件一定要把以下三样单独保留导出过的xsa文件、Vitis的elf文件、还有最终的bin文件。这三种文件分别对应了硬件描述、软件源码、部署镜像。理论上只要这三个文件还在即使换台电脑也能完整复现你的工程。特别是xsa文件没了它Vitis工程平台部分基本就废了。最后想再多说两句我个人在实际操作中的体会是MicroBlaze这套流程最反直觉的地方不在写C代码而在硬件和软件之间的“边界”。Vivado里改一个IP配置很多人以为重生成比特流就够了然后Vitis里反复F5还是看不到新设备其实是XSA没有重新导出平台工程也没有重新编译。这种细节问题如果不自己踩一次很难从文档中学到。如果你也是第一次上手这个组合建议刚开始不要追求一次搞定全部功能而是先写一个只打印“Hello MicroBlaze”的版本确认编译、下载、调试链路全通然后在这个基础上加GPIO、加UART、加IIC。一套套叠加每次保留一个可运行版本遇到问题回退起来也很快。等这套组合拳打熟了再去碰DDR、中断和DMA那些更复杂的外设你会明显感觉心里有底。本文还有配套的精品资源点击获取
返回列表