跳到主要内容

第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 通信能够正常工作,请确保安装了合适的驱动:

  • 通过 ESP-IDF 工具安装器

    管理员权限运行安装器,安装时勾选选项 “Espressif - WinUSB 对 JTAG 的支持 (ESP32-C3/S3)” 。

    用 ESP-IDF 工具安装器安装 JTAG 驱动

  • 通过 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. 示例代码

  1. 创建一个项目。如果不清楚如何操作,请参考 从模板创建项目

  2. 将以下代码复制到 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);
    }
  3. 配置烧录选项

    首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。参考 第 2 节 运行示例 - 1.3 配置项目

    VSCode 工具栏

  4. 点击 VS Code 一键构建烧录监视图标 一键自动依次执行构建、烧录和监视这三个步骤。

  5. 烧录完成后,串口监视器会开始打印信息。

    Hello world!
    The summation up to 6 is 21

4. 配置 OpenOCD

OpenOCD 采用服务器-客户端架构来调试嵌入式系统。OpenOCD 服务器通过调试适配器(通常是 JTAG)与目标硬件建立连接,并提供网络接口供 gdb 或 telnet 等客户端发送命令或加载代码。

  1. 选择 OpenOCD 开发板配置:

    使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行 > ESP-IDF: 选择 OpenOCD 开发板配置

    VS Code 选择 OpenOCD 开发板配置

    选择 ESP32-S3 chip (via Built-in USB-JTAG)

    VS Code 选择 OpenOCD 开发板配置2

  2. 启动 OpenOCD:

    使用快捷键 Ctrl + Shift + P 打开 VS Code 命令面板。然后运行 > ESP-IDF: OpenOCD 管理器

    VS Code 选择 OpenOCD 管理器

    点击 “Start OpenOCD”。

    VS Code 选择 OpenOCD 管理器2

  3. 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。

5. 启动调试会话

接下来,我们将启动 gdb 并连接到 OpenOCD。启动调试后,VS Code 将自动完成这个过程。

  1. 在代码 sum += i; 行处设置一个断点。转到这行并按 F9,或者点击行号左侧的空白处添加断点。

    VS Code 设置断点

  2. 按下 F5 开启调试,或者 VS Code 顶部的菜单栏中点击 “运行” -> “启动调试”。

    启动调试后,调试器默认会在 app_main 函数的第一行暂停,等待用户操作。

    你可以在左侧看到 变量监视调用堆栈断点 等调试相关信息的面板,底部是 调试控制台输出 等窗口,代码窗口的上方会出现一个 调试工具栏

    VS Code 启动调试后界面

6. 常用调试操作

  1. 调试工具栏包含了一组用于控制程序调试过程的按钮:

    调试工具栏

    • VS Code 调试工具栏 - 继续按钮:让程序从当前暂停的位置继续运行,直到遇到下一个断点或程序结束。
    • VS Code 调试工具栏 - 逐过程按钮(逐过程):执行当前行代码。如果当前行是一个函数调用,它会执行完整个函数,然后停在下一行,不会进入函数内部。
    • VS Code 调试工具栏 - 单步调试按钮(单步调试):执行当前行代码。如果当前行是一个函数调用,调试器会进入该函数内部,并停在函数的第一行。
    • VS Code 调试工具栏 - 单步跳出按钮(单步跳出):继续执行代码,直到当前函数执行完毕并返回到调用它的地方,然后暂停。
    • VS Code 调试工具栏 - 重启按钮:重新开始整个调试会话。
    • VS Code 调试工具栏 - 断开连接按钮:停止并完全退出当前的调试会话。
  2. 常用的调试操作:

    1. 开启调试后,再次按下 F5 或者点击 VS Code 调试工具栏 - 继续按钮,程序会运行到断点位置然后停下。

      可以看到程序停在了第 8 行代码的位置,左侧可以查看当前各个变量的数值。注意,由于当前行代码尚未执行,所以 sum 变量的值仍为 0

      VS Code 调试 1

      也可以在调试控制台输入变量名并回车,以查看变量的值。

      VS Code 调试 2

    2. 按下 F10 或者点击 VS Code 调试工具栏 - 逐过程按钮 进行逐过程调试。

      按下后,代码进入下一行。多次按下这个按钮,观察调试器如何逐行执行程序,同时观察各个变量的变化。

      VS Code 调试 3

    3. 设置条件断点,可以在满足特定条件时停止执行程序。

      找到之前设置断点,右键点击,然后选择“编辑断点”,然后输入 i==6

      修改断点

      修改断点2

      按下 F5 或者点击 VS Code 调试工具栏 - 继续按钮。此时,代码运行到 i 变量的值为 6 时,暂停执行。

      条件断点

    4. 按下 F11 或点击 VS Code 调试工具栏 - 单步调试按钮 单步调试。当遇到函数时,调试器会进入函数内部。多次点击 VS Code 调试工具栏 - 单步调试按钮 可以看到调试器进入了 printf 函数内部,甚至 FreeRTOS 内核代码中。

      单步调试

    5. 按下 Shift + F11 或点击 VS Code 调试工具栏 - 单步跳出按钮 跳出回到主函数。

      单步跳出

    6. 最后,可以按下 Shift + F5 或点击 VS Code 调试工具栏 - 断开连接按钮 断开连接退出调试。

7. 常见问题与故障排除

遇到问题?试试这些解决方案

LIBUSB_ERROR_NOT_FOUND 错误

此页面 搜索相应错误代码,查看可能的解决方案。

OpenOCD 错误排查

  • 确认在 Arduino IDE 中选择的开发板型号是否正确
  • 检查端口选择是否正确
  • 重新尝试按 BOOT 键进入下载模式
  • 关闭其他可能占用串口的程序

调试行为异常

尝试重新烧录代码。每次修改代码后再调试时,都需要重新构建项目并烧录固件。

8. 参考链接