- name
- esp32-debug
- description
- ESP32 debugging via serial log and USB-JTAG (OpenOCD + GDB). Invoke when user encounters bugs, crashes, or unexpected behavior on ESP32 and needs systematic root cause analysis.
# ESP32 Debug — Systematic Troubleshooting
## Overview
This skill covers the complete debug workflow for ESP32 projects:
- **Serial log debugging** (ESP_LOGI/ESP_LOGE) — lightweight, non-intrusive, preferred first approach
- **USB-JTAG debugging** (OpenOCD + GDB) — deep hardware-level debugging
- **Combined approach** (serial + JTAG) for complex issues
## Platforms
| Debug Method | ESP32 | ESP32-S3/C3/C6/C2/H2 | Hardware Required |
|--------------|:-----:|:---------------------:|-------------------|
| Serial log | ✅ | ✅ | USB cable only |
| USB-JTAG (built-in) | ❌ | ✅ | USB cable only |
| External JTAG | ✅ | ✅ | ESP-Prog / J-Link |
## Execution Flow
```
User reports a bug/crash/unexpected behavior
↓
[Step 1] 🔍 环境准备 — Verify dev environment, tools, connections
↓
[Step 2] ❓ 问题理解 — Ask clarifying questions to scope the problem
↓
[Step 3] 📖 代码研读 — Read ALL relevant code BEFORE making changes
↓
[Step 4] 🔧 调试方案选择 — Choose method A (serial), B (JTAG), or C (combined)
↓
[Step 5] ⚡ 方案执行 — Add diagnostics, build, flash, capture data
↓
[Step 6] 🧠 根因分析 — Identify direct + root cause with evidence
↓
[Step 7] 📋 报告输出 — Structured report with analysis + fix options
↓
[Step 8] ⏳ 等待用户决策 — Wait for user to choose action
```
---
## Step 1: Environment Preparation
### 1.1 Verify esp32-dev Skill
> **AI 首次执行本 Skill 时,必须先确认 `esp32-dev` skill 已加载。**
```
AI 自行判断 esp32-dev skill 是否可用:
1. 检查 esp32_env.json 是否存在(位于当前 skill 的 scripts/ 目录)
2. 检查脚本 idf_export.ps1、idf_build.ps1 等是否可用
3. 如缺少关键脚本或环境文件 → 引导用户先执行 esp32-dev skill 完成环境初始化
```
> esp32-dev skill 提供完整的 ESP-IDF 工具链路径、工程编译、烧录流程。本 Skill 的编译/烧录步骤均复用 esp32-dev 的能力。
### 1.2 检查 USB-JTAG 连接(方案 B / C 必需)
> **AI 在使用 JTAG 调试前,必须先探测设备并验证连接。**
**Step A — 检查 USB 设备是否被系统识别:**
```bash
# Windows — 用脚本检查 USB 设备列表和驱动状态
powershell -NoProfile -ExecutionPolicy Bypass -File "check_jtag_driver.ps1"
```
查看输出中是否有 `USB JTAG/serial debug unit` (VID 303A, PID 1001),且 MI_02 接口的驱动为 `libusb0`。
| 结果 | 动作 |
|------|------|
| 设备存在 + 驱动为 `libusb0` | ✅ 继续 |
| 设备存在 + 驱动为 `WINUSB` | ❌ 引导用户使用 Zadig 替换驱动(见 `esp32-dev` skill) |
| 设备不存在 | ❌ 检查 USB 线、芯片型号是否支持 USB-JTAG(ESP32 经典版不支持) |
**Step B — 启动 OpenOCD 并验证 JTAG 连接:**
```bash
# Windows — 使用脚本启动 OpenOCD
powershell -NoProfile -ExecutionPolicy Bypass -File "idf_openocd.ps1" -project_path <project_dir>
# Linux/macOS
idf.py openocd
```
**预期输出:**
```
Info : esp_usb_jtag: Device found.
Info : JTAG tap: esp32s3.tap0 tap/device found: 0x120034e5
Info : [esp32s3.cpu0] Examination succeed
Info : [esp32s3.cpu1] Examination succeed
Info : Listening on port 3333 for gdb connections
```
| 结果 | 动作 |
|------|------|
| CPU0/CPU1 Examination succeed | ✅ JTAG 调试通道正常 |
| `LIBUSB_ERROR_NOT_FOUND` | ❌ 驱动问题,回到 Step A 替换驱动 |
| `JTAG tap: esp32s3.tap0 found` | ✅ 芯片识别正确 |
| Only CPU0 found | 正常(部分芯片为单核) |
**Step C — 验证 OpenOCD 可读写芯片内存:**
```bash
# 通过 OpenOCD telnet 快速验证
echo -e "mdw 0x3FC9C000 4\nexit" | telnet localhost 4444
```
> 如能正常输出内存地址和对应的 hex 值,说明 JTAG 读写通道正常。
### 1.3 安装并配置串口监听工具
**指定工具 — EmberInterDebugTool(首选):**
> **本工具为安装包形式,需安装后才能使用。** 工具配套 **GUI** 和 **CLI** 两种模式,**使用前需先启动 GUI**。
**下载地址:** `https://github.com/kukucaiCndy/EmberinterTool/releases`
| 步骤 | 操作 |
|------|------|
| 1. 下载安装包 | 从 Releases 页面下载最新安装包(`.exe` / `.dmg` / `.deb`) |
| 2. 运行安装程序 | 按安装向导完成安装,记录安装目录 |
| 3. 启动 GUI | 安装完成后先启动 GUI,确认工具正常运行 |
| 4. CLI 可用 | GUI 启动后,CLI 命令行接口才可用于脚本/自动化场景 |
**安装后默认路径:** `C/D/E/F:\Program Files (x86)\EmberInterDebugTool`
**GUI 架构:** GUI 负责串口连接管理,CLI 通过 IPC 与 GUI 联动。CLI 可随时启停,不影响串口数据采集。CLI 断连不影响串口数据持续采集。
#### 加载工具 Skill 并学习 CLI 命令
> **AI 在使用串口工具前,必须先加载工具对应的 skill,掌握其命令行参数。**
**Skill 获取来源(按优先级):**
```
1. 本地优先:从工具安装目录获取
常见路径:
Windows: C/D/E/F:\Program Files (x86)\EmberInterDebugTool\skill\
2. 线上备用:从 GitHub 仓库获取
https://github.com/kukucaiCndy/embedded_ai_skills/tree/master/tools/emberinter
```
> **⚠️ 不要假设参数名。** 必须通过 skill 中的说明确认后再使用。
#### CLI 关键命令速查
**连接管理:**
```bash
# 列出可用串口
serial-monitor-cli --list
# 连接并清空日志
serial-monitor-cli --connect COM10 --baudrate 115200 --clear
# 交互模式(持续运行,可收发)
serial-monitor-cli -p COM10 --cli
```
**数据发送:**
```bash
# 发送文本(自动追加 CRLF)
serial-monitor-cli --send "help" -p COM10
# 发送 HEX 字节(不加换行符)
serial-monitor-cli --send-hex "FF AB 03 1A" -p COM10
```
**日志监听与诊断:**
```bash
# 持续监听并保存到文件
serial-monitor-cli -p COM10 -o esp32_log.txt
# 只显示含 ERROR 的日志
serial-monitor-cli -p COM10 -f ERROR
# 获取最近日志(JSON 格式,适合脚本解析)
serial-monitor-cli --get-logs 50 --json
# 查看连接状态
serial-monitor-cli --get-status --json
```
**交互模式命令(`--cli` 进入后):**
| 命令 | 说明 |
|------|------|
| `connect <port> [baud]` | 连接指定串口,默认 115200 |
| `disconnect` / `disc` | 断开当前串口 |
| `send <text>` | 发送文本(自动加 CRLF) |
| `sendhex <hex>` | 发送 HEX 字节 |
| `filter <keyword>` | 过滤关键词,空字符串取消过滤 |
| `hex` / `text` | 切换 HEX 显示模式 |
| `status` / `s` | 查看当前连接状态 |
| `clear` / `c` | 清空日志缓冲区 |
| `quit` / `q` | 退出 CLI(GUI 继续运行) |
#### 固件烧录后验证工作流
```bash
# Step 1: 确保 GUI 已运行,启动 CLI 持续监听
serial-monitor-cli -p COM10 -o esp32_log.txt
# Step 2: 烧录固件(需先断开 EmberInterDebugTool 连接以释放端口)
taskkill /F /IM serial-monitor.exe # 关闭 GUI,释放 COM 口
idf.py -p COM10 flash # 烧录
# Step 3: 重启 GUI + 连接 + 发送命令验证
start "" "D:/Program Files (x86)/EmberInterDebugTool/serial-monitor.exe"
serial-monitor-cli --connect COM10 --baudrate 115200 --clear
serial-monitor-cli --send "help" -p COM10
sleep 2
serial-monitor-cli --get-logs 50 --json
```
> **注意:** GUI 占用串口时 `idf.py flash` 会失败。烧录前需先关闭 GUI 释放端口。烧录完成后重启 GUI 进行交互测试。
#### 备选工具 — idf.py monitor
ESP-IDF 自带串口监听也可作为备选:
```bash
idf.py -p COM10 monitor
```
但优先使用 EmberInterDebugTool 以获得统一的调试体验。
### 1.4 准备调试工具链
**AI 在执行调试前必须确认以下工具可用:**
| 工具 | 用途 | 验证命令 | 来源 |
|------|------|---------|------|
| `esptool` | 芯片检测、Flash 读写 | `esptool --version` | ESP-IDF 工具链 |
| `idf.py monitor` | 串口监听 | `idf.py monitor --help` | ESP-IDF |
| `openocd` | JTAG 调试服务器 | 通过 `idf_openocd.ps1` 脚本启动 | ESP-IDF 工具链 |
| `xtensa-esp32s3-elf-gdb` | GDB 调试客户端 | `xtensa-esp32s3-elf-gdb --version` | ESP-IDF 工具链 |
| `xtensa-esp32s3-elf-objdump` | 反汇编 | `xtensa-esp32s3-elf-objdump --version` | ESP-IDF 工具链 |
| `xtensa-esp32s3-elf-nm` | 符号表 | `xtensa-esp32s3-elf-nm --version` | ESP-IDF 工具链 |
| `xtensa-esp32s3-elf-addr2line` | 地址到源码行 | `xtensa-esp32s3-elf-addr2line --help` | ESP-IDF 工具链 |
> **⚠️ 所有工具路径通过 `esp32_env.json` 自动获取,AI 使用脚本封装后调用。** 不要硬编码工具路径,始终通过脚本或 `esp32_env.json` 中的路径。
**芯片前缀速查表:**
| 芯片 | GDB 前缀 | objdump/nm/addr2line 前缀 |
|------|---------|--------------------------|
| ESP32 | `xtensa-esp32-elf-` | `xtensa-esp32-elf-` |
| ESP32-S2 | `xtensa-esp32s2-elf-` | `xtensa-esp32s2-elf-` |
| ESP32-S3 | `xtensa-esp32s3-elf-` | `xtensa-esp32s3-elf-` |
| ESP32-C3/C6/H2 | `riscv32-esp-elf-` | `riscv32-esp-elf-` |
---
## Step 2: Problem Understanding
**AI MUST ask the user these 4 questions before proceeding:**
### Q1: Problem symptoms
> "请详细描述问题现象。例如:程序卡死在某处、重启循环、输出异常值、外设不工作、Wi-Fi/BLE连接失败等。"
### Q2: Reproducibility
> "问题是稳定复现还是偶发?如果是稳定复现,触发条件是什么?"
### Q3: Recent changes
> "最近修改了哪些代码?有没有新增功能、修改配置、更换硬件?"
### Q4: Device state
> "设备当前状态是什么?串口有什么输出?有没有 panic 信息、backtrace 或错误日志?"
**After gathering answers, AI should classify the problem type:**
- **Crash/Panic** → Priority: JTAG + backtrace analysis
- **Logic error** (wrong behavior) → Priority: Serial log + code review
- **Performance** (slow, timing issue) → Priority: Serial log with timestamps
- **Connectivity** (Wi-Fi/BLE) → Priority: ESP_LOG + protocol analysis
---
## Step 3: Code Reading
**AI MUST read all relevant code BEFORE suggesting any changes.**
### Reading scope:
1. `main.c` — app_main() and main logic
2. All ISRs (interrupt service routines) in the project
3. FreeRTOS task creation and callback functions
4. Initialization code (GPIO, peripherals, Wi-Fi, BLE)
5. `sdkconfig` — check relevant Kconfig settings
6. Custom components in `components/`
7. IDF framework configs if relevant (e.g., `CONFIG_FREERTOS_*`, `CONFIG_ESP_WIFI_*`)
### Analysis checklist:
- [ ] Variable initialization — are all variables properly initialized?
- [ ] Function call chain — trace the execution path
- [ ] Timing/delay — are `vTaskDelay()` values sensible?
- [ ] Buffer sizes — are buffers large enough? No overflows?
- [ ] Stack sizes — are FreeRTOS task stacks sufficient?
- [ ] ISR timing — are ISRs short enough? No blocking calls?
- [ ] Interrupt priority — are priorities correct? (higher number = lower priority on ESP32)
- [ ] Shared data — are critical sections/mutexes used for shared resources?
- [ ] Error handling — are `ESP_ERROR_CHECK()` return values checked?
- [ ] Memory — any null pointer dereferences? Memory leaks?
---
## Step 4: Debug Method Selection
### Method A: Serial Log (首选 — Preferred)
- **When**: Stable repro, device responsive, logic/peripheral issues
- **Pros**: No extra hardware, real-time, low intrusion, keeps CPU running
- **Cons**: Limited to what you log, can miss transient states
- **Tools**: `ESP_LOGI()` / `ESP_LOGE()` macros + `idf.py monitor`
### Method B: USB-JTAG (备选 — Backup)
- **When**: Crash/panic, device unresponsive, need CPU register/stack dump
- **Pros**: Full CPU control, breakpoints, watchpoints, register dump
- **Cons**: Halts CPU, needs driver setup (Zadig on Windows)
- **Tools**: OpenOCD + GDB (`idf.py openocd` + `idf.py gdb`)
### Method C: Serial + JTAG Combined (联用)
- **When**: Complex issue — serial shows anomaly, JTAG provides context
- **Pros**: Best of both worlds, cross-validation
- **Workflow**: Serial log triggers → JTAG breakpoint to confirm
- **Example**: ESP_LOGI reports bad value → GDB `watch` to find writer
### Decision Flow
```
Can the device output serial logs?
├── YES → Method A (add ESP_LOGI diagnostics)
└── NO → Does the device have USB-JTAG?
├── YES → Method B (OpenOCD + GDB)
└── NO → Method A with minimalist logging (UART0 always works)
```
---
## Step 5: Method A — Serial Log Debugging
### 5.1 Add diagnostic logs
**AI should insert ESP_LOG calls at key points:**
```c
// Function entry/exit
ESP_LOGI(TAG, "function_name: entry, param=%d", param);
// Variable state
ESP_LOGI(TAG, "state: var1=%d, var2=0x%08x, ptr=%p", var1, var2, ptr);
// Error paths
ESP_LOGE(TAG, "error: esp_err=%s (0x%x)", esp_err_to_name(err), err);
// FreeRTOS task info
ESP_LOGI(TAG, "task: %s, stack_free=%lu", pcTaskGetName(NULL),
uxTaskGetStackHighWaterMark(NULL));
// Heap info
ESP_LOGI(TAG, "heap: free=%lu, min_free=%lu",
esp_get_free_heap_size(), esp_get_minimum_free_heap_size());
// Wi-Fi state
GitHubで見る