☰
MicroBlaze软核串口实战:AXI UART Lite寄存器、中断与固化全解析
2026/10/12 7:04:42 网站建设 项目流程

简介:面向FPGA嵌入式开发者的实用资料包,围绕Xilinx Microblaze软核处理器与UARTlite串行通信接口,系统讲解如何在FPGA中配置并启用UARTlite IP核、连接中断系统以及完成串口数据的收发与调试。压缩包为RAR格式,整体约25.81MB,适合有一定Verilog或FPGA基础、正在学习Microblaze或需要在项目中快速实现串行通信的工程师与学生。目前已有352人学习下载,内容聚焦UARTlite硬件配置、裸机及RTOS下驱动编写、中断处理程序设计、TeraTerm终端联调方法,并涵盖波特率调整与中断延迟优化等实用经验,可帮助读者从零搭建一套可运行的Microblaze串口通信方案,同时为嵌入式系统课程设计或工程开发提供参考。

1. MicroBlaze + UART Lite:一个 .rar 背后最常见的软核串口工程

如果你手里也躺着一个叫Microblaze_uartlite.rar的压缩包,大概率刚从某块 FPGA 开发板的资料里解压出来。这个文件名本身就在告诉你两件事:处理器是 Xilinx 的 MicroBlaze 软核,串口外设是 AXI UART Lite。MicroBlaze 在 FPGA 里以逻辑资源实现,启动后第一件要做的事多半是找个通道把调试信息打出来,UART Lite 就是为这个场景设计的极简串口 IP。

这套组合要解决的实际问题很具体:怎么让软核在 FPGA 上跑起来、串口怎么初始化、printf 怎么往串口吐字、断点挂不住时怎么靠打印定位问题,以及最后怎么把程序固化到 Flash 里实现上电自启。写这篇文章不依赖那个 .rar 包里的具体代码,而是把一套稳妥的从业方案完整讲清楚。适合刚开始接触 MicroBlaze 的嵌入式工程师,也适合已经从 Zynq 转过来、想快速理解软核串口和外设中断机制的老手。

2. 在连寄存器之前,先把 AXI UART Lite 的模型看清楚

2.1 UART Lite 与 UART Full 的差别:一个寄存器数量就解释清了选型

Xilinx 体系里有两个名字很像的串口 IP,一个是 AXI UART Lite,一个是 AXI UART Full。很多第一次做 MicroBlaze 的人会纠结选哪个,其实只要看一眼寄存器规模就明白了。UART Full 的寄存器有两页,支持 5 到 8 位数据位、1 到 2 位停止位、奇偶校验、自动流控 RTS/CTS,还能挂 DMA 通道,适合做通信协议和高速收发。UART Lite 则把这一切砍到只剩 4 个寄存器,数据格式固定在 8 位数据、1 位停止位、无校验,没有 DMA 接口。

我一般选 Lite 的条件很直接:串口只当调试口用、波特率固定不变、协议就是 8N1、不需要流控。调试口最关键的是稳定和简单,UART Lite 没有 DMA、没有流控协商,CPU 把字节写进发送 FIFO 就算完成,逻辑简单到出错概率很低。反过来,如果产品里要用串口跑 Modbus、要变帧长、要 RTS/CTS 控制,就别碰 Lite,直接上 UART Full。另外要注意,MicroBlaze BSP 里对两个 IP 的驱动截然不同,搜代码时看到XUartLite_和XUartPs_开头的函数,别混用。

2.2 AXI UART Lite 的 4 个寄存器:RXD、TXD、STAT_REG、CTRL_REG

AXI UART Lite 的寄存器映射非常规律,按 32 位地址对齐,每个寄存器偏移固定。理解这张表之后,后续写驱动或者排查问题基本可以脱离 SDK 也能干活。

偏移名称读写作用
0x00RXD只读接收数据寄存器,读到数据后自动清除状态位
0x04TXD只写发送数据寄存器,写入即进入发送 FIFO
0x08STAT_REG只读状态寄存器,标记收发 FIFO 状态与错误
0x0CCTRL_REG读写控制寄存器,负责中断使能和 FIFO 复位

