尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MicroBlaze软核串口实战:AXI UART Lite寄存器、中断与固化全解析

MicroBlaze软核串口实战:AXI UART Lite寄存器、中断与固化全解析 简介面向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_REGAXI UART Lite 的寄存器映射非常规律按 32 位地址对齐每个寄存器偏移固定。理解这张表之后后续写驱动或者排查问题基本可以脱离 SDK 也能干活。偏移名称读写作用0x00RXD只读接收数据寄存器读到数据后自动清除状态位0x04TXD只写发送数据寄存器写入即进入发送 FIFO0x08STAT_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 LiteBSP 的 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 或 125MHzIP 配置界面用的是主频分出来的频率而不是晶振原始频率。查完时钟再查 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_REGGIE、RIE 都要置位XUartLite_EnableInterrupt只做这一件事。第二层是 MicroBlaze 的中断控制器 XIntcXIntc_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_printf5. 把 UART Lite 固件固化进 FlashMicroBlaze 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 ImageBIF 文件里把 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 的写入地址这四处对不上任何一个上电就可能黑屏无声。本文还有配套的精品资源点击获取
返回列表