ZYNQ 7020 UART0串口工程文件详解:从Vivado配置到裸机驱动实战
2026/9/23 12:55:00 网站建设 项目流程

简介:面向赛灵思ZYNQ 7020平台的UART0串口通信完整工程,专注处理器系统(PS)端外设的配置与调试,适合FPGA与嵌入式开发入门及进阶者直接使用。压缩包共544个文件、5.56MB,包含Vivado工程设计文件(工程文件、块设计、硬件定义)、C语言与头文件源码、串口驱动、软件开发套件工程及可执行文件等编译产物,同时保留自动化构建脚本、仿真文件和详尽的说明文档,便于完整导入工程并理解软硬件协同流程。目前已有3770人学习下载,被验证为可直接运行的全流程方案。资源基于Vivado 2018.2环境,涵盖串口IP核添加、MIO14与MIO15引脚约束、处理器初始化代码及串口收发测试程序,并附带底层库文件与配置脚本,可帮助使用者从零搭建设计环境,免去繁琐的项目配置,快速实现UART0数据收发。此外,工程内文件组织清晰,按模块划分明确,对学习ZYNQ异构计算、掌握PS与PL协同开发具有很好的参考价值。 如果你刚拿到一块ZYNQ 7020开发板,我猜你第一件事大概率是把UART0串口跑通——只有看到串口终端里蹦出字符,这块板子才算真正“活”了。从最早用Zynq-7000系列做项目到现在,我被问得最多的就是“有没有能直接用的UART0全工程文件”。网上不少教程要么停在旧版本Vivado,要么只丢一段main.c、缺了Block Design配置,新手照着做,卡在MIO引脚那一步就放弃了。

这篇文章就干一件事:把ZYNQ 7020(XC7Z020-CLG484)上UART0串口全工程文件的完整链路拆开讲透。从Vivado里的PS配置、MIO选型,到SDK/Vitis里的裸机驱动代码、收发验证方法,再到我实际踩过的一堆坑,全部给完整方案,照着做就能复现一个“可以直接使用”的串口工程。文章适合刚接触ZYNQ的硬件工程师、嵌入式软件开发者,也适合想拿现成工程做二次改造的老手。

1. 先聊为什么起点放UART0,而不是UART1或EMIO

1.1 ZYNQ 7020的串口资源全景

Zynq-7000系列的PS(Processing System)部分集成了两个完全一致的UART控制器:UART0和UART1。它们的内核逻辑一模一样,都是ARM PrimeCell PL011的授权实现,支持可编程波特率、5到8位数据位、奇偶校验、1或2个停止位,还有64字节的发送FIFO和64字节的接收FIFO。两个控制器都支持中断触发和DMA触发,也都能在轮询、中断、DMA这三种工作模式之间切换。

但这里有个新手最容易忽略的点:UART0和UART1的物理引脚映射完全不同,而且都走MIO(Multi-use I/O)通道,不占PL逻辑资源。UART0最常见的引脚是MIO 14(TXD)和MIO 15(RXD),UART1则默认落在MIO 48/49一带。绝大多数ZYNQ 7020开发板,比如正点原子、米联客、黑金这些主流板卡,板载USB转串口芯片都是直接连到UART0的MIO 14/15。所以选UART0做起点,本质上是顺着绝大多数硬件设计走。

1.2 UART0 / UART1 / EMIO 怎么选

经常有人问我:既然UART0和UART1功能一样,那用UART1行不行?当然行,但没必要给自己加戏。我自己做项目时的选择逻辑很简单:

对比维度UART0(MIO 14/15)UART1(MIO 48/49等)EMIO扩展串口
是否占用PL逻辑是,要消耗LUT引脚
开发板默认支持度极高,主流板卡默认较低,需查原理图需自己分配引脚和电平转换
启动早期打印FSBL阶段即可输出同左要等PL配置完成后才可用
驱动支持XUartPs驱动,BSP默认生成同左本质还是同一个驱动,但引脚下沉到PL
使用复杂度最低较高,涉及引脚约束、PL时钟

建议很直接:除非你的MIO 14/15被其他外设占了,或者你有特殊的多串口需求,否则第一块板子跑串口,一律选UART0。就连FSBL和U-Boot的默认调试串口也是UART0,把它作为“系统调试锚点”,后续无论是跑Linux还是调试PL逻辑,都能复用同一套串口基础设施。

