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挪到EMIO
1.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 接芯片的 RX,ZYNQ 的 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 接到模块的 RX;
- ZYNQ 的 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 Bank(High Speed)供电范围通常在 1.2V 到 1.8V,HR Bank(High 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.3V:
set_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 综合、实现与导出XSA
Block 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 引脚完全不工作。排查下来是导出时没有选中 bitstream,SDK 里加载的 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) 里用轮询方式查询 FIFO:
while (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: serial@e0001000 { 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 留出一对引脚并且做成跳线可切换,这个习惯让我节省了大量调试时间,也推荐给你试试。