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

资讯详情

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

Vivado AXI UART Lite IP核实战:FPGA串口通信高效配置与避坑指南

Vivado AXI UART Lite IP核实战:FPGA串口通信高效配置与避坑指南 1. 为什么我劝你别再手写UART收发逻辑了刚入行那会儿我也觉得手写一个UART收发模块是FPGA工程师的基本功。波特率分频、起始位检测、中间采样、停止位判断一套状态机写下来两三百行仿真波形调半天上板还得拿示波器抓时序。后来项目一多我发现一个残酷的事实手写UART除了感动自己几乎没有任何工程价值。它不产生差异化不提升系统性能纯粹是重复造轮子而且每次换波特率、换数据位宽都得重新算分频系数稍不留神就踩坑。真正让我彻底放弃手写UART的是Xilinx现AMD在Vivado里提供的AXI UART Lite IP核。这个IP核把UART收发逻辑、FIFO缓冲、寄存器接口全部封装好了你只需要在Block Design里拖一个出来配置几个参数连上AXI总线5分钟就能跑通串口通信。它支持标准的UART协议波特率可配置收发各带16字节FIFO还能产生中断。对于绝大多数FPGA项目来说——无论是调试打印、上位机通信、还是跟MCU做数据交互——这个IP核完全够用。这篇文章我想聊的不是怎么点鼠标而是为什么这么选、参数怎么算、坑在哪里、怎么排查。我会从方案选型讲到实操步骤再把我这些年踩过的坑整理成速查表。适合刚接触Vivado的FPGA新手也适合那些还在手写UART、想找个理由说服自己偷懒的老工程师。核心关键词就几个Vivado、AXI UART Lite、IP核、FPGA、串口通信围绕它们展开。先说结论如果你用的是Xilinx 7系列及以上的FPGAVivado版本在2015.4之后那么用AXI UART Lite做串口通信是性价比最高的方案。没有之一。2. 方案选型为什么是AXI UART Lite而不是自己写2.1 手写UART的隐性成本被严重低估很多人觉得手写UART可控性强但实际项目里这个可控性往往变成可维护性灾难。我列几个手写UART常见的隐性成本波特率分频计算容易出错。假设系统时钟100MHz要跑115200波特率分频系数是100000000/115200≈868.05。你得做小数分频或者取整取整后实际波特率变成115207误差0.006%勉强能用。但如果系统时钟是50MHz分频系数434.02取整434实际波特率115207误差同样很小。可一旦系统时钟是奇怪的频率比如27MHz分频系数234.375取整234实际波特率115384误差0.16%接近UART容忍极限通常要求误差小于2%但实际工程建议小于1%。这些计算每次都要重来。采样点对齐问题。UART接收需要在起始位下降沿后延迟半个位周期采样之后每隔一个位周期采样一次。手写状态机如果采样点偏移误码率会飙升。我见过一个项目仿真没问题上板偶尔丢数据查了三天才发现是采样点在第7个时钟周期而不是第8个导致停止位判断临界。FIFO和流控要自己搭。没有FIFOCPU读数据稍慢就丢包没有流控高速传输时上位机来不及处理。手写这些逻辑代码量翻倍。跨时钟域处理。UART收发时钟和系统时钟不同源时还得加同步器又是一堆坑。这些成本加起来手写UART至少需要2-3天调试而AXI UART Lite配置加连线熟练的话10分钟。2.2 AXI UART Lite到底封装了什么AXI UART Lite IP核内部结构大致分三块AXI4-Lite从接口、寄存器组、UART收发核心。AXI4-Lite接口负责跟MicroBlaze或Zynq PS通信寄存器组暴露了接收FIFO、发送FIFO、状态寄存器、控制寄存器UART核心负责实际的串行收发。关键特性特性参数数据位宽5-8位可配默认8位波特率可配置支持自定义校验位支持奇偶校验可配停止位1位或2位FIFO深度收发各16字节中断支持接收FIFO非空、发送FIFO空等中断接口AXI4-Lite注意它不支持硬件流控RTS/CTS也不支持9位数据模式。如果你的应用需要这些得考虑AXI UART 16550 IP核。但对于90%的调试和通信场景Lite版本足够。2.3 什么场景该用什么场景不该用推荐使用Zynq/MicroBlaze系统的调试串口跟PC上位机做低速数据交互1Mbps跟MCU、蓝牙模块、GPS模块等外设通信需要快速验证原型不想在UART上浪费时间不推荐使用需要硬件流控的高速传输需要9位数据模式的多机通信纯FPGA逻辑无处理器且资源极度紧张的场景——这时候手写一个精简UART可能更省LUT我个人的判断标准很简单如果系统里有MicroBlaze或Zynq PS无脑用AXI UART Lite如果是纯逻辑FPGA看资源余量余量够也用IP核省心。3. 核心细节解析参数计算与配置要点3.1 波特率到底怎么算AXI UART Lite的波特率计算公式在PG142文档里写得很清楚波特率 时钟频率 / (分频系数 × (数据位 校验位 停止位 1))等等这个公式容易让人误解。实际上Vivado IP核配置界面里你只需要填时钟频率和目标波特率工具会自动算分频系数。但你要知道它怎么算的才能判断误差是否可接受。以100MHz时钟、115200波特率为例分频系数 100000000 / (115200 × 16) ≈ 54.25AXI UART Lite内部对时钟做了16倍过采样所以分频系数是54.25。工具会取整为54实际波特率实际波特率 100000000 / (54 × 16) 115740 误差 (115740 - 115200) / 115200 ≈ 0.47%0.47%的误差在UART容忍范围内通常要求小于2%实际通信没问题。但如果你的时钟是24MHz目标115200分频系数 24000000 / (115200 × 16) ≈ 13.02 取整13实际波特率 24000000 / (13 × 16) 115384 误差 0.16%也还行。但如果时钟是12MHz分频系数 12000000 / (115200 × 16) ≈ 6.51 取整6实际波特率 12000000 / (6 × 16) 125000 误差 8.5%这就超标了通信会丢包。所以时钟频率最好选100MHz、50MHz这类能被115200整除得比较整的频率。如果系统时钟不理想可以在IP核配置时选择Custom波特率手动填分频系数或者用时钟向导生成一个合适的时钟。提示Vivado IP核配置界面里有个Actual Baud Rate显示配置完看一眼实际波特率和误差超过1%就要警惕。3.2 AXI4-Lite接口的地址映射AXI UART Lite占用4个寄存器地址偏移如下偏移地址寄存器名功能0x00RX FIFO接收数据读操作0x04TX FIFO发送数据写操作0x08STAT状态寄存器读操作0x0CCTRL控制寄存器写操作状态寄存器的位定义Bit 0: RX FIFO非空Bit 1: RX FIFO满Bit 2: TX FIFO空Bit 3: TX FIFO满Bit 4: 中断使能Bit 5: 溢出错误Bit 6: 帧错误Bit 7: 奇偶校验错误控制寄存器的位定义Bit 0: 复位TX FIFOBit 1: 复位RX FIFOBit 2: 使能中断Bit 3: 使能奇偶校验这些寄存器在MicroBlaze或Zynq PS里通过指针访问比如#define UART_BASE 0x40600000 #define UART_RX (UART_BASE 0x00) #define UART_TX (UART_BASE 0x04) #define UART_STAT (UART_BASE 0x08) #define UART_CTRL (UART_BASE 0x0C) void uart_send_byte(uint8_t data) { while (*(volatile uint32_t*)UART_STAT 0x08); // 等待TX FIFO非满 *(volatile uint32_t*)UART_TX data; } uint8_t uart_recv_byte(void) { while (!(*(volatile uint32_t*)UART_STAT 0x01)); // 等待RX FIFO非空 return *(volatile uint32_t*)UART_RX 0xFF; }这段代码是我实际项目里用的简单直接。注意读RX FIFO时要跟0xFF做与操作因为高24位是无效的。3.3 中断模式 vs 轮询模式AXI UART Lite支持中断但实际项目里我90%的情况用轮询。原因很简单调试串口的数据量很小轮询完全够用而且代码简单不容易出bug。中断模式适合高速数据流或者低功耗场景但需要配置中断控制器增加复杂度。如果你确实要用中断配置步骤IP核配置界面勾选Enable Interrupt在Block Design里把中断输出连到MicroBlaze的中断控制器或Zynq PS的IRQ软件里写中断服务函数读STAT寄存器判断中断源清中断中断服务函数示例void uart_isr(void) { uint32_t stat *(volatile uint32_t*)UART_STAT; if (stat 0x01) { // RX FIFO非空 uint8_t data *(volatile uint32_t*)UART_RX 0xFF; // 处理数据 } // 清中断读STAT或写CTRL }注意AXI UART Lite的中断是电平触发不是边沿触发。如果不清中断源会一直进中断。清中断的方式是读RX FIFO直到空或者写CTRL寄存器复位FIFO。4. 实操过程从零搭建一个串口回环4.1 Block Design搭建步骤假设你已经有一个Zynq或MicroBlaze的基础工程系统时钟100MHz。步骤如下打开Block Design添加Zynq Processing System或MicroBlaze。添加AXI UART Lite IP核在Diagram窗口右键Add IP搜索AXI UART Lite双击添加。配置IP核双击IP核设置Baud Rate: 115200Data Bits: 8Parity: No ParityStop Bits: 1Enable Interrupt: 不勾选先用轮询时钟频率100MHz跟系统时钟一致连接时钟和复位把IP核的s_axi_aclk连到系统时钟s_axi_aresetn连到复位。连接AXI总线把IP核的S_AXI连到Zynq的M_AXI_GP0或MicroBlaze的AXI互联。引出UART引脚IP核的uart_txd和uart_rxd引出到顶层连到FPGA的IO引脚。分配地址在Address Editor里给AXI UART Lite分配地址比如0x40600000。生成BitstreamValidate DesignGenerate Output ProductsRun ImplementationGenerate Bitstream。整个过程熟练的话5分钟不熟练可能15分钟。关键点是时钟频率要填对填错了波特率就错了。4.2 引脚约束怎么写UART的TX和RX引脚需要约束到FPGA的物理引脚。以Xilinx Artix-7为例假设TX接在P4RX接在P5set_property PACKAGE_PIN P4 [get_ports uart_txd] set_property IOSTANDARD LVCMOS33 [get_ports uart_txd] set_property PACKAGE_PIN P5 [get_ports uart_rxd] set_property IOSTANDARD LVCMOS33 [get_ports uart_rxd]注意TX是输出RX是输入方向别搞反。另外如果FPGA板上有USB转串口芯片比如CP2102、FT232TX和RX要交叉连接FPGA的TX接芯片的RXFPGA的RX接芯片的TX。提示很多开发板已经把USB转串口芯片连好了你只需要约束到对应的FPGA引脚。查板卡原理图确认。4.3 软件端代码实现在Vitis或SDK里新建一个Hello World工程替换main函数#include stdio.h #include xparameters.h #include xil_io.h #include xil_printf.h #define UART_BASE XPAR_AXI_UARTLITE_0_BASEADDR #define UART_RX (UART_BASE 0x00) #define UART_TX (UART_BASE 0x04) #define UART_STAT (UART_BASE 0x08) #define UART_CTRL (UART_BASE 0x0C) void uart_init(void) { // 复位TX和RX FIFO Xil_Out32(UART_CTRL, 0x03); // 清除复位 Xil_Out32(UART_CTRL, 0x00); } void uart_send_byte(uint8_t data) { while (Xil_In32(UART_STAT) 0x08); // 等待TX FIFO非满 Xil_Out32(UART_TX, data); } uint8_t uart_recv_byte(void) { while (!(Xil_In32(UART_STAT) 0x01)); // 等待RX FIFO非空 return Xil_In32(UART_RX) 0xFF; } int main(void) { uart_init(); xil_printf(UART Loopback Test\r\n); while (1) { uint8_t data uart_recv_byte(); uart_send_byte(data); // 回环收到什么发什么 } return 0; }这段代码实现了一个简单的回环PC串口助手发什么FPGA就回什么。烧录后打开串口助手波特率115200发送Hello应该能收到Hello。4.4 上板验证与调试上板后如果没反应按以下顺序排查确认波特率串口助手和IP核配置的波特率必须一致。确认引脚用示波器或逻辑分析仪抓TX引脚看有没有波形。如果没有检查约束和引脚分配。确认时钟IP核的时钟频率是否跟实际系统时钟一致。如果系统时钟是50MHzIP核填了100MHz波特率会翻倍。确认地址软件里的UART_BASE是否跟Address Editor里分配的一致。确认FIFO复位初始化时是否复位了FIFO。有些板子上电后FIFO状态不确定需要手动复位。我遇到过最坑的一次是IP核时钟填了100MHz但实际系统时钟是50MHz结果波特率变成230400串口助手用115200收全是乱码。查了半天才发现是时钟频率填错。5. 常见问题与排查技巧实录5.1 串口乱码问题速查表现象可能原因解决方法全是乱码波特率不匹配检查IP核时钟频率和波特率配置确认串口助手波特率一致偶尔乱码波特率误差过大计算实际波特率误差超过1%换时钟频率或分频系数收到固定字符引脚接反TX和RX交叉连接检查约束无任何数据引脚未约束或时钟未连接检查约束文件用示波器抓TX波形数据丢失FIFO溢出降低发送速率或启用中断模式及时读数据只能收不能发TX FIFO满检查STAT寄存器Bit 3等待TX FIFO非满再写5.2 波特率误差计算实战假设你的系统时钟是33.333MHz有些板子用这个频率目标115200分频系数 33333000 / (115200 × 16) ≈ 18.08 取整18实际波特率 33333000 / (18 × 16) 115740 误差 0.47%还行。但如果时钟是25MHz分频系数 25000000 / (115200 × 16) ≈ 13.56 取整14实际波特率 25000000 / (14 × 16) 111607 误差 3.1%超标了。这时候要么换时钟要么用IP核的Custom模式手动填分频系数但分频系数只能是整数误差依然存在。最好的办法是用时钟向导生成一个100MHz或50MHz的时钟专门给UART用。提示Vivado的Clock Wizard IP可以生成任意频率时钟但会消耗MMCM/PLL资源。如果资源紧张尽量选系统已有的合适时钟。5.3 AXI总线握手失败排查有时候Block Design里AXI UART Lite连上了但软件读写寄存器没反应。常见原因是AXI总线握手失败。排查方法检查Address Editor确认IP核地址已分配且没有地址冲突。检查互联Zynq的M_AXI_GP0是否使能MicroBlaze的AXI互联是否正确。检查时钟和复位AXI UART Lite的s_axi_aclk和s_axi_aresetn必须连接且复位要释放。用ILA抓AXI信号在Vivado里插入ILA核抓AWVALID、AWREADY、WVALID、WREADY等信号看握手是否完成。我遇到过一次MicroBlaze的AXI互联少连了一根时钟导致UART IP核的AXI接口没时钟软件读写全挂。查了两小时才发现。5.4 中断不触发的排查如果用了中断模式但进不了中断服务函数确认中断使能CTRL寄存器Bit 2要置1。确认中断控制器配置MicroBlaze要配置中断控制器Zynq PS要配置GIC。确认中断连接IP核的interrupt输出要连到中断控制器。确认中断清标志读RX FIFO直到空或者写CTRL复位FIFO。确认优先级多个中断源时检查优先级配置。注意AXI UART Lite的中断是RX FIFO非空或TX FIFO空触发的不是每收一个字节触发一次。如果FIFO里一直有数据没读中断会一直触发。5.5 资源占用与性能考量AXI UART Lite的资源占用以Artix-7为例资源数量LUT约200-300FF约300-400BRAM0DSP0FIFO是用分布式RAM实现的不占BRAM。对于资源紧张的FPGA这个占用可以接受。如果连这个都嫌多那只能手写UART但省下来的LUT可能还不够你调试的时间成本。性能方面AXI UART Lite最高支持到约1Mbps取决于时钟频率。再高就不稳定了需要换AXI UART 16550或自己写高速UART。6. 进阶技巧让串口通信更稳更高效6.1 用FIFO阈值中断优化数据接收AXI UART Lite的RX FIFO深度16字节如果每收一个字节就进中断CPU负担很重。可以配置成FIFO达到一定阈值才触发中断。但Lite版本不支持阈值配置只能靠软件在中断里尽量多读。我的做法是进中断后循环读RX FIFO直到空一次性处理完所有数据。void uart_isr(void) { while (Xil_In32(UART_STAT) 0x01) { uint8_t data Xil_In32(UART_RX) 0xFF; // 存入环形缓冲区 ringbuf_put(rx_buf, data); } }这样一次中断能处理最多16字节效率高很多。6.2 环形缓冲区设计串口通信最怕数据丢失加一个环形缓冲区能有效缓解。我通常定义一个256字节的环形缓冲区#define BUF_SIZE 256 typedef struct { uint8_t buf[BUF_SIZE]; volatile uint32_t head; volatile uint32_t tail; } ringbuf_t; void ringbuf_put(ringbuf_t *rb, uint8_t data) { uint32_t next (rb-head 1) % BUF_SIZE; if (next ! rb-tail) { // 缓冲区未满 rb-buf[rb-head] data; rb-head next; } } uint8_t ringbuf_get(ringbuf_t *rb) { if (rb-head rb-tail) return 0; // 缓冲区空 uint8_t data rb-buf[rb-tail]; rb-tail (rb-tail 1) % BUF_SIZE; return data; }中断里只负责往缓冲区塞数据主循环慢慢处理。这样即使主循环偶尔卡顿也不会丢数据。6.3 多串口共存的处理有些项目需要多个串口比如一个调试口、一个通信口。AXI UART Lite可以例化多个每个分配不同地址。软件里定义不同的基地址#define UART_DEBUG_BASE 0x40600000 #define UART_COMM_BASE 0x40610000中断模式下多个UART的中断可以共用一个中断控制器通过读STAT寄存器判断是哪个UART触发的中断。6.4 跟DMA配合做高速传输AXI UART Lite本身不支持DMA但可以通过AXI DMA IP核间接实现。思路是UART收到数据后CPU把数据搬到DMA缓冲区DMA再搬到内存。但这样绕了一圈效率不一定比CPU直接读高。对于115200这种低速场景没必要上DMA。只有波特率超过1Mbps才考虑DMA方案。7. 我踩过的那些坑和最后的经验第一个坑是时钟频率填错。前面提过IP核配置界面填的时钟频率必须跟实际连接的时钟一致。我有一次系统时钟是50MHz但IP核里填了100MHz结果波特率翻倍串口助手全是乱码。查了一下午才发现。后来我养成习惯配置完IP核一定用示波器抓一下TX波形测一下位宽确认波特率对不对。第二个坑是FIFO复位。AXI UART Lite上电后FIFO状态不确定如果不复位可能残留垃圾数据。我在代码里加了初始化复位Xil_Out32(UART_CTRL, 0x03); // 复位TX和RX FIFO Xil_Out32(UART_CTRL, 0x00); // 清除复位这两行代码花不了多少时间但能避免很多诡异问题。第三个坑是AXI地址对齐。AXI UART Lite的寄存器是32位对齐的地址偏移必须是4的倍数。如果你用结构体映射寄存器注意编译器对齐问题。我一般直接用宏定义偏移地址避免结构体对齐的坑。第四个坑是中断优先级。多个中断源时如果UART中断优先级太低可能被其他中断阻塞导致数据丢失。Zynq PS的GIC可以配置优先级MicroBlaze的中断控制器也可以。我一般把UART中断设为中等优先级既不会被高频中断淹没也不会饿死。最后一个经验别在UART上炫技。我见过有人用状态机手写UART还加了各种校验、重传、流控代码上千行。结果项目延期bug一堆。用IP核5分钟搞定的事何必呢。FPGA工程师的价值在于系统架构和算法实现不在于重复写外设驱动。把时间省下来去优化你的图像处理流水线、去调你的高速接口时序那才是真正产生价值的地方。AXI UART Lite这个IP核我从2016年用到现在经手的项目少说几十个从来没掉过链子。配置简单、稳定可靠、资源占用低是Vivado里我最喜欢的IP核之一。如果你还在手写UART真的可以试试。5分钟配置省下两天调试时间这笔账怎么算都划算。
返回列表