1.3 MIO复用冲突

MIO不是一个“哪个空就插哪个”的概念。每个MIO引脚都有多种功能,比如MIO 14/15除了UART0,还可能被SDIO、SPI或CAN复用。实际工程里,如果Vivado的Block Design里同时勾选了SDIO和UART0,而两者争抢同一组MIO,工具会直接报错或者强制你重映射。

所以拿到任何一块新板子,第一件事不是打开Vivado,而是打开原理图,确认UART0电路实际接到了哪个MIO。绝大多数板卡是MIO 14/15,但我也见过个别板子放在MIO 10/11上。这个习惯能帮你省掉后面一整天的排查时间。

2. 全工程文件地图:Vivado与SDK两侧各有什么

2.1 Vivado侧的关键文件

一份能直接用的UART0全工程文件,本质上包含两套东西:一套是Vivado硬件工程,一套是SDK(新一代叫Vitis)软件工程。硬件工程的核心不是那些综合报告,而是Block Design。

一个路径大概是这样的:

vivado/ ├── zynq_uart0.xpr # Vivado工程入口 ├── srcs/ │ ├── bd/ │ │ └── system/ │ │ ├── system.bd # Block Design,核心中的核心 │ │ └── hdl/ │ │ └── system_wrapper.v # 顶层HDL包装 │ └── constrs/ │ └── system.xdc # 约束文件,PS-only设计基本用不到 └── zynq_uart0.runs/ # 综合、实现、比特流结果

system.bd这个文件里存放了ZYNQ7 Processing System IP的完整配置:UART0引脚、DDR颗粒参数、时钟频率、启动模式。你双击这个IP看到的所有配置,都存在这里面。如果工程文件能在你的Vivado版本里正常打开,那么所有关键配置都会自动恢复。

这里特别提一下DDR配置。很多人以为跑个串口程序用不到DDR,但其实SDK裸机程序默认就链接到DDR地址空间,PS启动第一步就要完成DDR初始化。如果system.bd里的DDR颗粒型号、位宽、速度等级和你的板卡对不上,程序大概率卡死在启动早期,串口根本来不及打印。所以看到全工程文件时,先检查DDR配置是否匹配板子,比急着看main.c重要得多。

2.2 SDK/Vitis侧的关键文件

硬件工程导出的.hdf文件是连接Vivado和软件开发的桥梁。在SDK里导入硬件平台后,会自动生成BSP(板级支持包),里面最核心的文件包括:

文件/目录作用
.hdf硬件平台描述,包含PS配置与比特流
uart0_bsp/板级支持包,内含xuartps驱动
uart0_bsp/ps7_cortexa9_0/include/xparameters.h外设基地址、设备ID、时钟频率宏定义
uart0_app/src/helloworld.c应用程序入口,串口收发逻辑所在
uart0_app/src/lscript.ld链接脚本,决定代码跑在DDR还是OCM

xparameters.h是排查问题的第一站。你会在里面看到类似这样的定义:

#define XPAR_XUARTPS_0_DEVICE_ID 0 #define XPAR_XUARTPS_0_BASEADDR 0xE0000000 #define XPAR_XUARTPS_0_CLOCK_HZ 50000000

BASEADDR是UART0控制器在PS地址空间的基地址,CLOCK_HZ是UART参考时钟频率。如果这两个值和Vivado里的实际配置不一致,波特率计算就会出问题。看到全工程后,先打开这个文件核对一遍。

2.3 拿到工程后先做什么

我最反感那种拿到工程包就一顿“Next”下载完不去看的操作方式。全工程文件虽然可以直接用,但前提是硬件环境匹配。我的建议流程分三步:

  1. 检查版本:低版本Vivado打不开高版本工程文件,高版本打开低版本有报错风险。确认工程是由哪个版本创建的。
  2. 检查器件:确认工程器件型号是xc7z020clg484-1,而不是zcu104或者别的。
  3. 检查DDR和UART0引脚:对照板卡原理图,确认DDR颗粒型号和UART0实际MIO引脚是否一致。

这三步只要有一个不匹配,直接下载大概率翻车,而翻车时串口通常“安静如鸡”,没有任何报错可看。

