ARTICLE DETAIL

资讯详情

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

Vivado中AXI Uartlite IP核从配置到调试的完整指南

Vivado中AXI Uartlite IP核从配置到调试的完整指南 在Zynq或纯FPGA项目里调串口很多人第一反应是自己写个UART收发模块或者拿现成的波特率发生器凑合一下。实际上如果系统里已经挂了AXI总线直接用Vivado自带的AXI Uartlite IP核是更省心的方案——它帮你把UART协议和AXI接口的适配全部封装好了你只需要在Block Design里拖出来、配置参数、连好引脚剩下的通信逻辑交给IP核处理。这篇文章我围绕AXI Uartlite的完整使用链路从选型理由、IP配置、硬件约束到驱动代码、线上排错把能踩的坑都摊开讲一遍适合正在用Vivado做嵌入式开发的工程师也适合刚接触AXI总线的同学拿来做入门参考。1. 为什么是AXI Uartlite轻量级串口IP的真实定位1.1 Uartlite、Uart16550、自己写UART模块怎么选很多新手项目组在选串口方案时习惯性打开百度或者翻老代码最后找了一个经典的VHDL的UART模块自己例化自己写AXI接口转换一套搞下来至少半天。而AXI Uartlite就是Xilinx官方提供的、挂在AXI总线上的串口控制器IP核它在硬件上实现了UART协议暴露出来的是AXI-Lite从接口软件通过读写寄存器就能完成收发。它的体积很小官方文档给的资源占用大概只有几百个LUT非常适合轻量级调试场景。对比一下常见的几种方案方案资源占用收发FIFO中断支持软件工作量适用场景AXI Uartlite低可配置通常16/32深度支持低有驱动库轻量级调试、控制指令传输Uart16550中高内置16/64深度支持低但有兼容复杂度传统PC串口、操作系统驱动自写UART模块可控自行实现容易出bug通常不完善高需自研波形和验证学习、特殊定制协议、无AXI总线AXI UART16550Xilinx也有中高深度较大支持较低需要兼容16550寄存器集从我自己的项目经验来说如果你只是给FPGA加一个调试口、跟PC串口助手通信、或者往Zynq PS端发一些控制命令AXI Uartlite是性价比最高的选择。它的寄存器接口简单驱动代码短出问题了排查范围也小。而Uart16550的优势在于兼容老牌驱动但如果不需要这种兼容16550带来的复杂度就是多余负担。1.2 适用场景和性能极限AXI Uartlite适合的波特率通常在9600到115200实际上最高可以到460800甚至更高但受限于IP内部时钟分频的精度高波特率时误差可能变大。一般工程上我们最常用115200数据位8位、无校验、1位停止位这个配置在几乎所有板卡和上位机工具里都是默认的。性能上限方面AXI Uartlite的收发FIFO深度默认是16字节发送的时候CPU写一次寄存器只能写一个字节所以连续发送大量数据时必须等待发送FIFO非满。如果FIFO深度不够高频发送会频繁被阻塞。这个IP合不合适关键在于你有没有大吞吐量需求。我之前做过一个采集系统需要以1ms为周期向PC发送512字节波形用Uartlite显然不合适——尽管115200波特率理论吞吐约11.5KB/s但CPU频繁查询发送状态寄存器会占用很大一部分处理时间。对于这种场景要么提高波特率并加深FIFO要么换USB-UART芯片走FIFO接口或者直接用AXI DMA自定义高速串口方案。所以我的建议是AXI Uartlite是拿来用的不是拿来扛吞吐的。它的定位就是控制面、调试面适合传输短小、低频的数据包比如传感器模数转换结果、指令交互、状态上报。搞清楚这个定位后面对寄存器配置和软件设计就不会跑偏。2. IP核配置界面里的几个关键选项以及它们各自的影响2.1 波特率、数据位、校验位最基础的设置在Vivado中从IP Catalog搜索“AXI Uartlite”并双击弹出的配置窗口有两大块Board Interface可以不选重点是IP Configuration下的参数。首先是Baud Rate这个参数决定了串口线速率。IP核内部会用一个计数器把系统时钟分频成波特率时钟所以系统时钟越高分频越准确。配置时有个隐含前提你需要把系统时钟引脚告诉IP在配置框里就是Input Clock Frequency (MHz)。很多人忽略这个字段结果波特率实际偏差很大。我实测过如果系统时钟填的是100MHz实际时钟频率差了500ppm波特率在115200时误差不大但如果填的数值跟实际差得太多比如系统时钟是125MHz你却填了100MHz那么波特率误差超过2%通信就容易出错。数据位和校验位按常规配置即可。Uartlite支持5到8位数据位可配置无校验、奇校验或偶校验。如果只是调试建议用8位无校验一是软件处理简单二是很多第三方上位机默认就是8N1。需要注意的是当你启用奇偶校验时接收端会多做一个校验位检查如果发来的数据中没有正确生成校验位IP核会标记帧错误接收数据可能被丢弃调试时会更麻烦。停止位在Uartlite里固定是1位没有可选项。这个不用纠结串口链路两边统一就行。2.2 FIFO深度与中断配置性能与资源的平衡FIFO深度选项有两个Receive FIFO Depth和Transmit FIFO Depth默认都是16。这个数值决定了收发缓冲区大小。浅FIFO的优点是寄存器访问延迟低占用资源少但缺点是软件响应速度跟不上时容易丢数据。我做过一个典型测试FIFO深度16、波特率115200、接收中断默认触发级别是1个字节就触发中断。这样每个字节进来都会产生中断CPU频繁进中断服务函数而中断进出栈消耗是很大的。如果改成深度32并且把触发级别设高一些比如收满8字节或者16字节才中断一次CPU负担会明显下降。Uartlite的接收FIFO触发级别可以通过寄存器配置IP配置窗口里的Receive FIFO Interrupt其实只是个包含关系具体的触发阈值可以在软件中设置ISR寄存器。这点在官方例程里没有讲透但实际工程中很有用。中断配置还有一个选项是Use Interrupt勾选框。如果勾选IP会输出一个interrupt信号在Block Design里需要连到Zynq PS的中断控制器或者纯FPGA的中断处理逻辑。如果不勾选就只能用轮询方式收发。Vivado IP配置界面里的中断相关字段通熟易懂但实际使用中我建议如果系统里没有多余中断资源就不勾直接用轮询。因为Uartlite的轮询效率足够应付绝大多数调试场景。3. 硬件连接与管脚约束从地址映射到物理引脚3.1 在Block Design里连接AXI接口当你把AXI Uartlite拖到Block Design第一件事是连接AXI接口。如果是Zynq-7000/MPSoCS_AXI接口需要连到PS端的M_AXI_GP或者通过AXI Interconnect转接。如果是纯FPGA比如Artix-7则需要由MicroBlaze或者其他AXI主设备连接。连接后IP会占用一段地址空间。在Address Editor里你需要给它分配一个地址范围。默认情况下Vivado会自动分配但为了调试方便我习惯把地址范围对齐比如给0x40000000开始的一段。这个地址后面写软件驱动时要用到特别是Verilog/VHDL例化时。RX和TX两个端口是串口信号连接到顶层后要约束到物理引脚。还有一个可选端口interrupt如果用到中断就把它连出来。有人漏看interrupt端口导致Block Design验证报错是因为没有连接到合适的端口或者悬空也不影响功能但如果连接了必须处理中断控制器。在纯FPGA项目里可以让它悬空软件用轮询。3.2 管脚约束模板与常见错误连接好Block Design并生成比特流之前需要顶层Port的管脚约束。下面是Artix-7板卡上串口引脚约束的一个示例set_property PACKAGE_PIN U18 [get_ports uart_rtl_0_rxd] set_property PACKAGE_PIN T18 [get_ports uart_rtl_0_txd] set_property IOSTANDARD LVCMOS33 [get_ports uart_rtl_0_rxd] set_property IOSTANDARD LVCMOS33 [get_ports uart_rtl_0_txd]其中uart_rtl_0_txd和uart_rtl_0_rxd是IP的TX、RX对应的顶层端口。有人总是把TX和RX接反经常出现发送有数据但接收端什么都收不到的情况。接这个约束之前一定对照板卡原理图确认方向FPGA的TX要连接外部UART芯片的RXFPGA的RX连接外部UART芯片的TX。另一个常见错误是没有约束IO标准。有些板载UART芯片用的不是LVCMOS33而是LVCMOS18或者LVDS电压不匹配会直接烧芯片或者通信异常。务必看原理图确认IO电平。如果使用的是Zynq的MIO/EMIO那么串口不太需要PL引脚约束而是通过EMIO映射到PL后把EMIO引脚配置到MIO或者专用bank。这块和Uartlite用法略有不同具体要看Zynq的PS UART还是PL的Uartlite——本文主要讲PL侧的Uartlite所以物理引脚约束是我们绕不开的一环。3.3 地址映射与读写寄存器的注意点AXI Uartlite寄存器映射表相当简洁官方文档里写得很清晰常用的有偏移地址寄存器名访问属性说明0x00RX FIFO只读读取接收到的数据0x04TX FIFO只写写入要发送的数据0x08Status寄存器只读bit0: TX FIFO full bit4: RX FIFO valid等0x0CControl寄存器读写使能收发、复位FIFO0x10中断使能寄存器读写使能/关闭各中断源0x14中断状态寄存器读写读取中断状态在纯FPGA或MicroBlaze中通过AXI总线读写这些寄存器即可。比如发送一个字节就需要先等待状态寄存器的发送FIFO不满bit0为0再往TX FIFO写数据。接收则是检查状态寄存器的bit4是否置1若为1则从RX FIFO读出。这里有一个容易出错的地方TX FIFO满标志的有效电平是1表示满。有些工程师从别的IP移植代码误以为0表示满结果发送死等。建议一上来先做一个最简单的寄存器读写回环直接把状态寄存器读出来打印到串口确认地址和映射是否正确。4. 软件驱动实测轮询、中断两种方式怎么灵活用4.1 BSP中Uartlite驱动API速览如果你用的是Zynq Vitis/SDK开发环境会自动生成Uartlite的BSP驱动。Xilinx提供了一组标准API最常见的几个XUartLite_Initialize(InstancePtr, DeviceId)初始化Uartlite实例需要传入硬件设备ID。XUartLite_Send(InstancePtr, DataBuffer, NumBytes)发送指定长度的数据。XUartLite_Recv(InstancePtr, DataBuffer, NumBytes)接收指定长度数据。XUartLite_ResetFifos(InstancePtr)复位收发FIFO。实际项目里我喜欢用低层寄存器操作因为标准API在多字节发送时经常有“半途被中断”的情况而且API内部有字节数计数一旦发送中途失败状态不好恢复。直接操作寄存器其实也不复杂核心代码很短。在使用Xilinx SDK/Vitis时需要确保BSP里勾选了xuartlite驱动。如果在生成BSP时忘记勾选就会出现链接错误提示找不到XUartLite_Initialize等函数解决方法是重新生成BSP并勾选外设驱动。4.2 轮询发送与接收最稳定的方式轮询方式最简单也是最容易排查问题的方式。发送函数核心流程如下#include xil_io.h #define UARTLITE_BASEADDR 0x40000000 void uart_send_byte(uint8_t ch) { // 等待发送FIFO不满status bit0 0 while ((Xil_In32(UARTLITE_BASEADDR 0x08) 0x01) ! 0); Xil_Out32(UARTLITE_BASEADDR 0x04, ch); } int uart_poll_recv_byte(uint8_t *ch) { uint32_t status Xil_In32(UARTLITE_BASEADDR 0x08); if (status 0x10) { // RX FIFO Valid *ch (uint8_t)Xil_In32(UARTLITE_BASEADDR 0x00); return 0; } return -1; }注意读RX FIFO时即使只有一个字节不要用32位变量直接接收然后截断最好强制类型转换。因为AXI总线读32位寄存器返回的数值高24位可能不定截断低8位才是有效数据。轮询接收循环如果写在主函数里会阻塞其他任务。一个改进是用一个定时器周期性轮询把接收到的数据放入一个环形缓冲区。我自己的项目就维护了一个256字节环形缓冲区主循环只查缓冲区有没有新数据这样发送和接收互不干扰。4.3 中断接收实测工程中最实用的模式如果数据量不大但需要实时响应更实用的方式是中断接收。这里有一个关键点Uartlite一个字节就会触发中断所以中断服务函数必须足够短只做取数据、放缓冲区、清中断不能在ISR里做耗时处理。中断初始化的常规流程#include xuartlite.h #include xscugic.h static XUartLite UartLite_Instance; static XScuGic Intc; void UartIntrHandler(void *CallBackRef) { // 读取状态 uint32_t isr Xil_In32(UARTLITE_BASEADDR 0x14); // 若接收事件 if (isr 0x01) { uint8_t rx_byte; while ((Xil_In32(UARTLITE_BASEADDR 0x08) 0x10) ! 0) { rx_byte (uint8_t)Xil_In32(UARTLITE_BASEADDR 0x00); // 放入buf越界需处理 } } // 清中断 Xil_Out32(UARTLITE_BASEADDR 0x14, isr); }在Zynq中Uartlite的中断引脚通常连接到GIC的某个IRQ_ID如61号。用Xilinx的驱动时很多人直接用快速中断示例但忽略了中断优先级配置。如果同时有多个中断源Uartlite中断优先级设得太高或者太低都会影响系统。我的实测经验是把Uartlite中断优先级设为中等偏低不至于阻塞定时器等高实时性中断同时又能及时取走串口数据。另一个常见的坑是中断状态寄存器必须在清中断前读取并保存所有有效位在ISR里通过写1来清中断。有些工程师不保存直接读一下然后写同一个值但是因为中断状态位是“写1清零”如果你在状态里写了无关的1可能会清除不该清的中断标志导致中断丢失。4.4 软仿真和硬件测试的数据对比我在写完驱动后通常先做纯逻辑仿真。Vivado的仿真库里自带Uartlite的仿真模型可以在Testbench里直接给RX线喂数据。有一个细节仿真模型对时序要求很精确时钟频率和波特率配置不匹配时仿真结果也会乱码。所以仿真前先确认IP配置里的Input Clock Frequency和Testbench里的clk频率一致。我在仿真时写过一段激励生成一个UART帧// 发送0xAA8N1波特率115200时钟100MHz reg rxd_line 1b1; // 空闲时为高起始位低电平然后8个数据位LSB first再停止位高 task send_byte(input [7:0] data); integer i; begin rxd_line 0; // start bit #8680; // 1/115200*1e9 ps ≈ 8680ns按100MHz时钟约为868个clk周期 for (i0; i8; ii1) begin rxd_line data[i]; #8680; end rxd_line 1; // stop bit #8680; end endtask注意这里时间单位需要根据仿真精度换算如果是ns8680ns就是约868个100MHz周期。如果配置成9600波特率这就要大得多所以仿真时间要够长很多人仿真没数据输出就是因为时间跑得不够。5. 实测中我踩过的坑和完整排查链路5.1 回环测试通过但接外部设备乱码问题出在哪第一个常见问题是把FPGA的TX直接和RX短接用串口助手自发自收数据正常但一接外部设备比如接PC的USB转串口收到的却是乱码。这种问题的排查链路先确认两边的波特率、数据位、停止位、校验是否完全一致。最容易的是停止位不一致。Uartlite固定1位停止位但上位机如果配成2位停止位虽然大多数情况下还能通信但高波特率时必然会出错。检查时钟频率配置与实际是否相符。通过外部示波器测量Uartlite TX引脚的波形测量一帧起始位和停止位之间的时间算出波特率。用串口助手的“HEX显示”看看接收到的数据是否有规律性错位。如果每个字节都相同但是错误比如0xAA变成0x55大概率是寄存器读写数据位顺序问题而不是波特率问题。检查FPGA的TX引脚是否接上拉电阻。有些UART芯片需要外部上拉如果上拉缺失信号在空闲时可能被拉低导致接收端误以为有起始位。检查地线。FPGA板卡和PC串口头之间没有共地或是地线接触不良会产生严重的比特误差。以上排查顺序我一般是从软件配置到硬件电气先快速排除最容易修正的配置项再考虑物理层。5.2 中断一直不触发中断控制器配置的排查顺序如果在Zynq里用Uartlite中断收数发现中断一直不来先别急着怀疑IP核大概率是中断控制器或者BSP配置有问题。我有一次整整查了一个晚上最后发现是XScuGic_Connect里的中断ID错了。排查顺序找对中断ID。在Vivado里查看Block Design的地址和中断连接确认Uartlite的interrupt输出连到GIC的哪个输入。通常PS端中断ID从61开始不等可以在Vitis的xparameters.h里找到XPAR_AXI_UARTLITE_0_INTERRUPT_MASK或者XPAR_AXI_UARTLITE_0_INTR_HANDLER相关定义但最直接的是用XScuGic_Config里的IntrId。我的习惯是通过函数调用获取IntcConfig XScuGic_LookupConfig(INTC_DEVICE_ID); XScuGic_CfgInitialize(Intc, IntcConfig, IntcConfig-CpuBaseAddress); XScuGic_Connect(Intc, UARTLITE_IRQ_ID, (Xil_ExceptionHandler)UartIntrHandler, UartLite; XScuGic_Enable(Intc, UARTLITE_IRQ_ID);使能Uartlite自身的中断源。在初始化中需要写中断使能寄存器0x10置位接收中断对应的bit。如果只连了GIC但没开Uartlite内部中断中断永远不来。确认全局中断使能。在Zynq的Cortex-A9里用Xil_ExceptionEnable()打开全局异常并且要确保Xil_ExceptionRegisterHandler注册了XScuGic_InterruptHandler。漏了这个中断就像掉进黑洞。用调试器手动置位中断状态寄存器。在Vitis里连接到开发板后用内存查看窗口直接往Uartlite的中断状态寄存器0x14写值模拟一次中断。如果这时GIC里的状态发生变化说明中断链路是通的问题在外部UART信号没有触发。如果GIC也没有反应那问题就在中断ID或者GIC配置上。5.3 与ILA联调时的注意事项调试时我习惯在Block Design里挂一个ILAIntegrated Logic Analyzer观察Uartlite的RX、TX、Status、interrupt等信号。用ILA时有一个暗坑ILA的采样时钟必须稳定而且频率不能太低。如果你用一个和Uartlite同源且频率较高的全局时钟作ILA采样时钟一般没问题。但如果用BUFG或者混合时钟而ILA的采样时钟频率低于串口信号变化频率采出来的波形可能完全对不上。比如波特率115200一个bit约8.68us采样时钟如果只有1MHz一个位周期只有不到9个采样点可以观察到但不够精准。我建议ILA采样时钟用50MHz以上这样能采到毛刺和异常翻转。另一个ILA的常见现象是在ILA里看到RX数据波形正确但软件里读出来不对。这通常是AXI接口时序的问题不是Uartlite本身的问题。排查时把AXI总线的读数据信号也放进ILA波形里比如读RX FIFO时观察通道的axi_araddr、axi_rdata在读到数据那一刻的状态确认软件访问地址偏移是否正确。如果只是硬件信号对而总线数据错大概率是地址映射配错了比如寄存器基地址设置成了别的IP的地址。6. 几个不一定写在文档里的调试建议6.1 用回环寄存器做快速自检AXI Uartlite没有一个专门的控制回环寄存器但可以通过外部将TX与RX短接来验证硬件链路。其实我更推荐在初始的硬件跑通测试里直接把FPGA的TX引脚和RX引脚在顶层模块里短接先不约束外部引脚用ILA触发观察。这样能快速区分是FPGA内部逻辑问题还是板级链路问题。如果内部短接后软件收发正常说明Uartlite配置和驱动没问题再解开导线检查外部接口。6.2 关于Vivado中文注释乱码和常见环境问题这个和Uartlite本身无关但我在用Vivado调试时经常遇到中文注释乱码。很多工程文件是用Notepad或VS Code保存为UTF-8而Vivado编辑器默认可能用操作系统区域的编码读取导致注释里的中文变成乱码。虽然不影响编译但看着很影响心情。解决办法是在Vivado的Settings - Text Editor里设置为UTF-8编码读取或者在文件头部加入编码声明。如果是已经乱码的文件尽量不要反复保存否则可能损坏源文件。6.3 驱动代码中加入超时机制不管轮询还是中断驱动的等待循环都必须有超时阈值。比如发送时while ((status 0x01) ! 0)可能导致死循环如果Uartlite时钟没跑起来或者FIFO一直满程序就卡死。我习惯加一个毫秒级超时计数器int uart_send_byte_timeout(uint8_t ch, uint32_t timeout_ms) { uint32_t tick 0; while (((Xil_In32(UARTLITE_BASEADDR 0x08) 0x01) ! 0) (tick timeout_ms)) { usleep(1000); // 或delay函数 tick; } if (tick timeout_ms) return -1; Xil_Out32(UARTLITE_BASEADDR 0x04, ch); return 0; }这种防御式编程在长时间运行的系统里非常重要。我曾经在一个设备上运行串口通信上位机意外断开后FPGA模块里发送FIFO被占满驱动死循环整个中断系统都被拖垮。加超时后即使外部设备掉线系统也能自动恢复。6.4 留意复位顺序Uartlite IP核有外部复位信号S_AXI_ARESETN这是AXI总线的复位低电平有效。在很多系统中AXI外设复位和处理器复位往往是同一个信号但时序上可能会有差异。如果复位释放得太早软件初始化时Uartlite内部状态还没准备好就会偶发出错。解决方案是在软件初始化的最开头调用一次XUartLite_ResetFifos()并加上至少1ms的延时确保硬件稳定。如果连这个都省了首字节丢失的奇偶问题会反复出现而且不好复现。6.5 用逻辑分析仪或者示波器验证波形而不是盲目改代码很多调试者一看到乱码就怀疑波特率配置改来改去其实最有效的方法是点开示波器看看TX引脚波形。正常一帧数据应该是一个低电平起始位紧跟8个数据位最后是停止位高电平。用示波器测量第一位的低电平持续时间反推出波特率波特率 1 / t_bit。如果t_bit偏差超过3%调整IP配置里的输入时钟频率或者改上位机波特率匹配。这个方法是万能的可以快速把问题定位在FPGA侧还是外部设备侧。我几乎每一次串口异常都会先测波形再决定下一步操作。个人体会最深的一点是AXI Uartlite虽然简单但再简单的模块在工程中都要按完整的产品规范来对待——时钟配置要验证FIFO深度要按包长设计软件要加超时硬件要检查电气特性。把基础模块吃透后续不管是用Axi IIC、AXI SPI还是其他AXI桥接IP都会少走很多弯路。最后分享一个小技巧如果你需要在纯FPGA项目里用一个软核比如MicroBlaze来管理串口但又不想引入完整的中断驱动可以只在MicroBlaze的AXI总线上挂Uartlite然后用定时器轮询访问串口代码量比完整BSP小很多出问题也好调试。这种“最简搭配”在量产项目的自检程序里非常实用既省资源又稳定。
返回列表