资讯详情

ZYNQ EMIO调试UART完整指南:从引脚规划到串口实测

📅 2026/10/5 11:16:38 | 华诺云谱 👁 阅读
ZYNQ EMIO调试UART完整指南:从引脚规划到串口实测
ZYNQ 开发里有两件事几乎绕不开一件是调试一件是串口。做调试离不开 UART做 UART 调试又绕不开 MIO 和 EMIO 的选择。我最早做 ZYNQ 的时候习惯直接用 PS 端 MIO 接出来的 UART0板子一上电就能在串口终端里看到 BootROM 打印省心得很。后来遇到一个项目MIO 引脚全被分配给了 NAND、SDIO、以太网剩出来的引脚下不了几个外设只能在 PL 侧想办法这才认认真真把 EMIO 这条路线研究了一遍。这篇就以“基于 ZYNQ 的 EMIO 调试 UART 实验”为线索把从硬件引脚规划、Vivado Block Design 配置、PS 侧软件编程到串口实测验证的完整过程拆开来讲顺便把我踩过的一些坑也一并写出来给准备入坑 ZYNQ 的工程师做个参考。这个实验说白了一句话把 PS 端 UART1 的 TX/RX 信号通过 EMIO 引到 PL 内部再从 PL 的通用 IO 引脚接到外部 USB 转串口芯片最终在电脑串口终端上看到 ZYNQ 打印的信息。适合刚接触 ZYNQ 的嵌入式工程师也适合已经会用 MIO 调试、但想在 PL 侧释放更多引脚、或者想把串口引脚位置改得更灵活的朋友。1. 为什么要把UART从MIO挪到EMIO1.1 MIO和EMIO到底差在哪很多第一次接触 ZYNQ 的人会有一个疑问MIO 和 EMIO 不都是引脚吗为什么还要分两套这个理解其实不太准确。MIO 的全称是 Multiplexed I/O属于 PS 处理系统专用的多功能引脚一共 54 根分布在 Bank 0、Bank 1、Bank 2 三个电压域可以直接接 SPI、I2C、UART、GPIO、SDIO、USB 这些 PS 内置外设。MIO 的最大特点就是一旦复位启动这些控制器就能独立工作不需要 PL 参与也不需要配置 FPGA 逻辑。EMIO 就不一样了。EMIO 的“E”是 Extended它的本质不是真正的物理引脚而是从 PS 外设控制器延伸出来的一根信号线从 PS 侧走到了 PL 侧再通过 PL 内部的布线逻辑接到芯片封装上的某个引脚。换句话说MIO 是 PS 自己的专属通道EMIO 是 PS 借用 FPGA 的资源往外送信号。用生活里的例子来类比MIO 有点像小区里每户提前留好的固定车位位置、大小都是设计好的你只要把车开进去就行EMIO 则是从家里修到广场的一条路到了广场之后你再用临时地锁占一个车位。前者省事但位置固定后者多几步路但车位随便挑。所以 MIO 和 EMIO 的本质区别可以归纳成三条引脚来源不同MIO 是 PS 固定引脚EMIO 是借助 PL 引脚输出的虚拟通路控制时序不同MIO 完全由 PS 控制上电即可工作EMIO 需要 PL 完成编程后信号才有效使用灵活性不同MIO 引脚位置固定数量最多 54 个EMIO 可以接到任意合适的 PL Bank数量也随封装引脚扩展。1.2 EMIO_UART这条通路完整走一遍明白了基本区别再看看 UART 信号走 EMIO 时到底经过了哪些环节。以 ZYNQ-7000 的 PS UART1 为例当你在 Vivado 的 ZYNQ7 Processing System IP 里把 UART1 接口类型从 MIO 改成 EMIO 时IP 的图形化界面上会多出两个端口UART1_TX 和 UART1_RX还可能看到 UART1_RTS 和 UART1_CTS 等流控信号但这些要配合 Modem 控制使用实验里通常不用管只做最简单的两线收发即可。这两个端口从 Block Design 引出去之后需要在顶层 Verilog 或 VHDL 模块里定义成顶层端口再通过 XDC 约束文件把端口映射到 FPGA 封装引脚。外部硬件电路里ZYNQ PL 引脚输出的是 1.8V/2.5V/3.3V 等可配置电平而 USB 转串口芯片比如 FT232R、CP2104一般工作在 3.3V TTL因此要保证电平一致同时 TX 和 RX 要交叉连接ZYNQ 的 TX 接芯片的 RXZYNQ 的 RX 接芯片的 TX。信号从 PS 的 UART 控制器出发经过 EMIO 进入 PL 的布线网络再从封装引脚输出到板级电路最终到电脑串口终端。理解了这条通路后面排查问题就有一个清晰的框架先看 PS 配置对不对再看 EMIO 端口有没有引出来然后看引脚约束对不对最后检查外部硬件连接按链路一层一层找。2. 硬件准备与引脚规划2.1 最小硬件连接做这个实验硬件上其实很简单不需要太多外围电路。我用的板子是常见的 ZYNQ-7020 开发板芯片型号 XC7Z020CLG400-2板上有一颗 USB 转串口芯片通过跳线可以选择接到 PS 的 MIO14/MIO15也可以接到 PL 的某些引脚。实验里就是要选择接到 PL 那一路让 UART1 走 EMIO。如果你手里的板子没有现成的 EMIO 到 USB 转串口通道也有办法找一颗 3.3V TTL 电平的 USB 转串口小板但这里要特别提醒一句市面上很多 USB 转 TTL 模块标注支持 3.3V实际并不是所有板子电平都标准用万用表测一下模块 VCC 和 TX 输出电平最稳妥。连线上面ZYNQ 和 USB 转串口模块之间ZYNQ 的 UART1_TX 接到模块的 RXZYNQ 的 UART1_RX 接到模块的 TX两边的 GND 必须连在一起这是串口通信能正常工作的前提。电平上ZYNQ PL 侧的 Bank 电压决定了引脚输出电平如果你把 UART 引脚分配在 3.3V 的 Bank比如 Bank 34 或 Bank 35那么直接接 3.3V TTL 的 USB 转串口模块就没有问题。如果分配在 1.8V Bank就要加电平转换不建议新手一开始就去碰跨电压域的设计先把 3.3V 跑通再说。2.2 引脚分配与电平匹配ZYNQ 的 PL 引脚按 Bank 划分每个 Bank 的供电电压独立HS BankHigh Speed供电范围通常在 1.2V 到 1.8VHR BankHigh Range可以支持 1.2V 到 3.3V。XC7Z020CLG400 这颗芯片PL 侧有 Bank 13、Bank 34、Bank 35 等其中 Bank 34 和 Bank 35 是 HR Bank支持 3.3V所以做 UART 引脚分配时优先选这两个 Bank。引脚分配要考虑的另一个因素是这个引脚会不会和板上其他资源冲突。比如某些开发板上 Bank 34 的引脚已经有按键、LED 或者 LCD 接口你如果直接拿去接 UART可能会影响其他外设或者被板上的上拉电阻干扰。最靠谱的做法是打开开发板的原理图查一下哪些引脚空闲、哪些引脚连到了 USB 转串口芯片的切换开关上记录下对应的 FPGA 引脚编号再写进约束文件。引脚编号查起来也很简单Vivado 里打开 Package Pin View或者查看器件封装图找到你选的 Bank 范围内没有连接任何外设的引脚或者板载原理图标注为串口相关的那几个引脚。通常开发板原理图会标注类似“UART1_TX_PL”直接对应到 FPGA Pin Name把这个名字抄下来就行。2.3 XDC约束怎么写拿到引脚编号之后XDC 约束文件的写法很简单核心就两行set_property PACKAGE_PIN D12 [get_ports {uart1_tx}] set_property PACKAGE_PIN C11 [get_ports {uart1_rx}]我这里 D12 和 C11 只是举例不同板卡引脚位置差别很大一定要参考你自己板子的原理图。除了引脚位置建议加上 IO 电平约束明确写成 3.3Vset_property PACKAGE_PIN D12 [get_ports {uart1_tx}] set_property PACKAGE_PIN C11 [get_ports {uart1_rx}] set_property IOSTANDARD LVCMOS33 [get_ports {uart1_tx}] set_property IOSTANDARD LVCMOS33 [get_ports {uart1_rx}]这里有个细节你的顶层端口名必须和 Block Design 里 Make External 后的端口名保持一致。比如你在 Vivado 的 Block Design 里把 UART1_TX 引出来端口名可能自动叫 uart1_rtl_0_tx如果你在 XDC 里写get_ports {uart1_tx}那编译肯定会报错。所以我的习惯是先在工程里完成 Block Design 引线生成 HDL Wrapper打开顶层文件看清楚端口名再写 XDC这样能避免低级错误。还有一个实操经验约束文件里最好把 TX 和 RX 分成两个端口名不要自己造一个位宽为 2 的端口。分开命名的好处是时序报告里看得清楚而且出问题的时候可以在 Vivado 的 I/O Planning 界面里直接看到两个引脚的分配状态。3. Vivado工程搭建与PS侧配置3.1 建Block Design并配置PS这个实验我用的是 Vivado 2023.1流程上各个版本基本一致。新建一个 RTL 工程器件选择 XC7Z020CLG400-2然后在 IP Catalog 里搜索并添加 ZYNQ7 Processing System。添加之后ZYNQ IP 会自动弹出一个配置界面也可以双击 IP 打开 Re-customize IP。第一次双击时如果 IP 显示需要重新关联不要慌按提示执行Generate Output Products和Reset操作。配置 PS 的时候最关键的一步在 “PS-PL Configuration” - “Peripheral I/O Pins” 界面里左侧列表会显示所有 PS 外设对应的引脚使用情况。默认情况下 UART1 可能被分配到了 MIO 14/15你要手动找到 UART1把它的引脚分配从 MIO 改成 EMIO。操作方式是在 UART1 那一行的下拉列表里选择“EMIO”这时右侧图形化界面里 UART1 的引脚图会消失取而代之的是 PS 和 PL 之间出现一组新连接线。在同一个界面里UART0 那边如果你暂时用不到建议直接取消勾选避免后续 SDK 阶段标准输入输出把串口默认关联到 UART0造成打印信息发不到 EMIO 那一路的混乱。Clock Configuration 里PS 的 UART 时钟单独有一个设置项通常在 “Input Clocks” 或者 “Peripheral Clocks” 下面默认可能是 50MHz有的版本显示 100MHz。这个时钟频率很关键后续波特率能不能分频到 115200就取决于它。我个人习惯地把 UART 时钟设置为 100MHz因为 100MHz 对常见波特率的分频误差都很小而且 Vivado 的 IP 配置界面会直接提示你能支持哪些波特率哪个看着舒服用哪个。不过 50MHz 也没问题计算分频时用 50MHz 除 16 再除 115200结果是 27.12取整后误差在可接受范围内但不如 100MHz 直观。3.2 把UART1引出到PL端口配置完 PS IP 后回到 Block Design 画布。你会看到 ZYNQ7 Processing System IP 的外围多了一些端口找到 UART1_TX 和 UART1_RX这两个名字可能因为版本不同略有差异但特征很明确。在端口上右键选择 “Make External”Vivado 会自动生成两个外部端口默认名字可能是UART1_TX_0和UART1_RX_0。这样的端口名如果直接用于顶层会有随机后缀工程整洁度不高。我的做法是选中外部端口把名字改成uart1_tx和uart1_rx方便后续约束和顶层代码阅读。如果你还需要 RTS/CTS 做流控实验也可以一并引出来但本实验用不到忽略即可。这里有一个容易踩的坑Block Design 里如果 UART1 配置成了 EMIO但画布中你没有把这些端口 Make External编译时报告可能不会直接报错而是会在实现阶段提示某些端口没有连接。最后的结果就是 bit 文件虽然生成成功但上板以后 UART 完全没有输出。所以生成 bitstream 之前一定要回到 Block Design 里确认uart1_tx和uart1_rx是 external 或至少连接到了顶层端口。3.3 综合、实现与导出XSABlock Design 部分完成后右键设计文件选择 “Create HDL Wrapper”Vivado 会自动生成一个顶层 Verilog 文件里面例化了我们的 Block Design同时把uart1_tx和uart1_rx以端口形式暴露出来。打开这个顶层文件确认端口声明就可以把之前准备好的 XDC 约束文件加入到工程里。接下来按顺序执行综合和实现这一步和普通 FPGA 开发没有区别。综合完成后查看一下 I/O Ports 报告确认 uart1_tx 和 uart1_rx 的引脚、电平、Bank 电压是否符合预期。如果引脚分配冲突Vivado 在实现阶段会报错提示引脚被占用这时候就要回到 XDC 里换引脚或者调整 Bank 规划。生成 bitstream 之后记得执行 “File - Export Hardware”导出时要勾选 “Include bitstream”得到.xsa文件。这个文件会同时包含硬件平台信息、PS 配置信息和 PL 的 bit 流后面 Vitis 建平台工程时直接用它。导出时如果发现不能勾选包含 bitstream通常是因为还没生成 bit 或者实现过程中有 ERROR 没解决。另外我遇到过一种情况生成了 bit 流但导出的 xsa 少于预期大小上板后发现 PL 侧的 EMIO 引脚完全不工作。排查下来是导出时没有选中 bitstreamSDK 里加载的 PL 配置是空的。这个细节当时困扰了我大半天提醒大家导出后可以看一眼 xsa 文件的大小正常情况应该在几 MB 到十几 MB如果只有几百 KB多半是 bit 流没打进去。4. Vitis裸机软件与验证4.1 创建平台工程和应用工程拿到 XSA 文件之后工作重心从 Vivado 转移到 Vitis老版本叫 SDK。启动 Vitis工作空间自己选一个干净目录在 Platform 工程里选择刚才导出的 XSA 文件创建平台。创建成功后Vitis 会自动根据 XSA 里的 PS 配置生成 BSP包括 UART 驱动库。需要注意Vitis 的 Platform 工程里会列出一些默认的 BSP 设置其中有一项叫 “standalone” 或 “freertos”实验选择 standalone。BSP 支持的标准输入输出设备默认可能关联到某个 UART 实例这个一定要确认。进入 BSP 设置界面找到 Board Support Package Settings在 “stdout” 和 “stdin” 选项里如果列出了uart0和uart1你就要根据实验目标选择。因为咱们的 UART1 已经改成了 EMIO所以 stdout 和 stdin 都要选uart1。如果你之前在 Vivado 里还把 UART0 也勾选了且板载的 MIO14/15 那一路 USB 转串口也接好了那么返回到串口终端你会发现有一个串口在输出 BootROM 信息另一个串口才是你应用工程的打印信息。两个串口同时开着倒也没问题但初学者很容易搞懵所以我的建议是本实验直接把 UART0 关掉集中精力调试 EMIO 这一路。4.2 用XUartPs驱动发一串测试数据创建完平台工程和空的 Application 工程后Vitis 默认的 Hello World 模板已经是一个最简单的验证程序底层会自动调用 XUartPs 驱动完成初始化并输出字符串。在项目资源管理器里打开helloworld.c核心代码长这样#include xparameters.h #include xuartps.h #include xil_printf.h int main(void) { XUartPs UartInst; XUartPs_Config *Config; int Status; Config XUartPs_LookupConfig(XPAR_XUARTPS_1_DEVICE_ID); if (Config NULL) { return XST_FAILURE; } Status XUartPs_CfgInitialize(UartInst, Config, Config-BaseAddress); if (Status ! XST_SUCCESS) { return XST_FAILURE; } XUartPs_SetBaudRate(UartInst, 115200); xil_printf(EMIO UART Test\r\n); xil_printf(Hello from ZYNQ PL Pin\r\n); while (1) { // 可以在这里加回显逻辑 } return 0; }注意代码里的XPAR_XUARTPS_1_DEVICE_ID这个宏对应的是 PS 的 UART1 实例。如果你在 Vivado 里只使能了 UART1那么这个宏在 xparameters.h 里会有定义并且数值是 1。如果你只用了 UART0代码里就要改成XPAR_XUARTPS_0_DEVICE_ID。这是很多新手第一次移植代码时最容易出错的地方一旦宏写错运行时可能会抛出无法解析的外设中断或者初始化挂死。XUartPs_SetBaudRate这一步很重要驱动会根据 BSP 里的时钟频率自动计算分频系数。如果你前面的时钟配置是 100MHz这个函数内部会算出合适的分频值把波特率设成 115200。如果你把时钟配置改成了 50MHz函数也能工作但建议二次确认实际输出波特率没问题。串口调试助手的参数统一设置为 115200、8 位数据、无校验、1 位停止位。4.3 实测串口终端看到什么才算通把生成的 BOOT.BIN 或者直接通过 JTAG 下载运行看板子的 USB 转串口有没有打印。在 Vitis 里直接选择 “Run As - Launch on Hardware”Vivado 会先把 PL 配置到 FPGA 中然后下载程序。这个过程如果一切正常串口终端里应该会看到两行文字一行是 BootROM 或者 FSBL 的打印取决于启动模式另一行就是应用工程里的 “EMIO UART Test”。如果没有看到任何输出先别急着改代码。按这个顺序排查先看串口终端对应的 COM 口号是否正确有的 USB 转串口芯片有多个虚拟串口然后用示波器或万用表量 ZYNQ 的 TX 引脚有没有 3.3V 的电平跳变程序启动时如果发送了字符串TX 引脚一定会有波形如果没有波形再回到 Vivado 查 bit 流里的 I/O 约束。看到打印信息之后继续做一步回显验证。可以修改代码把收到的每一个字节都原样发回去或者做一些简单的命令交互。这样能确认 RX 方向也是通的而不仅仅是 TX 单向输出。我用的是中断方式在XUartPs_SetInterruptHandler里注册处理函数收到字符串后经过简单判断再打印状态但如果你只是做验证最简单的思路是在 while(1) 里用轮询方式查询 FIFOwhile (1) { while (XUartPs_IsReceiveData(0xE0001000)) { u8 data XUartPs_ReadReg(0xE0001000, XUARTPS_FIFO_OFFSET); XUartPs_WriteReg(0xE0001000, XUARTPS_FIFO_OFFSET, data); } }这段代码里的基地址0xE0001000是 ZYNQ UART1 的寄存器地址属于 PS 外设地址空间你要是看过 ZYNQ TRM 就知道UART0 和 UART1 的地址分别是 0xE0000000 和 0xE0001000。不过在工程里我不推荐直接写死用XPAR_XUARTPS_0_BASEADDR之类的宏更稳妥。轮询方式虽然简单但在没有中断时发送大量数据容易丢字节验证够用了。5. 常见问题与排查实录5.1 无输出、乱码、回显不正常的排查表这个实验做完之后我把实战中遇到的几个典型问题和排查思路整理成了一张表分享给大家。先看最典型的无输出问题原因往往是硬件层面不是软件现象可能原因排查方法完全没有输出引脚约束没生效打开 Vivado 实现报告确认 uart1_tx 引脚是否存在并正确分配完全没有输出Block Design 的 EMIO 没 Make External回到 Block Design 检查端口是否为 external重新生成 wrapper 和 bit 流完全没有输出顶层端口名字和 XDC 不匹配检查 wrapper 里的端口名大小写完全没有输出USB 转串口 TX/RX 接反交换 ZYNQ TX 和 RX 两根线完全没有输出两边 GND 没共地用万用表测 ZYNQ GND 与 USB 转串口模块 GND 是否连通输出乱码波特率不匹配确认调试助手参数是 115200-8-N-1输出乱码UART 输入时钟频率和驱动计算不一致确认 Vivado 里 UART 时钟配置并在 Vitis 里查看 BSP 中的CLOCK_FREQ宏能发不能收RX 引脚约束遗漏检查 uart1_rx 是否在 XDC 中分配并正确实现能发不能收外部芯片 TX 驱动能力不足用示波器测量接收端引脚电平确认有数据波形我印象最深的一次踩坑是 UART 输出全部是乱码而且首字符特别明显每次都是#开头。一开始我以为是板子坏了后来把 Vivado 里的 UART 时钟从 50MHz 改成 100MHz重新生成 bit 流之后乱码就消失了。问题根源就是 PS 配置里时钟和 BSP 里传递给驱动库的时钟参数不一致驱动用错误的时钟频率算了分频系数。所以建议养成一个习惯Vivado 里时钟配置成多少就在 BSP 和代码里核对一下对应宏别想当然。5.2 和MIO调试不一样的那些坑用 MIO 上的 UART 调试时其实很多问题是不会出现的。MIO 是 PS 固定引脚引脚位置固定、电气特性固定上电即用哪怕 PL 没有加载 bit 流串口也能打印 BootROM 信息。但 EMIO 方案下整个过程变成了“软硬件协同”于是多出几类特殊的坑。第一类是启动阶段的黑屏问题。如果你的板子启动模式设置为从 Quad-SPI 或 SD 启动BootROM 在启动早期其实使用的是 MIO 配置里和启动模式绑定的调试串口如果 UART1 变成了 EMIO那么 BootROM 阶段它无法通过 PL 输出打印必须等 FSBL 加载完 PL bit 流之后UART1 的 EMIO 通路才真正建立。所以你可能会发现启动过程中前面一段日志是空的等到某个时刻突然跳出一串字符这不是系统卡住了而是 EMIO 通路在 bit 流加载后才生效。第二类是复位时序问题。如果 PS 先启动、PL 后配置那么 PL 侧的引脚在未配置前是 Hi-Z 状态此时外部 USB 转串口芯片接收到的电平可能是不确定状态。有些串口芯片在 RX 悬空时会不断产生噪声字节UART 一恢复就可能进来一堆垃圾。解决方案是在外部加一个上拉电阻保证空闲时 TX 引脚是高电平。第三类是引脚复用的隐蔽冲突。PL 引脚不像 MIO 那样能通过引脚复用手册一眼看到全部占用有些开发板上一根引脚可能既连了按键又连了 LED还可能引到了扩展排针表面看不出来。我曾经把一组 EMIO UART 引脚分配到了一个同时连接着板载 I2C EEPROM 的 Bank 引脚上结果 UART 功能一切正常但 EEPROM 的读写就随机失败。这种情况不是 UART 本身的问题而是板级资源冲突务必对照原理图逐脚检查。5.3 Linux设备树和驱动层面的补充这个实验做完裸机验证之后如果打算把系统跑起来比如用 Petalinux 或者直接在 Linux 里使用这套 EMIO UART那情况又会有点不同。裸机场景下Vitis BSP 会自动根据 XSA 生成所有参数Linux 下UART 的控制器信息、引脚复用信息都要在设备树里体现。设备树里 ZYNQ 的 UART1 节点一般长这样uart1: seriale0001000 { compatible xlnx,xuartps; reg 0xE0001000 0x1000; interrupts 0 82 4; clocks clkc 24; clock-names uart_clk; status okay; };但上面的片段只是控制器层面EMIO 机制下还需要在引脚复用层面说明这些引脚的用途。ZYNQ 的 pinctrl 驱动里对于 EMIO 引脚的描述并没有一个标准化的写法通常你的 bit 流已经通过 XSA 配置好了 PL 逻辑Linux 侧只要保证 uart 节点 status 为 okay并且clkc里的 UART 参考时钟频率和你在 Vivado 里配置的一致即可。如果 Linux 启动后串口没有任何输出可以先查dmesg | grep ttyPS看驱动是否 probe 成功再确认uartclk是多少这个值来自设备树clocks和 Vivado 里的时钟设置必须同步。如果你想在 Linux 里用一个串口作为登录控制台还需要在chosen节点里指定stdout-path serial1:115200n8这里的 serial1 对应设备树里aliases的serial1 uart1。这种配置在 Petalinux 的system-user.dtsi里改起来比较方便但记得要做完整的构建生成新的 BOOT.BIN、boot.scr、image.ub再重新制作 SD 卡不然设备树变更不会生效。每次讲到 EMIO 调试 UART我都会提醒同事这个实验看着简单但它背后牵扯的知识点其实很密PS 配置、PL 约束、驱动参数、板级硬件、外加启动阶段的时序任何一环理解不到位就可能卡上半天。而一旦把这条路彻底走通你之后再去玩 ZYNQ 的 PS-PL 协同设计比如用 EMIO 扩展 GPIO、复用 PL 引脚做高速数据交互都会顺畅很多。我自己现在做新板卡调试时会特意在原理图阶段就给 EMIO UART 留出一对引脚并且做成跳线可切换这个习惯让我节省了大量调试时间也推荐给你试试。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