3. 波特率生成与初始化代码:从时钟树到FIFO的完整链路

3.1 时钟到底怎么来的

很多人把波特率配置当成一个黑盒:调用XUartPs_SetBaudRate就完事了。但真出问题时,你还是要懂底层的来龙去脉。

ZYNQ 7020的PS_CLK通常接33.333MHz晶振,这个时钟进到PS内部的PLL(锁相环)后倍频,再分频出各个外设的参考时钟。UART的参考时钟在Vivado的ZYNQ7 PS配置项里可以单独设,常见的默认值是50MHz。这个值会跟随硬件导出到SDK,最后体现在xparameters.h的CLOCK_HZ宏里。

而ARM PL011的波特率生成公式是:

Baud = RefClk / (16 × Divisor)

其中Divisor由整数分频IBRD和小数分频FBRD组合而成。以RefClk=50MHz、Baud=115200为例:

Divisor = 50000000 / (16 × 115200) ≈ 27.1267 IBRD = 27 FBRD = round(0.1267 × 64) = 8 实际波特率 = 50000000 / (16 × (27 + 8/64)) ≈ 115207.4 误差 ≈ 0.006%

这个误差完全在UART的容忍范围内,所以115200这个常用波特率在50MHz参考时钟下很稳。反过来,如果UART参考时钟是33.333MHz或者别的频率,误差就可能是另一个故事了。排查乱码问题时,第一步就该算一下参考时钟和波特率是否匹配。

3.2 初始化代码逐段拆解

全工程里main.c的初始化部分大概长这样,我加上了注释:

#include "xuartps.h" #include "xparameters.h" #include "xil_printf.h" #define UART_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID static XUartPs UartInst; int UartInit(void) { XUartPs_Config *Config; int Status; /* 1. 从配置表里找到UART0的配置信息 */ Config = XUartPs_LookupConfig(UART_DEVICE_ID); if (Config == NULL) { return XST_FAILURE; } /* 2. 把硬件配置写入驱动实例,完成初始化 */ Status = XUartPs_CfgInitialize(&UartInst, Config, Config->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* 3. 设置波特率,内部会计算IBRD/FBRD并写寄存器 */ Status = XUartPs_SetBaudRate(&UartInst, 115200); if (Status != XST_SUCCESS) { return XST_FAILURE; } /* 4. 设置正常工作模式,不带流控 */ XUartPs_SetOperMode(&UartInst, XUARTPS_OPER_MODE_NORMAL); return XST_SUCCESS; }

这三步顺序不能乱:LookupConfig从静态配置表里拿地址和设备ID,CfgInitialize把控制器复位并配置基本参数,SetBaudRate才真正修改波特率寄存器。如果你先调SetBaudRate再CfgInitialize,后者的初始化动作会把你刚配好的波特率寄存器重写一遍。

3.3 收发模式怎么选:轮询、中断还是DMA

全工程默认用轮询模式,这是最不容易出错的方案。发送用XUartPs_Send,接收用XUartPs_IsReceiveData判断FIFO是否非空,再用XUartPs_ReadReg从FIFO读数据。

带一个可靠发送的发送函数,我用的是这种写法:

void UartSendString(const char *str) { int len = strlen(str); int sent = 0; while (sent < len) { sent += XUartPs_Send(&UartInst, (u8 *)str + sent, len - sent); } }

XUartPs_Send内部是把数据往TX FIFO里塞,FIFO满时会等待。我这个循环写法是保险起见,如果驱动因为异常只发送了一部分,剩余数据还能继续补发。

接收轮询也很简单:

int UartPollReceive(u8 *buf, int max_len) { int count = 0; while (count < max_len && XUartPs_IsReceiveData(&UartInst)) { u8 ch = XUartPs_ReadReg(UartInst.Config.BaseAddress, XUARTPS_FIFO_OFFSET); buf[count++] = ch; } return count; }

轮询的缺点是CPU全程都在“等”,不适合大数据流。如果后续要做高吞吐或者实时响应,再改中断模式。中断模式下UART0的SPI中断号是82,需要配合XScuGic中断控制器使用,在事件回调函数里处理XUARTPS_EVENT_RECV_DATA和XUARTPS_EVENT_RECV_TOUT。全工程文件里我保留了中断模式的基础代码,但默认不启用,目的是让新手先跑通轮询链路,再逐步增加复杂度。

