ARTICLE DETAIL

资讯详情

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

ZYNQ7020无UART调试:用CoreSight ITM重定向xil_printf打印

ZYNQ7020无UART调试:用CoreSight ITM重定向xil_printf打印 1. 项目背景与需求拆解1.1 为什么会出现“没有Uart硬件”的调试困境ZYNQ7020 这类 SoC 在板级设计阶段PS 端的 UART 引脚通常会被规划到 MIO 或者 EMIO 上再经过板载的 USB-UART 桥片比如 FT232R、CP2102N、FT231X 这类常见型号引出一个调试串口。正常流程下我们在 Vivado 里勾选 UART1分配好 MIO 引脚导出硬件到 SDK/Vitis然后在代码里调用printf或者xil_printf打开串口终端就能看到打印信息。但实际项目里经常遇到几种尴尬情况板子是自己画的第一版硬件忘了把 UART 引出来或者 UART 引脚被其他功能占用了比如拿去做 RS485 控制或者跟别的模块通信又或者手头这块核心板只引出了 JTAG没有调试串口。这时候代码跑起来了但完全不知道程序走到哪一步、变量是什么值调试基本靠猜。我遇到过最典型的一次是帮朋友看一块 ZYNQ7020 的工控板板子上只有一个 JTAG 接口UART 被拿去做 Modbus 了。程序烧进去之后没有任何输出只能靠 LED 判断状态效率极低。后来就是用 CoreSight 的 ITM/SWO 机制配合xil_printf把打印重定向出来才把问题定位清楚。1.2 核心思路把打印重定向到 CoreSightZYNQ7020 的 PS 端是双核 Cortex-A9ARM 架构自带 CoreSight 调试组件其中就包括 ITMInstrumentation Trace Macrocell。ITM 可以把程序里的打印信息通过 SWOSingle Wire Output引脚或者 TPIU 送出来再经过 JTAG 调试器传到主机端。Xilinx 的 SDK/Vitis 里已经封装好了xil_printf的底层实现我们只需要把它的输出目标从 UART 改成 ITM就能在没有物理串口的情况下看到打印。这个方案的好处是不占用任何 PS 外设资源不需要改硬件也不需要额外的引脚。只要 JTAG 能连上调试信息就能出来。对于 ZYNQ7020 这种资源紧张、引脚复用频繁的芯片来说非常实用。1.3 适用人群与前置条件这篇内容适合正在用 Vivado 2019.1 开发 ZYNQ7020 的工程师尤其是遇到板子没有调试串口、或者串口被占用的情况。你需要具备基本的 Vivado 工程创建能力、SDK 使用经验以及对 ZYNQ PS 端外设配置的初步了解。前置条件包括Vivado 2019.1 已安装并能正常打开工程ZYNQ7020 的 JTAG 连接正常能在 Hardware Manager 里识别到设备SDK 能正常创建和编译应用工程。如果这些还没搞定建议先把基础环境跑通再来看打印重定向的部分。注意本文所有操作基于 Vivado 2019.1 SDK 环境Vitis 的配置方式略有不同但核心原理一致。2. 环境准备与工程配置2.1 Vivado 工程中 PS 端的关键设置打开你的 Vivado 工程进入 Block Design双击 ZYNQ7 Processing System 进入配置界面。在 PS-PL Configuration 里找到 UART 相关的选项。如果你确实没有 UART 硬件这里可以不勾选任何 UART或者保留默认的 UART1 但不分配引脚。关键是不要因为 UART 没接就报错Vivado 本身不会强制你必须有 UART。接下来重点看Debug选项卡。在 ZYNQ7 Processing System 的配置里有一个 “Debug and Trace” 区域。确保Debug相关的选项是使能的特别是Trace部分。对于 Cortex-A9需要使能TPIU和ITM相关的信号。具体来说在 “Debug and Trace” 里勾选Trace Enable并确保Trace Port选择的是SWO或者TPIU模式。如果你用的是 Xilinx 官方的 JTAG 调试器通常选 SWO 就能满足 ITM 打印的需求。这里有个细节ZYNQ7020 的 CoreSight 调试信号是通过 EMIO 引到 PL 端再连到 JTAG 的。所以在 Block Design 里你会看到 ZYNQ7 Processing System 会多出一些调试相关的接口比如TRACE_*信号。这些信号需要正确连接到顶层或者至少保持默认的连接方式。如果你在 Block Design 里看到这些信号是悬空的问题也不大因为 JTAG 调试通道本身是独立的ITM 的数据可以通过 JTAG 的 TPIU 通道传出来不一定非要走物理的 SWO 引脚。2.2 生成 Bitstream 与导出硬件配置完成后按照正常流程生成 Bitstream。这里可能会遇到 “Implement Design 变红” 的情况常见原因是引脚约束冲突或者时序不满足。如果你没有 UART 引脚约束自然不会因为 UART 报错。但如果你之前有 UART 约束现在要去掉记得在 XDC 文件里把对应的set_property PACKAGE_PIN和set_property IOSTANDARD注释掉或删除。生成 Bitstream 成功后通过File - Export - Export Hardware导出硬件。注意勾选Include bitstream这样 SDK 里才能正确识别到调试配置。导出的文件通常是.hdf格式Vivado 2019.1 默认就是这个格式。2.3 SDK 工程创建与 BSP 设置打开 SDK新建一个 Application Project选择刚才导出的硬件平台。在创建工程时SDK 会自动生成一个 BSPBoard Support Package。BSP 里包含了xil_printf的底层实现我们需要修改的就是这部分。在 BSP 工程上右键选择Board Support Package Settings。在standalone配置里找到stdout和stdin的设置。默认情况下stdout 是ps7_uart_1或者类似的 UART 外设。如果你没有 UART这里可能显示为none或者直接报错。我们需要把它改成coresight相关的选项。在 Vivado 2019.1 的 BSP 设置里stdout 的下拉菜单里通常会有ps7_coresight_0或者coresight这样的选项。选中它保存设置。这一步非常关键。如果 BSP 里没有 coresight 选项说明你的硬件导出时没有使能 Debug and Trace。回到 Vivado 检查 PS 配置确保 Trace 相关选项已勾选重新导出硬件再在 SDK 里更新 BSP。提示修改 BSP 设置后SDK 会自动重新编译 BSP。如果编译报错检查一下是不是有多个 stdout 设备冲突或者 coresight 驱动没有正确包含。3. xil_printf 重定向的核心原理与代码实现3.1 xil_printf 的底层调用链xil_printf是 Xilinx 提供的一个轻量级打印函数相比标准 C 库的printf它去掉了浮点支持和一些格式化功能代码体积更小适合嵌入式环境。它的底层最终会调用outbyte或者_out_byte这样的函数把字符一个一个送出去。在 BSP 里outbyte的实现会根据 stdout 的配置选择不同的外设。如果 stdout 是 UARToutbyte就会往 UART 的 FIFO 里写数据如果 stdout 是 coresightoutbyte就会往 ITM 的寄存器里写数据。所以理论上只要 BSP 设置正确xil_printf会自动走 ITM 通道不需要我们手动改代码。但实际测试中有时候 BSP 设置改了xil_printf还是没输出。这通常是因为 BSP 的驱动没有正确初始化或者 ITM 的寄存器地址映射有问题。这时候就需要手动干预一下。3.2 手动实现 ITM 打印函数如果 BSP 的 coresight 选项不生效我们可以自己写一个outbyte的替代函数直接操作 ITM 寄存器。Cortex-A9 的 ITM 寄存器地址在 ZYNQ7020 的技术参考手册里有定义通常 ITM 的 Stimulus Port 寄存器基地址是0xF8800000左右。具体来说ITM 有 32 个 Stimulus Port每个 Port 对应一个 32 位寄存器。我们常用 Port 0 来输出字符。下面是一个简单的 ITM 发送字符的函数#define ITM_STIM_PORT0 (*(volatile unsigned int *)0xF8800000) void itm_send_char(char c) { if (c \n) { itm_send_char(\r); } while (ITM_STIM_PORT0 0); ITM_STIM_PORT0 c; } void itm_send_string(const char *s) { while (*s) { itm_send_char(*s); } }这个函数的逻辑很简单先检查 ITM 的 Stimulus Port 0 是否 ready寄存器值不为 0 表示可以写入然后把字符写进去。如果是换行符先补一个回车这样在终端里显示才正常。然后我们可以重写outbytevoid outbyte(char c) { itm_send_char(c); }把这段代码放到你的应用工程里编译链接。注意outbyte在 BSP 里可能已经被定义过了直接重定义会报重复符号。解决办法是在 BSP 设置里把 stdout 改成none然后自己实现outbyte。或者用--wrap链接选项把原来的outbyte替换掉。3.3 在 SDK 中配置 ITM 终端代码写好了还需要在 SDK 里打开 ITM 终端才能看到输出。在 SDK 的菜单栏里找到Window - Show View - Other然后搜索ITM打开ITM Console或者SWV相关的视图。不同版本的 SDK 叫法可能不一样Vivado 2019.1 里通常叫Xilinx System Debugger或者ITM Console。打开 ITM 终端后需要配置一下选择正确的 JTAG 设备设置 ITM 的 Stimulus Port 为 0波特率一般不用管因为 ITM 是走 JTAG 时钟的。然后运行程序如果一切正常你就能在 ITM 终端里看到xil_printf的输出。注意ITM 终端需要在程序运行之前就打开并配置好否则可能会错过早期的打印信息。另外ITM 的打印速度受 JTAG 时钟影响如果打印量很大可能会丢字符建议在关键位置打印不要刷屏。4. 实操流程与关键环节记录4.1 完整操作步骤清单下面我把整个流程按顺序整理一遍你可以照着一步步来Vivado 端打开 Block Design双击 ZYNQ7 PS在 Debug and Trace 里使能 Trace选择 SWO 或 TPIU 模式。保存配置。生成 Bitstream如果之前有 UART 约束先删除或注释掉。然后 Generate Bitstream确保没有报错。导出硬件File - Export - Export Hardware勾选 Include bitstream导出.hdf文件。SDK 端打开 SDK新建 Application Project选择导出的硬件平台。BSP 设置在 BSP 上右键 - Board Support Package Settings把 stdout 改成coresight或ps7_coresight_0。保存并等待 BSP 重新编译。代码修改如果 BSP 的 coresight 不生效在应用工程里手动实现outbyte用 ITM 寄存器直接输出。打开 ITM 终端在 SDK 里打开 ITM Console配置 Stimulus Port 0。下载运行下载 Bitstream 和 ELF 文件到板子运行程序观察 ITM 终端输出。4.2 参数计算与选择依据ITM 的 Stimulus Port 地址不是随便定的它来自 Cortex-A9 的私有外设空间。ZYNQ7020 的 ITM 基地址在 UG585 里有说明通常是0xF8800000。Stimulus Port 0 的偏移是 0所以直接访问0xF8800000就是 Port 0 的寄存器。如果你要用其他 Port比如 Port 1地址就是0xF8800004以此类推。波特率方面ITM 走的是 JTAG 的 TCK 时钟不需要像 UART 那样设置波特率。但 JTAG 时钟频率会影响 ITM 的吞吐量。如果你的 JTAG 时钟设得很低比如 1MHz那 ITM 的打印速度也会很慢大量打印时容易丢数据。建议把 JTAG 时钟设到 10MHz 以上这样 ITM 打印会比较流畅。4.3 实操现场记录一次完整的调试过程我拿一块 ZYNQ7020 的核心板做测试板子上只有 JTAG没有 UART。Vivado 工程里 PS 配置只勾了基本的 DDR 和 QSPIUART 一个都没选。在 Debug and Trace 里使能了 Trace模式选 SWO。生成 Bitstream 后导出硬件SDK 里新建工程。BSP 设置里 stdout 下拉菜单里果然有ps7_coresight_0选中后保存。然后在 main 函数里写了一个简单的循环每隔一秒打印一次计数#include xil_printf.h #include sleep.h int main() { int count 0; while (1) { xil_printf(Count: %d\r\n, count); sleep(1); } return 0; }编译下载打开 ITM Console配置好 Port 0运行。ITM 终端里立刻开始输出 Count 值每秒一次非常稳定。整个过程没有改任何底层代码BSP 的 coresight 驱动直接生效了。后来我又试了手动实现outbyte的方式把 BSP 的 stdout 改成none然后在应用工程里加上 ITM 发送函数。编译下载后ITM 终端同样能正常输出。两种方式都可行BSP 自带的方式更省事手动方式更灵活适合 BSP 不支持 coresight 的情况。5. 常见问题与排查技巧实录5.1 ITM 终端没有输出怎么办这是最常见的问题。排查思路可以按以下顺序来检查 BSP 设置确认 stdout 是否真的改成了 coresight。有时候改了但没保存或者 BSP 没有重新编译。检查 ITM 终端配置Stimulus Port 是否设成了 0JTAG 设备是否选对。检查程序是否运行有时候程序跑飞了自然没有打印。可以在 main 函数开头加一个死循环点灯确认程序在跑。检查 JTAG 连接JTAG 线松动或者时钟太低都会导致 ITM 数据传不出来。试着提高 JTAG 时钟频率。检查打印函数如果用的是xil_printf确认它没有被重定向到其他地方。可以换成手动 ITM 发送函数测试。5.2 打印乱码或丢字符ITM 打印乱码通常是因为终端配置不对比如数据位、停止位设置错误。ITM 终端一般不需要设置这些但有些版本的 SDK 可能会要求选择正确的 ITM 协议。丢字符则多半是 JTAG 时钟太低或者打印量太大。解决办法是提高 JTAG 时钟或者在打印之间加一点延时给 ITM 缓冲留出时间。5.3 BSP 里找不到 coresight 选项如果 BSP 设置里 stdout 下拉菜单没有 coresight 选项说明硬件导出时没有使能 Debug and Trace。回到 Vivado检查 PS 配置里的 Debug and Trace 是否勾选特别是 Trace 部分。修改后重新生成 Bitstream重新导出硬件再在 SDK 里更新 BSP。更新 BSP 的方法是右键 BSP 工程 - Re-generate BSP Sources。5.4 程序下载后 ITM 终端才打开ITM 终端必须在程序运行之前打开并配置好否则早期的打印信息会丢失。如果你在程序运行后才打开 ITM 终端只能看到之后的打印。所以建议先打开 ITM 终端配置好再下载运行程序。5.5 常见问题速查表问题现象可能原因解决方法ITM 终端无输出BSP stdout 未设为 coresight修改 BSP 设置并重新编译ITM 终端无输出ITM 终端未配置 Port 0打开 ITM Console设置 Stimulus Port 为 0打印乱码终端配置错误检查 ITM 终端的数据格式设置丢字符JTAG 时钟太低提高 JTAG 时钟频率到 10MHz 以上BSP 无 coresight 选项硬件未使能 TraceVivado 中使能 Debug and Trace重新导出硬件程序无打印程序跑飞加 LED 指示或死循环确认程序运行状态提示如果以上方法都试过了还是不行可以尝试用 Xilinx 的 System Debugger 代替 ITM Console有时候 System Debugger 对 ITM 的支持更好。6. 进阶技巧与经验总结6.1 用 ITM 实现多级日志输出ITM 有 32 个 Stimulus Port我们可以用不同的 Port 来区分日志级别。比如 Port 0 输出普通信息Port 1 输出警告Port 2 输出错误。在 ITM 终端里可以分别打开这些 Port用不同颜色显示方便快速定位问题。实现方式很简单定义几个宏#define LOG_INFO(fmt, ...) itm_printf(0, fmt, ##__VA_ARGS__) #define LOG_WARN(fmt, ...) itm_printf(1, fmt, ##__VA_ARGS__) #define LOG_ERROR(fmt, ...) itm_printf(2, fmt, ##__VA_ARGS__)然后实现itm_printf根据 Port 号选择不同的寄存器地址。这样调试的时候错误信息一目了然。6.2 结合 SDK 的调试功能一起用ITM 打印只是调试手段之一SDK 本身还支持断点、单步、变量查看等功能。实际调试时我通常先用断点确认程序大致流程然后在关键分支加 ITM 打印观察实际运行时的变量值。两者结合效率比单纯用串口打印高很多。另外SDK 的Xilinx System Debugger支持实时变量监控可以在不暂停程序的情况下查看全局变量的值。这个功能配合 ITM 打印基本可以替代串口调试的全部功能。6.3 注意事项与踩坑记录ITM 打印不要用在中断里ITM 的写入操作可能会阻塞如果在中断服务函数里大量打印会导致中断响应变慢甚至丢中断。建议在中断里只做标记在主循环里打印。JTAG 时钟别设太高虽然提高 JTAG 时钟能加快 ITM 打印但设得太高可能导致 JTAG 连接不稳定。一般 10MHz 到 20MHz 之间比较合适具体看你的调试器和板子。BSP 更新后要重新编译应用工程修改 BSP 设置后应用工程需要重新编译才能链接到新的 BSP 库。有时候 SDK 不会自动触发应用工程的重编译需要手动 Clean 再 Build。ITM 终端和串口终端不要同时开如果你之前用过串口终端记得关掉否则可能会占用 JTAG 资源导致 ITM 终端无法正常工作。6.4 这个方案还能怎么扩展ITM 不仅能打印字符还能输出时间戳和事件计数。Cortex-A9 的 ITM 支持全局时间戳可以在每个打印前加上精确的时间信息方便分析程序的时序问题。另外ITM 还可以和 DWTData Watchpoint and Trace配合实现变量变化的实时监控不需要修改代码就能看到某个变量的历史值。如果你用的是 ZYNQ7020 的双核ITM 还可以区分不同核的打印。每个核有独立的 ITM 寄存器可以在 ITM 终端里分别显示调试多核通信时非常有用。我个人在实际操作中的体会是ITM 打印虽然不如串口那么直观但胜在不占资源、不需要硬件支持。一旦配置好了调试体验和串口几乎没区别。尤其是对于 ZYNQ7020 这种引脚紧张的项目掌握这个技巧能省下不少麻烦。最后再分享一个小技巧如果你觉得 ITM 终端配置太麻烦可以试试用 SDK 自带的Terminal视图新建一个 Terminal选择Xilinx System Debugger作为连接方式也能看到 ITM 输出而且配置更简单。
返回列表