第5节 JTAG 调试
本教程的核心逻辑适用于所有 ESP32 开发板,但所有操作步骤均以 微雪 ESP32-S3-Zero 迷你开发板 为例进行讲解。如果您使用其他型号的开发板,请根据实际情况修改相应设置。
本节演示如何使用 VS Code 和 ESP-IDF 扩展,通过 JTAG 对 ESP32-S3 开发板进行调试。
1. 调试过程
调试是开发过程中的关键环节,在嵌入式系统开发中尤为重要。它帮助开发者发现并修复错误,确保固件与外设及外部硬件正常交互。
通过串口打印信息进行调试(通常借助串口监视器查看)是最常见、最简单的调试方法。它主要通过在代码中添加 printf
等打印语句,将变量值或程序状态输出到串口终端。如果没有外部调试工具,这是一种收集代码执行信息的有效方式。但这种方法有明显局限:基于 printf
的调试需要每次测试新内容时修改代码并重新构建项目。而使用调试器则可以逐步执行代码、检查内存和设置断点,无需修改源代码。
ESP32 系列芯片通常利用 JTAG(Joint Test Action Group)接口,配合 OpenOCD 和 GDB 实现断点、单步执行、变量与堆栈查看等调试功能,帮助开发者快速定位并修复问题。JTAG 调试通过目标芯片上的专用接口,建立从开发主机到目标芯片的完整调试链路,支持对运行中程序的深入分析与控制。
在 ESP-IDF 开发环境中,JTAG 调试过程由多个工具协同完成:
IDE (如 VS Code) 作为用户界面,通过 ESP-IDF 扩展调用 GDB (esp-gdb) 调试器客户端。GDB 再与 OpenOCD (openocd-esp32) 服务器通信,而 OpenOCD 最终通过 JTAG 适配器与 ESP32 芯片连接实现硬件级调试,各工具协同完成从用户界面到芯片底层的调试链路。
为更好地支持乐鑫芯片系列,ESP-IDF 使用了标准工具的特定分支版本:
- esp-gdb:GDB 的分支,增强了对 ESP 系列芯片的支持。
- openocd-esp32:OpenOCD 的分支,更快支持新发布的 ESP 芯片。
在后续内容中,如无特殊说明,GDB 与 esp-gdb、OpenOCD 与 openocd-esp32 可视为等同概念。
2. 硬件准备与驱动
2.1 硬件要求
本节介绍如何利用 ESP32-S3 内置 JTAG 接口进行调试。本教程的方法仅适用于具备 USB JTAG 功能的设备(如 ESP32-S3 或 ESP32-C3)。
对于不具备 USB JTAG 功能的开发板,仍可使用外部 JTAG 调试器(如 ESP-PROG)进行调试,但相关内容不在本文讨论范围内。更多细节可参考 Espressif 官方教程:Debugging with ESP-IDF VS Code extension。
2.2 安装驱动
为确保 JTAG 通信能够正常工作,请确保安装了合适的驱动:
-
以管理员权限运行安装器,安装时勾选选项 “Espressif - WinUSB 对 JTAG 的支持 (ESP32-C3/S3)” 。
-
通过 PowerShell 命令
如果不想重新运行安装程序,则可以通过 idf-env 实现相同的效果。以管理员权限打开 PowerShell 并运行以下命令:
Invoke-WebRequest 'https://dl.espressif.com/dl/idf-env/idf-env.exe' -OutFile .\idf-env.exe; .\idf-env.exe driver install --espressif
3. 示例代码
-
创建一个项目。如果不清楚如何操作,请参考 从模板创建项目。
-
将以下代码复制到 main/main.c 中:
#include <stdio.h>
// 计算从1到'number'所有整数求和的函数
int summation(int number)
{
int sum = 0;
for (int i = 1; i <= number; i++){
sum += i;
}
return sum;
}
void app_main(void)
{
printf("Hello world!\n");
int final_number = 6;
int sum = summation(final_number);
printf("The summation up to %d is %d\n", final_number, sum);
} -
配置烧录选项
首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。参考 第 2 节 运行示例 - 1.3 配置项目 。
-
点击
一键自动依次执行构建、烧录和监视这三个步骤。
-
烧录完成后,串口监视器会开始打印信息。
Hello world!
The summation up to 6 is 21
4. 配置 OpenOCD
OpenOCD 采用服务器-客户端架构来调试嵌入式系统。OpenOCD 服务器通过调试适配器(通常是 JTAG)与目标硬件建立连接,并提供网络接口供 gdb 或 telnet 等客户端发送命令或加载代码。
-
选择 OpenOCD 开发板配置:
使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行
> ESP-IDF: 选择 OpenOCD 开发板配置
。选择
ESP32-S3 chip (via Built-in USB-JTAG)
。 -
启动 OpenOCD:
使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行
> ESP-IDF: OpenOCD 管理器
。点击 “Start OpenOCD”。
-
OpenOCD 启动后,你应该能看到如下输出,表示服务器现在正在等待连接:
Open On-Chip Debugger v0.12.0-esp32-20250422 (2025-04-22-13:02)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
debug_level: 2
Info : only one transport option; autoselecting 'jtag'
Info : esp_usb_jtag: VID set to 0x303a and PID to 0x1001
Info : esp_usb_jtag: capabilities descriptor set to 0x2000
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : esp_usb_jtag: serial (24:EC:4A:2C:6D:AC)
Info : esp_usb_jtag: Device found. Base speed 40000KHz, div range 1 to 255
Info : clock speed 40000 kHz
Info : JTAG tap: esp32s3.tap0 tap
/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : JTAG tap: esp32s3.tap1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
Info : [esp32s3.cpu0] Examination succeed
Info : [esp32s3.cpu1] Examination succeed
Info : [esp32s3.cpu0] starting gdb server on 3333
Info : Listening on port 3333 for gdb connections- 默认情况下,OpenOCD 服务器启动后,端口
4444
用于 Telnet 通信;端口6666
用于 TCL 通信;端口3333
用于 GDB。
- 默认情况下,OpenOCD 服务器启动后,端口
5. 启动调试会话
接下来,我们将启动 gdb 并连接到 OpenOCD。启动调试后,VS Code 将自动完成这个过程。
-
在代码
sum += i;
行处设置一个断点。转到这行并按 F9,或者点击行号左侧的空白处添加断点。 -
按下 F5 开启调试,或者 VS Code 顶部的菜单栏中点击 “运行” -> “启动调试”。
启动调试后,调试器默认会在
app_main
函数的第一行暂停,等待用户操作。你可以在左侧看到 变量、监视、调用堆栈和断点 等调试相关信息的面板,底部是 调试控制台、输出 等窗口,代码窗口的上方会出现一个 调试工具栏。
6. 常用调试操作
-
调试工具栏包含了一组用于控制程序调试过程的按钮:
:让程序从当前暂停的位置继续运行,直到遇到下一个断点或程序结束。
(逐过程):执行当前行代码。如果当前行是一个函数调用,它会执行完整个函数,然后停在下一行,不会进入函数内部。
(单步调试):执行当前行代码。如果当前行是一个函数调用,调试器会进入该函数内部,并停在函数的第一行。
(单步跳出):继续执行代码,直到当前函数执行完毕并返回到调用它的地方,然后暂停。
:重新开始整个调试会话。
:停止并完全退出当前的调试会话。
-
常用的调试操作:
-
开启调试后,再次按下 F5 或者点击
,程序会运行到断点位置然后停下。
可以看到程序停在了第 8 行代码的位置,左侧可以查看当前各个变量的数值。注意,由于当前行代码尚未执行,所以
sum
变量的值仍为0
。也可以在调试控制台输入变量名并回车,以查看变量的值。
-
按下 F10 或者点击
进行逐过程调试。
按下后,代码进入下一行。多次按下这个按钮,观察调试器如何逐行执行程序,同时观察各个变量的变化。
-
设置条件断点,可以在满足特定条件时停止执行程序。
找到之前设置断点,右键点击,然后选择“编辑断点”,然后输入
i==6
。按下 F5 或者点击
。此时,代码运行到
i
变量的值为6
时,暂停执行。 -
按下 F11 或点击
单步调试。当遇到函数时,调试器会进入函数内部。多次点击
可以看到调试器进入了
printf
函数内部,甚至 FreeRTOS 内核代码中。 -
按下 Shift + F11 或点击
跳出回到主函数。
-
最后,可以按下 Shift + F5 或点击
断开连接退出调试。
-
7. 常见问题与故障排除
LIBUSB_ERROR_NOT_FOUND 错误
在 此页面 搜索相应错误代码,查看可能的解决方案。
OpenOCD 错误排查
- 确认在 Arduino IDE 中选择的开发板型号是否正确
- 检查端口选择是否正确
- 重新尝试按 BOOT 键进入下载模式
- 关闭其他可能占用串口的程序
调试行为异常
尝试重新烧录代码。每次修改代码后再调试时,都需要重新构建项目并烧录固件。