STAT_REG 里最常用的几位是 bit0 的 RX Valid Data、bit3 的 TX FIFO Full,以及 bit4 的 Frame Error、bit5 的 Overrun Error。CTRL_REG 则是 bit6 全局中断使能 GIE、bit0 接收中断使能 RIE、bit1 发送中断使能 TIE,外加 bit5 复位接收 FIFO、bit4 复位发送 FIFO。这些位的具体编号以头文件xuartlite.h里的宏定义为准,但寄存器偏移是固定的,直接读地址也不会踩坑。

#define UARTLITE_BASEADDR XPAR_UARTLITE_0_BASEADDR #define UARTLITE_STAT_REG (UARTLITE_BASEADDR + 0x08) #define UARTLITE_RXD_REG (UARTLITE_BASEADDR + 0x00) u32 stat = Xil_In32(UARTLITE_STAT_REG); if (stat & 0x01) { u8 ch = (u8)Xil_In32(UARTLITE_RXD_REG); /* ch 就是刚收到的字节 */ }

这段代码用Xil_In32直接访问状态寄存器和接收寄存器,逻辑是轮询检查 bit0 是否为 1,为 1 说明接收 FIFO 里有有效数据,再读 RXD 取走字节。Xil_In32自带内存屏障,在 MicroBlaze 上访问 AXI 外设时推荐统一走这套接口,不要用普通指针解引用,避免编译器乱序优化把状态判断和读数据打成错误顺序。

2.3 时钟与波特率绑定:为什么 UART Lite 不能像 Full 那样运行时调速

UART Lite 的波特率在 Vivado 里配置 IP 时就确定了,IP 内部根据输入时钟算出一个整数分频值,然后在硬件里固化。软件层面没有任何寄存器能改波特率,这是它和 UART Full 最大的行为差异之一。很多人调试时发现串口助手怎么换波特率都收不到正常数据,原因往往不是代码,而是 IP 配置的默认值和板载晶振对不上。

实际选时钟时,我通常让 MicroBlaze 和 UART Lite 共用同一路时钟,避免 AXI 总线和串口不在同频域导致跨时钟域问题。IP 配置界面里填写目标波特率后,Vivado 会显示实际计算出的分频值,如果计算结果不是整数,波特率就会带误差。串口接收端一般能容忍 3% 左右的偏差,但两侧设备各承担一半时会更紧,所以超过 921600 波特率之后,建议用示波器实测 TXD 引脚的电平宽度确认真实波特率。

这套寄存器模型的理解方式,同样可以迁移到 microblaze iic、microblaze can 这类外设上。区别只是每个 IP 有自己的寄存器和中断位,但从 Block Design 连接、地址分配到驱动初始化的套路几乎完全一致。学会看外设寄存器基地址和状态位,比死记驱动 API 更抗版本升级。

3. 用 Vivado + Vitis 把 MicroBlaze UART Lite 的最小工程跑起来

3.1 Block Design 里连接 MicroBlaze 与 AXI UART Lite 的操作要点

在 Vivado 里搭最小系统的步骤固定,第一次做时照着顺序走不会漏线。先新建 RTL 工程,在 Block Design 里添加 MicroBlaze IP,这时会弹出 Run Block Automation,让它自动补上时钟、复位和 AXI 互联,不要手动去接这些。接着添加 AXI UART Lite IP,双击打开配置界面,把波特率改成板载串口需要的值,FIFO 深度默认 16 就够调试用。

UART Lite 的对外引脚需要手动处理,常见做法是点 UART 接口旁边的引脚,选择 Make External,这样 TXD 和 RXD 会暴露为顶层端口;如果开发板有板级约束文件,也可以直接连到 xdc 里定义的串口管脚。之后运行 Run Connection Automation,把 UART Lite 的 AXI 从接口挂到 MicroBlaze 的总线上,验证 Design、生成输出产物,最后 Generate Bitstream。这一步完成后再 File 下 Export Hardware,务必要勾选 Include bitstream,否则后面 Vitis 里没有可用硬件。

地址分配这一步很多人忽略,但相当关键。Vivado 会自动给 AXI UART Lite 分配一段地址,常见的是 0x40600000 或 0x42C00000,具体看 Block Design 里 Address Editor 的显示。这段地址最终会出现在 Vitis 生成的xparameters.h里,如果之后你手动改过外设连接顺序,地址可能会变,这时候要以重新导出的硬件为准。换成 microblaze iic、microblaze can 外设时流程完全相同,只是 IP 名字和寄存器不同。

3.2 驱动初始化与轮询收发:一段可以直接编译的最小 C 工程

