拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Zynq-7000 PL侧扩展9路UARTLite串口:从Vivado到Linux驱动的完整指南

简介本资源面向Zynq嵌入式开发工程师与FPGA系统集成人员聚焦PL侧扩展多路UART通信的实际工程痛点——Zynq PS端仅提供2路串口难以满足工业控制、多传感器接入等场景下9路及以上串口需求。资源提供完整可运行的Vivado工程含9路UARTLITE IP并行例化、适配Linux系统的设备树源文件.dts/.dtsi及详细调试文档覆盖从IP配置、约束添加、综合实现到设备树修改、内核编译与串口验证的全流程。压缩包共606个文件27.43MB包含73个Verilog逻辑文件、49个XDC约束、36个DCP综合结果、30个Shell脚本及4个设备树接口文件结构清晰、模块分离便于按阶段复现与问题定位。已有948人学习下载配套文档深入剖析常见综合失败、设备树节点挂载异常、/dev/ttyUL*设备缺失等典型排错思路显著降低多路UARTLITE在Zynq PL侧落地门槛。1. 项目背景与核心价值最近在做一个基于Zynq-7000系列SoC的工业网关项目需要在PL可编程逻辑侧扩展多路串口来连接各种传感器和PLC。Xilinx官方提供的PS处理系统端UART控制器只有两个远远不够用。这时候一个非常经典且高效的方案就浮出水面了使用PL侧的UARTLite IP核来扩展串口。这个方案听起来简单但真要把从Vivado工程创建、IP核例化、约束文件编写到设备树Device Tree配置、驱动加载这一整套流程跑通尤其是要稳定地跑起来9路串口中间有不少细节和“坑”需要留意。网上关于UARTLite的零散教程很多但要么只讲Vivado部分要么只贴一段设备树代码缺乏一个从硬件到软件、从理论到实操的完整闭环指南。特别是当串口数量增多时地址分配、中断管理、设备树节点命名这些细节如果处理不当很容易导致驱动加载失败或者数据收发异常。这篇文章我就结合自己最近成功实现9路UARTLite串口的实战经验把每一步的操作、背后的原理以及容易踩的坑掰开揉碎了讲清楚。无论你是FPGA新手还是有一定经验的嵌入式工程师都能跟着这个指南一步步搭建起自己的多串口通信系统。2. Vivado工程创建与UARTLite IP核配置整个流程的起点在Vivado。我们的目标是在Zynq的PL部分实例化9个独立的UARTLite IP核并通过AXI总线与PS端的ARM处理器进行通信。2.1 新建工程与添加Zynq Processing System首先打开Vivado创建一个新的RTL工程选择对应的Zynq器件型号例如xc7z020clg400-1。工程创建好后第一步就是添加Zynq Processing System IP核。这是整个系统的核心它代表了PS端的ARM双核Cortex-A9处理器及一系列外设。在Block Design中点击“”号添加IP搜索并添加“ZYNQ7 Processing System”。双击添加进来的ZYNQ7 IP核进行配置。这里有几个关键点MIO Configuration: 根据你的硬件板卡原理图配置好PS端外设的引脚复用例如QSPI Flash、SD卡、USB、以太网等。确保这些基础功能正常系统才能启动。Clock Configuration: 注意PS输入时钟FCLK_CLK0的频率它通常来自板载晶振。同时我们需要为PL提供时钟。在“PL Fabric Clocks”中使能FCLK_CLK0或其他可用的FCLK并设置一个合适的频率例如50MHz或100MHz。这个时钟将作为我们后续添加的PL逻辑的主时钟。DDR Configuration: 正确配置DDR内存的型号、位宽和时钟频率这是系统运行Linux所必需的。勾选UART0或UART1: 在“PS-PL Configuration” - “General” - “Enable UART 0”或“UART 1”。这一步是为了让PS端的UART控制器可用我们通常用它作为Linux系统的控制台Console方便调试。注意这是PS自带的UART和我们后面要在PL添加的9路UARTLite是两回事。配置完成后点击“OK”Vivado会自动生成一个包含ZYNQ7 IP的Block Design。2.2 添加并配置UARTLite IP核接下来就是主角登场了。我们需要连续添加9个UARTLite IP核。再次点击“”号添加IP搜索“AXI Uartlite”。将其拖入Diagram中。你会看到一个名为axi_uartlite_0的IP。关键的一步配置第一个UARTLite实例。双击axi_uartlite_0打开配置窗口。Baud Rate: 设置你需要的波特率例如115200。UARTLite的波特率是通过时钟分频产生的所以这里的设置必须和后续提供给IP的时钟频率匹配。Data Bits, Parity, Stop Bits: 根据你的通信协议设置通常为8-N-18位数据无校验1位停止位。最重要的是“Interface Options”确保“Enable UART”被勾选。“BAUD”和“CLOCK”的频率设置这里的“BAUD”就是你上面设置的波特率目标值。“CLOCK”频率必须填写我们之前从ZYNQ7 IP的FCLK输出给PL的时钟频率例如50MHz。UARTLite IP会基于这个CLOCK频率内部计算分频系数来产生目标波特率。如果这里填错实际波特率就会偏差导致通信失败。重复以上步骤再添加8个AXI Uartlite IP核分别命名为axi_uartlite_1到axi_uartlite_8。每个的配置波特率、数据格式可以相同也可以不同根据实际需求来。但请务必为每一个IP核单独配置并确认其CLOCK频率设置正确。2.3 连接时钟、复位与AXI总线添加完IP后我们需要用“连线”把它们组织成一个能工作的系统。时钟连接将ZYNQ7 IP的FCLK_CLK0输出端口连接到每一个UARTLite IP的s_axi_aclkAXI总线时钟和uart端口下的clkUART模块时钟。这确保了整个AXI通信和UART核心逻辑同步在同一个时钟下工作。你可以使用Vivado的“自动连接”功能但连接后务必检查是否正确。复位连接将ZYNQ7 IP的FCLK_RESET0_N输出端口连接到每一个UARTLite IP的s_axi_aresetnAXI总线复位低电平有效。这确保了系统上电或复位时所有IP能同步复位。AXI总线连接这是连接PS和PL逻辑的数据通道。ZYNQ7 IP默认有一个M_AXI_GP0接口通用AXI主端口。我们需要一个AXI Interconnect互联器来管理一个主设备PS对多个从设备9个UARTLite的访问。添加一个“AXI Interconnect” IP核。将其S00_AXI从接口连接到ZYNQ7的M_AXI_GP0主接口。将AXI Interconnect的M00_AXI到M08_AXI共9个主接口分别连接到9个UARTLite IP的S_AXI从接口。这样PS端的ARM处理器就能通过M_AXI_GP0经由AXI Interconnect访问到每一个UARTLite的内部寄存器了。地址分配连接过程中Vivado会自动为每个UARTLite分配一个基地址Base Address。你可以在“Address Editor”标签页中查看和微调。务必确保这9个地址空间没有重叠且落在Zynq PS访问PL的地址范围内通常是0x4000_0000到0x7FFF_FFFF或0x8000_0000到0xBFFF_FFFF具体取决于配置。记下这些地址后续写设备树要用。外部端口连接每个UARTLite IP都有RX和TX信号。在Block Diagram中分别右键点击每个UARTLite的RX和TX选择“Make External”。这会在设计顶层创建对应的引脚端口例如uart_0_rxd,uart_0_txd一直到uart_8_rxd,uart_8_txd。2.4 生成顶层HDL与引脚约束在Diagram空白处右键选择“Create HDL Wrapper”让Vivado根据Block Design自动生成顶层的Verilog或VHDL文件。接下来是硬件设计的关键一步编写XDC约束文件。你需要根据你的FPGA板卡原理图将刚才创建的uart_x_rxd和uart_x_txd这些端口映射到具体的FPGA物理引脚上并设置正确的I/O标准如LVCMOS33。例如set_property PACKAGE_PIN AB12 [get_ports {uart_0_txd}]例如set_property IOSTANDARD LVCMOS33 [get_ports {uart_0_txd}]特别注意UART是异步通信RX线在空闲时应为高电平。通常FPGA引脚内部有弱上拉但为了可靠建议在约束文件或原理图中为RX引脚配置外部上拉电阻避免因浮空产生误触发。完成约束后进行“Generate Bitstream”。这个过程包括综合、实现和生成比特流文件。如果一切顺利你将得到一个.bit文件它包含了整个PL部分的硬件配置信息。3. 设备树Device Tree的详细配置硬件比特流准备好了接下来要让Linux系统认识并驱动这9个“新外设”。这就要靠设备树DTS来描述了。设备树相当于一张硬件的“地图”告诉Linux内核在地址空间的什么位置有什么样的设备。3.1 理解设备树中的UARTLite节点UARTLite在Linux内核中通常有对应的驱动程序xilinx_uartlite。我们需要在设备树中为每一个UARTLite IP实例创建一个节点。一个标准的UARTLite设备树节点如下所示axi_uartlite_0: serial43c00000 { compatible xlnx,xps-uartlite-1.00.a; reg 0x43c00000 0x10000; interrupts 0 29 4; clocks clkc 15; clock-names s_axi_aclk; current-speed 115200; port-number 0; status okay; };我们来逐行解析关键属性axi_uartlite_0 节点标签label可以自定义方便其他地方引用。serial43c00000 节点名称。serial表示这是一个串口设备后面是该设备的基地址Base Address必须与Vivado Address Editor中分配的地址一致。这是最重要的信息之一填错了内核就找不到硬件。compatible “xlnx,xps-uartlite-1.00.a”; 兼容性字符串。这是驱动匹配设备的“密码”。必须准确填写为UARTLite驱动所识别的字符串。不同版本内核可能略有差异但xlnx,xps-uartlite-1.00.a是通用的。reg 0x43c00000 0x10000; 寄存器区域。第一个值是基地址同上第二个值是地址空间的长度length。UARTLite的寄存器空间很小通常0x1000064KB足够也可以设为0x10004KB。地址和长度必须与硬件设计匹配。interrupts 0 29 4; 中断描述。这是一个难点。第一个值0 中断类型0通常代表SPI共享外设中断这是ARM GIC的中断类型。第二个值29中断号Interrupt ID。这个数字不是随便写的它由Zynq的硬件中断映射决定。在Vivado中当你将UARTLite的interrupt端口引出并连接到xlconcat中断连接器再送给Zynq的IRQ_F2P时可以在Address Editor或system.hdf/xparameters.h文件中找到每个IP的中断ID。务必使用Vivado分配的正确ID。例如第一个UARTLite可能是29第二个是30以此类推。第三个值4 中断触发类型。4代表高电平触发IRQ_TYPE_LEVEL_HIGH。UARTLite通常使用电平触发。clocks和clock-names 指定该设备使用的时钟源。clkc 15表示指向clkc时钟控制器的第15个输出。这个数字需要参考你的Zynq PS-PL时钟配置。更简单的做法是如果时钟频率已知可以直接用clock-frequency属性指定例如clock-frequency 50000000;50MHz。这个频率必须和Vivado中配置给UARTLite的“CLOCK”频率完全一致否则波特率不准。current-speed 115200; 默认波特率。驱动加载时会以此波特率初始化串口。port-number 0; 可选自定义属性可以用来在用户空间标识这是第几个串口方便应用程序识别。status “okay”; 设备状态设为“okay”表示启用该设备。3.2 为9路串口编写完整的设备树源文件在你的Linux内核源码目录如arch/arm/boot/dts/下找到或创建对应你板子的.dtsi或.dts文件。将9个UARTLite节点添加到amba_pl或axi总线节点下因为它们是挂在AXI总线上的PL外设。amba_pl { axi_uartlite_0: serial43c00000 { /* 配置详情见上注意地址、中断号、时钟要逐一修改 */ compatible xlnx,xps-uartlite-1.00.a; reg 0x43c00000 0x10000; interrupts 0 29 4; clock-frequency 50000000; current-speed 115200; port-number 0; status okay; }; axi_uartlite_1: serial43c10000 { compatible xlnx,xps-uartlite-1.00.a; reg 0x43c10000 0x10000; interrupts 0 30 4; clock-frequency 50000000; current-speed 115200; port-number 1; status okay; }; // ... 依次添加 axi_uartlite_2 到 axi_uartlite_8 axi_uartlite_8: serial43c80000 { compatible xlnx,xps-uartlite-1.00.a; reg 0x43c80000 0x10000; interrupts 0 37 4; // 假设中断号递增到37 clock-frequency 50000000; current-speed 115200; port-number 8; status okay; }; };注意在实际操作中中断号29, 30...37是示例你必须根据Vivado工程中xlconcat输出的实际中断号来填写。一个可靠的查找方法是在Vivado中生成比特流后导出硬件Export Hardware在Vitis或SDK中打开查看ps7_init.tcl或xparameters.h文件搜索XPAR_INTC_0_UARTLITE_0_VEC_ID这样的宏定义其值就是中断ID。3.3 编译与更新设备树修改好设备树源文件.dts后使用设备树编译器DTC将其编译成二进制设备树 blob.dtb文件。dtc -I dts -O dtb -o devicetree.dtb system.dts将生成的devicetree.dtb文件连同Linux内核镜像uImage和FPGA比特流文件system.bit或加载后的.bin文件一起放到启动介质如SD卡的FAT分区中。配置你的Bootloader如U-Boot在启动内核时加载这个新的dtb文件。4. Linux系统下的驱动加载与测试当系统启动后如果一切配置正确内核会自动识别并加载xilinx_uartlite驱动。4.1 检查驱动加载情况在Linux终端中执行以下命令进行诊断查看内核启动日志dmesg | grep -i uart你应该能看到类似下面的信息表明驱动成功探测到了硬件[ 1.234567] xuartlite 43c00000.serial: at MMIO 0x43c00000 (irq 29, base_baud 5000000) is a xuartlite [ 1.234568] 43c00000.serial: ttyUL0 at MMIO 0x43c00000 (irq 29, base_baud 5000000) is a xuartlite ... (为其他8个串口打印类似信息)注意ttyUL0这个名称。UARTLite驱动创建的设备节点通常是/dev/ttyUL0,/dev/ttyUL1... 以此类推。查看设备树节点状态ls /sys/bus/platform/devices/ | grep serial这会列出所有平台设备中名称包含“serial”的设备应该能看到43c00000.serial等9个设备。查看生成的设备文件ls -l /dev/ttyUL*确认9个设备文件ttyUL0到ttyUL8都已创建。4.2 串口通信测试你可以使用任何串口调试工具进行测试例如minicom,picocom或者简单的echo和cat命令。配置并打开一个串口例如ttyUL0stty -F /dev/ttyUL0 115200 cs8 -parenb -cstopb # 配置波特率等参数 picocom -b 115200 /dev/ttyUL0 # 使用picocom打开串口在另一个终端向该串口发送数据echo Hello UARTLite 0 /dev/ttyUL0在picocom终端应该能看到接收到的字符串。回环测试Loopback Test 这是验证硬件连接和驱动是否正常的最直接方法。将FPGA板上对应uart_0_txd和uart_0_rxd的引脚用杜邦线短接。然后在终端执行cat /dev/ttyUL0 # 后台监听串口输出 echo Loopback Test /dev/ttyUL0如果驱动和硬件工作正常你会在终端看到“Loopback Test”被打印出来。用同样的方法测试其他8路串口。4.3 性能考量与多路复用当9路串口同时高速收发数据时需要考虑CPU中断负载。UARTLite每收到一个字节就会产生一个中断。在115200波特率下每秒最多可产生约11520个中断115200/10对于9路就是每秒超过10万个中断。这对CPU是个负担。优化建议1使用更高的波特率时钟。在Vivado中给UARTLite提供更高的CLOCK频率如100MHz可以让IP内部使用更精细的分频但中断频率不变。优化建议2驱动层使用DMA如果支持。但标准的UARTLite IP核不支持DMA需要更高级的UART IP核如AXI UART16550。优化建议3应用层缓冲与批量处理。在用户态应用程序中使用select,poll或epoll等多路复用I/O机制来同时监控多个串口避免为每个串口创建一个阻塞的读线程减少上下文切换开销。对于高吞吐场景可以考虑使用内核的serial_core框架或编写专门的多端口优化驱动。5. 常见问题排查与实战心得在调试多路UARTLite的过程中我遇到了几个典型问题这里分享出来希望能帮你节省时间。5.1 问题驱动加载成功但/dev/ttyULx设备未出现可能原因1设备树节点状态或兼容性字符串错误。排查检查dmesg日志看是否有该地址设备的probe失败信息。确认compatible属性完全匹配内核驱动支持的字符串。检查status是否为“okay”。可能原因2内核配置未启用UARTLite驱动。排查确保内核编译时勾选了Device Drivers - Character devices - Serial drivers - Xilinx uartlite serial port support。可能原因3地址冲突或错误。排查这是最常见的原因。反复核对设备树中的reg属性地址必须与Vivado Address Editor中分配的完全一致包括地址长度。一个字节都不能错。5.2 问题能打开设备但收发数据全为乱码或根本无数据可能原因1波特率不匹配。排查这是头号嫌疑犯。请进行三重核对Vivado中UARTLite IP配置的CLOCK频率如50MHz。设备树节点中clock-frequency属性值必须与第1点相同。用户态程序如stty,picocom设置的波特率如115200与设备树中current-speed及IP配置的BAUD一致。计算验证UARTLite波特率计算公式为BAUD CLOCK / (16 * (Divisor 1))。你可以用这个公式反推实际波特率。例如CLOCK50MHz驱动设置的Divisor为27则实际波特率50,000,000 / (16*28) ≈ 111607与115200有偏差。确保CLOCK频率能被目标波特率整除以获得精确的波特率。可能原因2硬件引脚约束错误或电平不匹配。排查用示波器或逻辑分析仪测量TX引脚。当发送数据时应该有明显的波特率方波。如果没有检查XDC约束文件中的引脚号是否写错I/O标准如LVCMOS33是否与对接设备匹配。确保RX引脚在空闲时有稳定的高电平可通过外部上拉电阻实现。可能原因3中断问题。排查如果发送正常但接收不到可能是中断未正确触发。检查设备树中的interrupts属性特别是中断号是否正确。在/proc/interrupts文件中查看对应中断号如29是否有计数增加。如果没有说明中断未成功注册或触发。5.3 问题多路串口同时工作时系统响应变慢或丢失数据可能原因中断风暴。分析与解决如前所述多路高速UARTLite会产生大量中断。可以通过vmstat或mpstat命令观察系统中断数in列和CPU使用率。如果中断数极高且si软中断CPU使用率高基本可以确定。缓解方案降低波特率如果应用允许这是最直接的方法。优化软件使用非阻塞I/O和多路复用减少不必要的读操作和上下文切换。硬件升级考虑使用带FIFO或DMA功能的UART IP核如AXI UART16550可以大幅减少中断次数。5.4 实战心得版本管理与自动化保存好Vivado的xdc和tcl脚本引脚约束.xdc和创建Block Design的Tcl脚本非常重要。它们能确保你下次重建工程或移植到其他板卡时硬件配置的一致性。设备树片段化不要把所有9个串口节点都堆在主.dts文件里。可以为这些PL外设创建一个单独的.dtsi片段文件如pl-uartlites.dtsi然后在主文件中用#include引入。这样模块化管理更清晰。利用Vitis自动生成设备树节点在Vivado导出硬件后使用Vitis的“Create Device Tree”功能可以基于.hdf文件自动生成包含地址和中断信息的设备树节点框架大大减少手动编写出错的可能。但生成后仍需仔细核对时钟、波特率等参数。先单路再多路调试时强烈建议先只使能一路UARTLite从硬件约束到设备树到驱动测试确保这一路完全调通。然后再逐步添加其他路并注意修改地址和中断号。这种“增量式”调试能快速定位问题是共性还是个性。通过以上从Vivado硬件设计、设备树配置到Linux驱动测试的完整流程你应该能够独立完成Zynq PL侧多路UARTLite串口的扩展。这个方案成本低、灵活性高非常适合需要大量低速串行通信的嵌入式应用。记住耐心和细致的核对是成功的关键尤其是在地址、时钟、中断这三个核心配置上多花一分钟检查可能就能省下几小时的调试时间。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门