4. 实测验证:回环测试、收发确认与“假通”的识别

4.1 硬件准备与连接

在把工程下载到板子上之前,先做好硬件准备。你需要一根USB转串口线,或者直接用开发板自带的USB转串口接口。大多数板卡的Micro USB口附近会印有UART或者UART0丝印,插上后电脑会枚举出一个COM口。

第一板跑通前,建议做一个最简单的回环测试:用杜邦线或跳线帽把UART0的TXD和RXD短接。这样板子发出的数据会直接从自己的RX脚收回来,通过回环是否成功可以快速判断UART0控制器本身工作是否正常,还能排除外部串口线和电平转换芯片的干扰。

4.2 上位机软件怎么配

串口助手的选择很多,SecureCRT、MobaXterm、Putty,或者国产的友善串口助手都可以。关键是参数不要配错:

  • 波特率:115200
  • 数据位:8
  • 停止位:1
  • 校验位:None
  • 流控:关闭(这一步极其关键,很多板子翻车都是因为默认勾选了RTS/CTS)

打开串口前,确认COM口号没有被其他软件占用。Windows设备管理器里如果出现黄色感叹号,先装USB转串口芯片的驱动,主流板卡常用CP210x或FT232。

4.3 回环测试代码与判定标准

全工程里加了个隐藏彩蛋:默认跑的不是Hello World,而是一段回环测试。程序上电后先通过UART0发送一行ASCII字符,然后进入回环等待状态,把收到的每个字节原样回发。

核心代码就是上面那个轮询循环,主函数里长这样:

#include "sleep.h" int main(void) { UartInit(); UartSendString("ZYNQ7020_UART0_LOOPBACK\r\n"); while (1) { u8 rx_buf[64]; int recv_count = UartPollReceive(rx_buf, 64); if (recv_count > 0) { UartSendString("\r\n[ECHO] "); XUartPs_Send(&UartInst, rx_buf, recv_count); UartSendString("\r\n"); } } return 0; }

测试流程是这样的:

  1. 先什么都不动,上电,看串口终端有没有打印ZYNQ7020_UART0_LOOPBACK
  2. 有打印,说明UART0初始化、发送链路、上位机接收这个闭环是通的。
  3. 然后手动发一条字符串,比如hello,看能不能收到[ECHO] hello回显。
  4. 如果发送和回显都正常,说明接收链路也通。

如果上电后完全没有打印,不要急着改代码。先用万用表或示波器量一下板卡上UART0 TXD引脚的静态电平,如果一直是高电平(3.3V),说明UART空闲状态正常;如果有波形,恭喜你,硬件其实在工作,问题大概率在MIO配置或波特率。

4.4 “假通”怎么分辨

我在帮人排错时经常遇到一种情况:串口能打开、能显示,甚至能看到板子发的数据,但内容全是乱码。这种“假通”最迷惑人。

乱码的排查优先级是:

  1. 波特率不对(最常见,尤其上位机和板子一个设115200、一个设9600)。
  2. UART参考时钟和BSP配置不一致。
  3. 电平不匹配,比如板子TXD是1.8V电平,转串口芯片不支持。
  4. 接地问题,USB转串口线和板子没有共地。

用示波器看TXD引脚输出波形,是区分这些原因最快的手段。空闲高电平、起始位低电平、8个数据位、停止位高电平,对着ASCII表一比对,就知道波特率和数据格式对不对了。

5. 排坑记录:五个让串口“哑火”的细节,我都踩过

5.1 MIO配成了EMIO,串口静悄悄的

第一次踩这个坑是在帮朋友调一块定制板,SDK程序编译下载一次过,但串口就是没输出。查了半天,发现Vivado的Block Design里,UART0的引脚选项被改成了EMIO,而不是MIO 14/15。EMIO意思是引脚从PS引到PL再通过PL引脚出去,但PL侧的引脚约束和电平转换电路根本没做,数据自然出不来。

排查方法很简单:双击Vivado里的ZYNQ7 PS IP,打开Peripheral I/O Pins页面,看UART0那一栏选的是MIO几还是EMIO。选成MIO 14/15,重新生成比特流、

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询