news 2026/9/9 17:28:52

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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跑100MHz,AXI外设也跑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里拖IP:

  • MicroBlaze (处理器核心)
  • AXI IIC
  • AXI UART Lite
  • AXI 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_0iic_rtl接口右键Make External,会生成iic_rtl_0端口
  • axi_uartlite_0uart端口同样Make External,生成uart_0端口
  • axi_gpio_0gpio_rtl端口Make External,生成gpio_rtl_0端口
  • Clocking Wizardclk_in1外接时钟输入,生成sys_clock端口
  • Processor System Resetext_reset_in接一个外部复位输入,生成reset端口

连线完成后,点击Address Editor标签页,给每个AXI从设备分配地址。一般情况下Vivado会自动分配,比如IIC分配到0x40800000,UART分配到0x40600000,GPIO分配到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配置的波特率,常用的就是115200,8位数据,无校验,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_Senddev_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 IP;Vivado里重新分配频率后重新生成XSA
IIC读写超时或卡死IIC设备地址用了7位而不是8位;总线上没有上拉电阻;SCL/SDA接反打印设备地址,确认左移一位;测量上拉电平;核对原理图
LED有tri_o端口但软件写不亮忘记调用XGpio_Initialize;GPIO通道号写错;输出极性搞反初始化后先写0xFFFFFFFF观察,确认极性再改
Vitis工程编译时报设备ID不存在XSA没有更新,或者应用工程没关联到最新平台工程重新Export Hardware,Rebuild平台工程,最后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_clockreset这种外部端口没有绑定到物理引脚,也没有设置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那些更复杂的外设,你会明显感觉心里有底。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 17:28:31

蓝桥杯训练士兵题解:C语言实现贪心排序与前缀和优化

2024年蓝桥杯省赛A组有一道让人印象很深的题&#xff0c;叫“训练士兵”。如果你是用C语言参赛&#xff0c;这道题基本把排序、贪心、前缀和优化这几个省赛高频考点一锅端了&#xff0c;代码量不大&#xff0c;但思路一旦卡住&#xff0c;很容易绕进“模拟每一天”的坑里出不来…

作者头像 李华
网站建设 2026/9/9 17:27:42

Flutter鸿蒙多地址监听实战:http_multi_server与Socket桥接方案

先说个背景。去年我给一个开源的多设备调试工具做 OpenHarmony 适配时&#xff0c;遇到了一个非常具体但又特别磨人的问题&#xff1a;开发板同时连着公司 WiFi 和开着一个手机热点&#xff0c;Flutter 侧的控制面板需要局域网内其他电脑、手机都能访问。折腾了一圈发现&#x…

作者头像 李华
网站建设 2026/9/9 17:26:28

微信聊天记录导出指南:4步把对话存成3种格式到本地

微信聊天记录导出指南&#xff1a;4步把对话存成3种格式到本地 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeChatMs…

作者头像 李华
网站建设 2026/9/9 17:23:32

Video2X 使用指南:3步把480p视频放大到1080p并插帧

Video2X 使用指南&#xff1a;3步把480p视频放大到1080p并插帧 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/video2x …

作者头像 李华
网站建设 2026/9/9 17:23:11

n8n GitHub节点实战:从凭证配置到智能体自动操作仓库

做智能体做到一定阶段&#xff0c;你会发现一个共性难题&#xff1a;模型再聪明&#xff0c;也只能在对话里输出文字&#xff0c;没法真正去操作你手上的系统。尤其是开发类智能体&#xff0c;代码仓库、Issue、PR 这些天天要打交道的东西&#xff0c;如果 Agent 不能直接读写&…

作者头像 李华