ARTICLE DETAIL

资讯详情

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

ZYNQ串口扩展:用AXI Uartlite将任意PL引脚变成串口

ZYNQ串口扩展:用AXI Uartlite将任意PL引脚变成串口 做ZYNQ开发的时间久了你会发现一个特别真实的焦虑来源需要用串口的地方永远比PS端可用的UART多。调试要一个、日志要一个、跟外部MCU通信又要一个偏偏ZYNQ的PS固定就那么几个UART多一个都没有。后来我把主意打到了PL上——用AXI Uartlite IP核直接把任意普通PL引脚变成串口。这个方案让我从“串口不够用”的泥潭里彻底解脱出来而且整个过程不复杂从Vivado里的IP配置、引脚约束到SDK里的驱动调用一整套链路我都踩过一遍今天把完整流程和坑都整理出来。这篇内容适合已经被PS串口数量卡住、或者想让ZYNQ的串口走PL侧自定义引脚的工程师。不需要你有多深的FPGA功底只要用过Vivado、会看基本原理图照着做就能把串口扩展出来。我尽量把每一步为什么这么做也讲清楚毕竟只抄作业不弄懂原理换个板子换个时钟你照样会翻车。1. 为什么要把串口放到PL侧1.1 PS自带串口的两点现实局限ZYNQ的PS端UART从硬件资源上说不算少比如ZYNQ-7020有2个UARTZYNQ UltraScale的型号会更多一些。但真实项目里这两个串口的处境非常尴尬。第一PS端的MIO引脚是固定的。每个UART对应的MIO引脚位置是芯片出厂就固定死的你想换也换不了。如果板级设计的时候MIO引脚已经被分配给了其他功能比如SD卡、EMMC、ENET那某个UART可能根本引不出来或者只能通过EMIO借道PL再往外接。第二PS串口往往承担着系统调试的重任。Boot ROM打印、U-Boot信息、内核启动日志全都默认走PS端的UART0或UART1。一旦你的调试串口被业务占用系统启动阶段出了异常你连个看日志的地方都没有。这时候PL侧顶一个串口出来专门用于和业务设备通信调试串口原封不动留给系统这是最舒服的分工。1.2 PL侧UART能带来的自由度PL侧做串口最吸引人的一点就是引脚不受MIO限制。只要满足bank电压和IO标准要求理论上PL的任意普通IO都能作为UART的TX和RX引脚。这意味着什么呢你可以在PCB设计阶段把串口信号就近拉到你方便走线、方便接插件的引脚上而不是被MIO固定位置逼着绕一圈。对已经做好的板子如果发现某个FPGA引脚空着没用也可以临时把它配置成串口不用改硬件只改约束和逻辑这灵活性是PS串口给不了的。不过要泼一盆冷水PL引出的串口是逻辑层信号不是直接的电平转换后的RS232。要对接PC的DB9或者USB转串口模块外部电路还是需要一个电平转换芯片比如MAX3232或者CH340G模块这个跟PS串口在板级上的要求是一样的。2. AXI Uartlite IP核是这样工作的2.1 从IP核名字拆解它的定位“AXI Uartlite”这个名字其实已经把它的特点说得很直白了。“AXI”说明它是挂在AXI总线上的外设ZYNQ的PS通过AXI接口就能访问它“Uart”说明它实现的是UART协议“Lite”则是说它在功能上是轻量版。和Xilinx另一个常用的UART IP核AXI UART16550相比Uartlite的寄存器数量更少FIFO深度也更浅没有16550那种兼容PC老式串口的复杂模式。Uartlite内部结构非常精简发送寄存器、接收寄存器、状态寄存器、控制寄存器外加一个可选的波特率发生器总共就这几个东西。别小看这个精简设计。对于FPGA里面做嵌入式控制我们大部分场景只关心“把字节发出去”和“把字节收进来”那些调制解调器控制信号、DMA流控、多种帧格式日常根本用不上。Uartlite在资源占用上非常省在ZYNQ上跑起来也就消耗几百个LUT对PL资源几乎谈不上压力。2.2 波特率发生器的原理和计算Uartlite支持两种波特率产生方式。一种是不勾选“Baud Rate Generator Enable”完全由外部逻辑提供一个特定频率的信号对应波特率是固定的另一种是勾选“Baud Rate Generator Enable”IP核内部通过分频的方式把输入时钟按照你设定的波特率重新分频得到串口时钟。在Vivado的IP配置界面你只需要填两个参数外部输入时钟频率和期望波特率。工具会自动算出分频系数。手动计算也不难UART Lite的分频公式是分频系数 期望波特率对应的分频基准 输入时钟频率 / (2 × 波特率)以100MHz外部时钟、115200bps为例100000000 / (2 × 115200) 434.0278分频系数要取整数434。那么实际波特率是100000000 / (2 × 434) 115207.37误差只有0.006%完全在UART通信允许的±2%误差范围之内稳得很。提示这里尤其要注意IP核配置界面里填的外部时钟频率必须跟实际接到IP核上的时钟频率严格一致。很多人的串口乱码不是电路问题而是这里拍脑袋填了一个数跟实际不一致结果波特率实际值跑偏到天边去了。2.3 IP核配置界面里那些关键选项在Vivado IP Catalog里搜索“Uartlite”或者“AXI UART Lite”就能看到这个核。双击进入配置界面真正需要关心的选项其实就那么几个Baud Rate目标波特率常用9600、115200、57600都行Baud Rate Generator Enable勾上让IP核内部自己分频不勾的话波特率由外部输入时钟直接决定Data Bits数据位宽常规选8位Parity校验方式None、Even、Odd可选Stop Bits停止位1位或2位FIFO收发FIFO的深度可选16字节或32字节够用就行这个IP核还有一个General Purpose选项通常是“UART_LITE”功能模式不需要动。配置界面的Interrupt选项建议把“Enable Interrupt”勾上。虽然裸机下轮询也能用但中断方式在系统的实时性上会好很多尤其是接收外部不定长数据的时候轮询很容易丢字节。3. Vivado实操从添加IP到生成比特流3.1 创建工程并添加IP核新建RTL工程这一步大家都会。器件型号选中你自己的ZYNQ型号比如XC7Z020CLG484。在原理图设计界面点击添加IP搜索“AXI Uartlite”双击添加进去。添加之后IP核会自动出现在Block Design里。接着把ZYNQ Processing System也添加进去运行Block Automation让Vivado自动把PS和PL之间的AXI总线连好。这里有一个容易忽略的点Block Automation的选项里会有一个“Apply Board Preset”的复选框除非你知道自己在做什么否则不要勾选预设让工具使用默认配置免得自动生成的引脚约束跟你后续手动约束冲突。3.2 连线方式与地址分配细节AXI Uartlite作为一个AXI从设备要接到PS的AXI主端口上。在ZYNQ-7020这种器件上PS的M_AXI_GP0口是最常用的连接点它可以挂载多个AXI外设Uartlite作为其中一个从机。在Block Design里选中Uartlite的S_AXI端口右键点击“Make Connection”连接到M_AXI_GP0。工具会自动给你分配一个地址空间但建议手动指定方便后续写设备树和驱动。常见做法是分配一个独立的地址块比如0x42C00000地址范围默认给64KBUartlite的寄存器只有十几个字节64KB对它来说绰绰有余系统里预留大一点反而避免跟其他IP地址重叠。连线的时候还要特别注意时钟和复位。Uartlite需要一个时钟输入通常直接用PL的FCLK_CLK0也就是PS生成并输出到PL的时钟。复位信号也要连上可以是PS的复位输出也可以是自己做的复位逻辑。3.3 关键约束怎么把UART信号绑定到任意引脚这一步就是标题里说到的“把任意引脚变成串口”的关键动作。在Block Design里Uartlite的UART接口是一组端口名字默认叫uart_tx和uart_rx。要让它们变成FPGA的真实物理引脚得在顶层模块里把它们引出来然后在XDC约束文件里指定位置。在顶层Verilog里把uart_tx和uart_rx声明为输出和输入连接到总线的例化端口。然后在XDC文件里写set_property PACKAGE_PIN AB10 [get_ports uart_tx] set_property PACKAGE_PIN AB11 [get_ports uart_rx] set_property IOSTANDARD LVCMOS33 [get_ports uart_tx] set_property IOSTANDARD LVCMOS33 [get_ports uart_rx]AB10和AB11只是示例实际引脚号要从你板子的原理图或者FPGA封装文件里查。要强调的一点是这里的引脚必须是PL侧的普通IOHR或者HP bank不能是PS专用引脚。只要bank电压等级和IOSTANDARD匹配TX和RX放到哪两个脚完全由你说了算。这里有个经验优先选择靠近电路板边缘、便于接插件走线的引脚同时避免占用跟DDR、Flash等硬核功能相关的专用引脚。另外同一个bank里的引脚IOSTANDARD必须统一不要把3.3V和1.8V混在同一个bank里用否则时序和电平都会出问题。3.4 生成比特流并导出硬件描述约束写好后先跑综合再跑实现最后生成比特流。这个过程如果遇到时序违例优先看是不是引脚约束的IOSTANDARD写错了或者bank电压不匹配。UART是低速外设理论上走线不会成为时序瓶颈大多数报错还是电平标准配置问题。比特流生成完毕记得在Vivado里执行File Export Hardware勾选“Include bitstream”导出XSA文件。这个XSA会作为后续SDK或者Vitis工程的硬件描述输入。到这一步硬件部分的配置工作就完成了接下来才是从逻辑世界走进驱动世界。4. 驱动层裸机和Linux下的两种调用路径4.1 裸机驱动SDK/Vitis中生成并调用在Vitis老版本叫SDK里基于刚才导出的XSA文件创建应用工程。新建平台工程时Vitis会自动解析XSA里的硬件信息为AXI Uartlite生成驱动源码。面向单个应用时常用的驱动API有这么几个XUartLite_Initialize初始化UART实例参数是设备ID或者基地址XUartLite_Send发送指定长度的数据XUartLite_Recv接收指定长度的数据XUartLite_SetRecvHandler设置接收中断回调函数XUartLite_IsReceiveDataEmpty查询接收FIFO是否为空裸机下最简单也最稳定的流程是初始化之后发送用轮询接收用中断。轮询发送的好处是逻辑简单不会出现数据覆盖接收用中断是因为外部设备什么时候发数据是完全不可预测的轮询接收在实时性上根本没有保障。中断处理有一点要提醒Uartlite的中断是电平敏感的回调函数里要尽快把FIFO里的数据读出来否则中断一直挂起系统会被打断到崩溃边缘。我在项目里一般是在回调里把数据搬进一个环形缓冲区中断处理函数本身只做“搬运工”绝不做复杂解析这样系统响应又快又稳。4.2 Linux驱动设备树与uartlite模块配置Linux环境下Xilinx提供了现成的uartlite驱动内核配置里开启CONFIG_SERIAL_UARTLITE以后驱动模块就会在系统启动时自动探测设备树里描述的Uartlite节点。设备树里Uartlite节点的写法大同小异核心内容如下serial42c00000 { compatible xlnx,xps-uartlite-1.00.a; reg 0x0 0x42c00000 0x0 0x1000; interrupts 0 23 4; interrupt-parent intc; clock clkc 22; };这里几个字段要解释一下。reg里的地址要和Vivado里分配的地址完全对应否则驱动探测不到设备interrupts是中断号这个数值不是随便写的它由PS的IRQ编号和PL外设连接到PS中断控制器的具体位置决定。具体数值怎么查Vitis的xparameters.h里有宏定义比如XPAR_AXI_UARTLITE_0_INTC_TYPE照着搬就行。设备树配好以后内核起来应该能在/dev下看到ttyUL0、ttyUL1这样的设备节点。ttyUL是uartlite专用的命名用它就行。在Linux下调用跟普通串口没有任何区别。打开设备节点、配置termios参数、调用read/write这套标准操作完全适用。我的习惯是用stty工具配置波特率stty -F /dev/ttyUL0 115200 raw配置好之后直接echo test /dev/ttyUL0就能把数据发出去。5. 测试环节与踩坑记录5.1 验收功能前先做回环测试无论裸机还是Linux系统拿到一个新串口我的习惯是先把TX和RX短接做一次硬件回环测试。把TX和RX用一根杜邦线直接连起来然后在代码里发送一串数据如果接收端能原样收到说明UART通路是通的。这个测试在硬件调试阶段最有效。因为如果回环都收不到数据问题百分之百出在引脚约束、电平标准或者时钟配置上跟后面业务逻辑一点关系都没有。先把这个最简单的链路打通再去接外部设备排查问题的时候范围能缩小一大半。裸机下做回环测试的伪代码很好写XUartLite_Initialize(UartLite, 0); XUartLite_Send(UartLite, test_buf, buf_len); while (!XUartLite_IsReceiveDataEmpty(UartLite)) { XUartLite_Recv(UartLite, recv_ch, 1); }如果收回来的数据跟发出去的完全一致恭喜硬件链路没问题。5.2 最容易翻车的几个坑我在这套流程里踩过的坑按出现频率排序大概是这几个。第一个是波特率对不上表现是数据全是乱码。排查时先回头看IP核配置界面的外部时钟频率跟实际给的时钟是不是一致。我之前有一次是从FCLK_CLK1取的100MHz时钟配置界面里却填了50MHz结果波特率偏了一倍排了半天才发现是这种低级的粗心错误。第二个是引脚约束报错。Vivado在综合的时候报“端口不存在”或者“引脚是专用输入引脚”之类的问题通常是因为顶层模块端口名和XDC里get_ports的名字不匹配。这个名字必须跟顶层RTL里的端口名完全一致大小写都不能错。第三个是Linux下设备节点没生成。大概率是设备树地址和Vivado分配的地址对不上。建议直接去Vitis生成的xparameters.h里复制基地址粘贴进设备树不要手动输入能省去对齐地址的麻烦。第四个是中断不生效。裸机下如果设置了接收中断但一直不进回调先查中断控制器的连接有没有使能再看Uartlite的中断输出有没有接到PS的中断输入端口。用户自定义的逻辑里这一步经常因为忘记在Block Design里连线而白忙活。5.3 排查思路速查表现象最可能的原因排查方向完全无数据输出引脚约束错误或外部电平转换芯片故障先测IO电平再查XDC和bank电压输出乱码波特率不匹配核对IP时钟频率与预期波特率发送正常收不到RX引脚约束错误或外部设备未接GND测量RX引脚静态电平回环测试验证Linux下没有ttyUL节点设备树地址错误或驱动未编译核对reg地址检查内核config中断回调不触发中断连线遗漏或中断号错误检查Block Design中断连接和xparameters宏定义收发一段时间死机接收FIFO溢出中断回调搬数据要快使用环形缓冲这套排查思路其实不局限在Uartlite这个IP上。任何PL外设出问题我都会先按“硬件链路 → 寄存器配置 → 驱动配置”这三层来排查。硬件层面的问题用示波器看波形寄存器层面的问题用AXI调试工具读寄存器值驱动层面的问题看内核日志和系统调用返回每一层都有对应的高效工具善用它们能省下大把调试时间。6. 走完一遍流程后的实用建议6.1 扩展应用与资源规划如果你发现一个Uartlite不够用可以瞬间再例化第二个、第三个只要PL资源够用。它们彼此完全独立互不干扰每个都只需要一条AXI地址空间和一个中断通道。在Block Design里多拖几个IP核再调整一下地址和中断一次性扩展出好几个串口不是梦。但别太放飞自我。每个Uartlite在PS上都要占一个中断号中断号资源是有限的尤其是PL到PS的中断只有固定的几个通道。串口数量太多中断分配会变得非常局促。另一个要注意的就是AXI总线的带宽低速串口虽然吃不了多少带宽但多个外设同时高频率访问时总线资源还是有竞争最好估算一下峰值流量。6.2 一句真心话从我个人的实际经验看AXI Uartlite是ZYNQ开发里“性价比”极高的一个IP核。学习成本低、硬件实现快、驱动也成熟再加上PL引脚自由度带来的硬件设计弹性几乎是一本万利的选择。比起去抠那些复杂总线协议和高速接口把这类小IP用好、用透能解决很多项目里的实际问题。如果你正被串口数量困扰不妨花一个下午把这个方案落地大概率会回来感谢现在的自己。
返回列表