Vitis 里新建 Application 工程时,BSP 已经自动包含xuartlite驱动。最小工程的初始化只需要两步:先查配置表,再做 CfgInitialize。直接用官方 API 写轮询 echo,代码量很少,但已经把初始化、状态查询、收发三个关键点全带出来了。

#include "xparameters.h" #include "xuartlite.h" #include "xil_printf.h" static XUartLite UartLite; int main(void) { XUartLite_Config *Cfg; int Status; Cfg = XUartLite_LookupConfig(XPAR_UARTLITE_0_DEVICE_ID); if (Cfg == NULL) { return -1; } Status = XUartLite_CfgInitialize(&UartLite, Cfg, Cfg->RegBaseAddr); if (Status != XST_SUCCESS) { return -1; } XUartLite_Reset(&UartLite); xil_printf("MicroBlaze UART Lite ready\r\n"); while (1) { if ((Xil_In32(UartLite.BaseAddress + 0x08) & 0x01) != 0) { u8 ch = (u8)Xil_In32(UartLite.BaseAddress + 0x00); while ((Xil_In32(UartLite.BaseAddress + 0x08) & 0x08) != 0) { /* 等待发送 FIFO 腾出空间 */ } Xil_Out32(UartLite.BaseAddress + 0x04, ch); } } }

XUartLite_LookupConfig的参数是设备 ID,值来自xparameters.h中的XPAR_UARTLITE_0_DEVICE_ID,它对应硬件导出的 UART Lite 实例。XUartLite_CfgInitialize第三个参数是寄存器基地址,直接传入配置结构体里的RegBaseAddr,不要自己硬编码。XUartLite_Reset会通过写控制寄存器复位两个 FIFO,上电后执行一次能清掉不确定的初始状态。轮询发送时读状态寄存器 bit3,为 1 表示发送 FIFO 满,继续死等;接收侧读 bit0,为 0 表示没有数据。这个循环没有任何超时保护,仅适合调试,产品代码要加超时计数。

3.3 把 printf 重定向到 UART Lite:BSP 的 stdout 设置是怎么生效的

MicroBlaze 的裸机 BSP 里,printf 系列函数的底层输出默认走一个叫 stdout 的抽象设备。这个设备指向哪个串口,在 Vitis 的 Board Support Package Settings 里配置,而不是在代码里指定。操作路径是:右键 platform 工程的 BSP,选择 Board Support Package Settings,找到 Standalone 库,把 stdout 下拉框改成 "uartlite_0" 或其他以你 IP 实例名命名的设备。改完保存并重新生成 BSP,之后再编译应用工程,printf 和 xil_printf 就会自动走串口。

如果你用的是xil_printf而不是标准printf,要注意它对浮点数的处理不同。xil_printf是 Xilinx 的轻量实现,不格式化浮点数,适合打印调试信息和十六进制数;标准 printf 功能完整,但串口打印大量数据时会因为等待发送 FIFO 拉低吞吐。调试阶段我推荐优先用xil_printf,等需要打印浮点测量结果时再切回 printf。还有一点,如果 BSP 里没有出现 stdout 的选项,说明当前 BSP 没检测到可用的串口 IP,回 Vivado 检查 UART Lite 是否真的连到了 MicroBlaze 总线。

3.4 中断方式收发:Handler 注册顺序和三处使能缺一不可

轮询在调试阶段够用,但一旦工程里同时跑定时器、GPIO 或网络协议栈,轮询串口会拖垮其他任务的实时性。UART Lite 的中断处理套路是先注册回调,再开外设中断,最后使能中断控制器。顺序错了最常见的现象是第一个字符能收,后面的全丢,或者中断一次之后再也不触发。

#include "xintc.h" #include "xuartlite.h" static XUartLite UartLite; static XIntc Intc; void RecvHandler(void *CallBackRef, unsigned int EventData) { XUartLite *p = (XUartLite *)CallBackRef; while ((Xil_In32(p->BaseAddress + 0x08) & 0x01) != 0) { u8 ch = (u8)Xil_In32(p->BaseAddress + 0x00); /* 把 ch 放入 ring buffer,或直接回显 */ Xil_Out32(p->BaseAddress + 0x04, ch); } } void SetupUartInterrupt(void) { XUartLite_SetRecvHandler(&UartLite, RecvHandler, &UartLite); XUartLite_EnableInterrupt(&UartLite); XIntc_Initialize(&Intc, XPAR_INTC_0_DEVICE_ID); XIntc_Connect(&Intc, XPAR_INTC_0_UARTLITE_0_INTERRUPT_INTR, XUartLite_InterruptHandler, &UartLite); XIntc_Start(&Intc, XIN_REAL_MODE); XIntc_Enable(&Intc, XPAR_INTC_0_UARTLITE_0_INTERRUPT_INTR); }

