Skip to content
Development
Skill

/testbench-debug

Remote GDB debugging of ESP32 devices via the testbench. Covers USB JTAG (C3/S3), dual-USB (S3 two-port), and ESP-Prog (FT2232H) approaches. Use when setting up JTAG debugging, connecting GDB, configuring OpenOCD, or troubleshooting debug connections. Triggers on "GDB", "JTAG",

From plugin
embedded-ai-harness
18018 skills
Install
$ npx -y skills add SensorsIot/Embedded-AI-Harness --skill testbench-debug --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/testbench-debug

Context preview

The summary Claude sees to decide when to auto-load this skill.

Remote GDB debugging of ESP32 devices via the testbench. Covers USB JTAG (C3/S3), dual-USB (S3 two-port), and ESP-Prog (FT2232H) approaches. Use when setting up JTAG debugging, connecting GDB, configuring OpenOCD, or troubleshooting debug connections. Triggers on "GDB", "JTAG",

SKILL.md

testbench-debug.SKILL.md
name: testbench-debug
description: Remote GDB debugging of ESP32 devices via the testbench. Covers USB JTAG (C3/S3), dual-USB (S3 two-port), and ESP-Prog (FT2232H) approaches. Use when setting up JTAG debugging, connecting GDB, configuring OpenOCD, or troubleshooting debug connections. Triggers on "GDB", "JTAG", "debug", "OpenOCD", "breakpoint", "ESP-Prog", "step through", "debug session".

Testbench GDB Debugging

Remote GDB debugging of ESP32 devices through the testbench Pi. OpenOCD runs on the Pi, GDB connects from the container over TCP.

**Architecture:**

[Container]              [Pi (testbench)]           [ESP32]
  GDB ──── TCP :3333 ────── OpenOCD ──── USB JTAG ──────── CPU
                             (or)
                           OpenOCD ──── ESP-Prog ── JTAG pins

---

Three Approaches

| Approach | Chips | Extra Hardware | Serial During Debug | |----------|-------|:-:|:-:| | **USB JTAG** (FR-024) | C3, S3 (native USB only) | None | Yes | | **Dual-USB** (FR-025) | S3 (two USB connectors) | None | Yes + app USB | | **ESP-Prog** (FR-026) | All ESP32 variants | ESP-Prog (~$15) + cable | Yes |

---

Auto-Debug (Zero Config)

OpenOCD starts **automatically** when a device is plugged in or at boot. No API call needed.

**How it works:** 1. Device hotplugged → serial proxy starts → auto-detect tries OpenOCD configs 2. Chip identified via JTAG TAP ID → OpenOCD starts on the assigned GDB port 3. If USB JTAG fails (e.g. classic ESP32), falls back to ESP-Prog probe if available 4. GDB port reported in `/api/devices` response

**Check debug status:**

curl $TESTBENCH_URL/api/devices
# Look for: "debugging": true, "debug_chip": "esp32s3", "debug_gdb_port": 3335

**Manual override (optional -- only needed to force-stop or force-start):**

# Force stop (won't auto-restart until next hotplug)
curl -X POST $TESTBENCH_URL/api/debug/stop -d '{}'

# Force start with specific chip
curl -X POST $TESTBENCH_URL/api/debug/start \
  -d '{"chip": "esp32c3"}'

---

JTAG Reset (Preferred When Available)

When a debug session is active, the testbench automatically uses **JTAG reset** instead of DTR/RTS serial reset. This is transparent — the same `/api/serial/reset` API is used.

**Why JTAG reset is better:**

  • No USB re-enumeration (device node stays stable)
  • No flapping risk
  • No 2-second boot delay
  • Works even when serial port is unresponsive
  • Can halt the CPU to stop boot loops

**Via OpenOCD telnet (manual):**

# Soft reset (chip reboots normally)
echo "reset run" | nc "$BENCH" 4446

# Halt CPU (stops execution immediately)
echo "halt" | nc "$BENCH" 4446

# Reset and halt (for debugging from first instruction)
echo "reset halt" | nc "$BENCH" 4446

**Via testbench API (automatic):**

# Uses JTAG reset when debug session is active, DTR/RTS otherwise
curl -X POST $TESTBENCH_URL/api/serial/reset \
  -H "Content-Type: application/json" -d '{"slot": "SLOT1"}'