XUartLite_SetRecvHandler的第一个参数是实例指针,第二个是回调函数,第三个是透传给回调的引用参数,这里把实例指针传进去,回调里就能通过CallBackRef拿到基地址。XUartLite_EnableInterrupt打开的是 UART Lite 内部的中断输出,对应 CTRL_REG 里的 GIE 和 RIE 位。XIntc 这边的宏名XPAR_INTC_0_UARTLITE_0_INTERRUPT_INTR是按 IP 实例名生成的,如果工程里外设叫别的名字,这个宏要改成xparameters.h中实际存在的定义,最简单的检查办法是在代码里搜索INTC_0相关的宏。

4. 串口不出字?MicroBlaze UART Lite 排错清单

4.1 先对三样东西:基地址、设备 ID、中断号

UART Lite 完全不工作,第一件事不是看代码逻辑,而是核对xparameters.h里的宏和 Vivado Address Editor 里的值。基地址不一致的情况常发生在重新生成了硬件但忘了在 Vitis 里刷新 platform 工程。Vitis 里右键 platform,选择 Update Hardware Specification,重新指向最新的 xsa 文件,然后 Clean 后重新编译 BSP。

设备 ID 和中断号的问题更隐蔽,尤其工程里有多个串口时。比如 Block Design 里放了两个 AXI UART Lite,第二个实例的设备 ID 是 1,中断号宏带UARTLITE_1后缀,代码里如果全复制第一个实例的宏,编译不报错但运行时中断根本进不来。这类错误只能靠逐个比对宏名解决,没有捷径。

4.2 波特率误差:怀疑对象永远先排时钟,再排分频

乱码、首字符错、偶发丢字节,优先量 TXD 引脚的波特率实测值。常见做法是让程序循环发送0x55,这个字节的二进制是 01010101,示波器上能看到均匀的方波,数一下一个位的时间就知道实际波特率。误差偏大时回 Vivado 检查 UART Lite 的输入时钟,很多板载 FPGA 用的是 50MHz 或 100MHz 晶振,但 MicroBlaze 主频可能经过 MMCM/PLL 变成了 80MHz 或 125MHz,IP 配置界面用的是主频分出来的频率,而不是晶振原始频率。

查完时钟再查 IP 配置里的波特率枚举值,有些版本里下拉框给出的是预设值,比如 9600、19200、115200、460800 等,选自定义值时要确认它实际算出的分频因子是整数。数据格式默认 8N1 不用改,但串口助手里如果之前调过奇偶校验,也会造成看起来像波特率不对的乱码。接收端和发送端的误差预算各占一半,所以这里别只盯着 FPGA 侧,USB 转串口芯片的晶振误差也要算进去。

4.3 FIFO 状态与复位时序:发送全满、接收悬空怎么处理

发送永远卡在等待 TX FIFO 空,最常见原因是上一次发送的字节没发完就继续写,或者接收方向有硬件流控误解。UART Lite 没有流控引脚,但有些人的 TTL 转串口模块带了 CTS/RTS 自检功能,模块默认认为 FPGA 侧支持流控,结果收到数据后不回合法电平,导致模块不发数据。排查方法是摘掉所有多余的 DTR/DSR/CTS 连线,只留 TXD、RXD、GND 三根。

接收悬空表现为串口一直收到0xFF或乱码,因为 RXD 引脚悬空时电平不确定。外部串口线没接的时候,MicroBlaze 读到的接收 FIFO 可能有垃圾数据。开发板上如果串口座子带了三极管电平转换,这个问题不常见,但自己画的板子或者用杜邦线直接连引脚时要特别注意。

4.4 中断不触发的三层使能检查

中断方向出问题时,按三层去查。第一层是 UART Lite 的 CTRL_REG,GIE、RIE 都要置位,XUartLite_EnableInterrupt只做这一件事。第二层是 MicroBlaze 的中断控制器 XIntc,XIntc_Enable必须传入和中继控制器连接时一致的中断号。第三层是 MicroBlaze 处理器的全局中断使能,Vitis 的 standalone BSP 通常在启动代码里已打开,但如果你裁剪过 boot 流程,需要在 main 里调用microblaze_enable_interrupt()补上。

回调注册顺序也在这里体现。XUartLite_SetRecvHandler必须发生在中断真正到达之前,否则中断来了驱动层找不到回调,进了一次XUartLite_InterruptHandler但没有实际消费数据,状态位不被清除,后续中断全部丢失。调试时可以在回调入口放一个全局计数器,每进一次加一,用 xil_printf 打印这个值,就能确认中断到底有没有进到回调。

4.5 一张检查清单,覆盖 90% 的串口问题

症状最可能原因排查动作
完全无输出基地址或 stdout 配置错核对 xparameters.h,检查 BSP stdout
输出乱码波特率或时钟频率不符发送 0x55 示波器量位宽
首字符丢失发送前未等 TX FIFO 空发送前读 STAT_REG bit3
跑一会儿死掉接收 FIFO 溢出,错误位未清检查 OVER 位,及时读空 RXD
中断只触发一次回调里没读完,状态未清回调里循环读到 RX Valid 清零
RXD 悬空收到 FF引脚未接或外部模块问题只留 TXD/RXD/GND 三线
打印很慢标准 printf 浮点格式化改用 xil_printf

5. 把 UART Lite 固件固化进 Flash:MicroBlaze bootloop 与 QSPI 烧写

5.1 用 bootloop 组装 MicroBlaze 启动镜像

调试时用 JTAG 下载的方式只对开发过程有效,断电重来程序就没了。MicroBlaze 固化要走 QSPI Flash:先把 bitstream 和应用程序放在 Flash 里,上电后 FPGA 先加载 bitstream 配置逻辑,MicroBlaze 处理器从复位向量取指,这时需要一小段引导代码从 Flash 把应用程序搬运到可执行区域,或者直接在 Flash 里 XIP 执行,这段引导代码就是 bootloop。

Vitis 里创建新工程,模板选 MicroBlaze Bootloader 或 Bootloop,生成的 ELF 就是启动镜像的入口。然后在 Vitis 菜单里选 Xilinx 下的 Create Boot Image,BIF 文件里把 bootloop 指定为 bootloader,应用程序 ELF 作为 load 分区,bitstream 也作为独立分区打进去,生成 BOOT.BIN。这里要注意应用程序的 linker script 决定了它应该被加载到哪个地址,bootloop 里的搬运目标地址必须和 Linker Script 一致,最常见的问题是 app 链接到了 DDR 地址而板子上没有挂 DDR。

5.2 用 program_flash 烧写和上电验证

生成 BOOT.BIN 之后,打开 Vivado Hardware Manager 连接目标板,在设备上右键选择 Program Configuration Memory Device,也可以直接在 XSCT 命令行里执行等价操作:

program_flash -f ./BOOT.BIN -offset 0 \ -flash_type qspi-x4-single \ -cable type xilinx_tcf url tcp:127.0.0.1:3121

命令里的-offset表示写入 Flash 的起始地址,通常从 0 开始;-flash_type要匹配开发板上实际挂的 Flash 型号,这里qspi-x4-single是常见的四线单倍速率 QSPI 参数,如果你的板子是双倍速率或 x8 模式,这个参数要改成对应值。烧写完成后断电复位,观察 UART Lite 是否在上电约几百毫秒后打印出启动信息。如果没有任何输出,优先重新烧写并对比 Flash 里读出数据和 BOOT.BIN 的校验值,其次是确认 bootloop 的跳转地址没有被链接脚本改写。

固化后的验证有一个技巧:把 UART Lite 收到的数据再接回上位机,做成一个简单的串口自测程序,上电后循环回显收到的字节,同时周期性打印运行时长。这能在不接调试器的情况下快速确认 Flash 加载、时钟初始化、UART 外设和中断链路全部正常。MicroBlaze 的固化流程踩过一次坑后就会形成肌肉记忆,核心就是地址一致性:bitstream 的加载地址、bootloop 的跳转地址、应用程序的链接地址、program_flash 的写入地址,这四处对不上任何一个,上电就可能黑屏无声。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询