**Availability:** | Scenario | JTAG reset? | |----------|:-:| | C3/S3/C6/H2 with native USB | Yes (auto-started) | | Classic ESP32 + ESP-Prog | Yes (when probe wired) | | Classic ESP32 without ESP-Prog | No — DTR/RTS fallback |

---

Prerequisites

OpenOCD is pre-installed on the Pi:

/usr/local/bin/openocd-esp32           # binary
/usr/local/share/openocd-esp32/scripts/ # config files

If missing, run `install.sh` or manually:

# On Pi (aarch64)
wget https://github.com/espressif/openocd-esp32/releases/download/v0.12.0-esp32-20260304/openocd-esp32-linux-arm64-0.12.0-esp32-20260304.tar.gz
tar xzf openocd-esp32-linux-arm64-*.tar.gz
sudo cp openocd-esp32/bin/openocd /usr/local/bin/openocd-esp32
sudo mkdir -p /usr/local/share/openocd-esp32
sudo cp -r openocd-esp32/share/openocd/scripts /usr/local/share/openocd-esp32/scripts

---

1. USB JTAG (ESP32-C3 / ESP32-S3 with Native USB)

Identify the device

The device must show as `303a:1001` (Espressif USB JTAG/serial debug unit). Boards with CH340/CP2102 UART bridges do NOT support USB JTAG.

# Check from Pi
ssh pi@<bench-hostname> "lsusb -d 303a:1001"
# → Bus 001 Device 066: ID 303a:1001 Espressif USB JTAG/serial debug unit

USB interface layout (no kernel unbind needed):

  • Interface 0: CDC-ACM serial → `/dev/ttyACM*` (RFC2217 proxy)
  • Interface 1: CDC Data
  • Interface 2: Vendor Specific (**unclaimed** — OpenOCD uses via libusb)

Chip auto-detection

USB PID `303a:1001` is the same for C3 and S3. The JTAG TAP ID identifies the chip:

| TAP ID | Chip | Architecture | Config | |--------|------|-------------|--------| | `0x00005c25` | ESP32-C3 | RISC-V single-core | `esp32c3-builtin.cfg` | | `0x00010c25` | ESP32-H2 | RISC-V single-core | `esp32h2-builtin.cfg` | | `0x0000dc25` | ESP32-C6 | RISC-V single-core | `esp32c6-builtin.cfg` | | `0x120034e5` | ESP32-S3 | Xtensa dual-core | `esp32s3-builtin.cfg` |

Start OpenOCD manually (for testing)

# ESP32-C3
ssh pi@<bench-hostname> "openocd-esp32 -s /usr/local/share/openocd-esp32/scripts \
  -f board/esp32c3-builtin.cfg \
  -c 'gdb port 3333' -c 'telnet port 4444' -c 'bindto 0.0.0.0'"

# ESP32-S3
ssh pi@<bench-hostname> "openocd-esp32 -s /usr/local/share/openocd-esp32/scripts \
  -f board/esp32s3-builtin.cfg \
  -c 'gdb port 3333' -c 'telnet port 4444' -c 'bindto 0.0.0.0'"

Connect GDB from container

# C3 (RISC-V)
riscv32-esp-elf-gdb build/project.elf \
  -ex "target extended-remote $BENCH:3333" \
  -ex "monitor reset halt"

# S3 (Xtensa)
xtensa-esp32s3-elf-gdb build/project.elf \
  -ex "target extended-remote $BENCH:3333" \
  -ex "monitor reset halt"

Connect via OpenOCD telnet (quick test)

import socket, time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((BENCH, 4444))
time.sleep(0.5)
banner = s.recv(4096)  # telnet negotiation + "Open On-Chip Debugger"
s.sendall(b'halt\n')
time.sleep(1)
print(s.recv(4096).decode('latin-1'))  # "Target halted, PC=0x..."
s.sendall(b'resume\n')
time.sle
Read more
Ships withembedded-ai-harness

Spec to silicon, hands off. A horse is strong, fast, and willing — and useless for heavy loads until you harness it. The harness is not a part of the horse and not a part of the cart: it is the coupling that turns raw strength into pulled weight.

Get the whole plugin
Stats
181
Stars
54
Forks
Maintained
Maintenance
Python
Language
MIT
License
1mo ago
Last commit
8mo ago
Created

Repo: SensorsIot/Embedded-AI-Harness

Other skills on embedded-ai-harness.