esp-target-debug — Espressif built-in USB-JTAG backend
Methodology — intrusiveness ladder, board locks, dual-side capture, diagnosis
standards — lives in target-debug; this skill is the Espressif backend: a
different gdb, a different openocd (fork), no probe serial (the debugger IS a
USB device), and a PHY story that decides whether JTAG exists at all.
Built-in USB-Serial-JTAG only; external JTAG is a TODO (no rig adapter).
The PHY map — decides everything (verified on the rig)
| Board |
USB-SJ vs TinyUSB OTG |
JTAG while USB device runs? |
| espressif_p4_function_ev |
separate pins: USB-SJ GPIO24/25 (FS), OTG own HS PHY |
yes — coexist (verified: 303a:1001 + cafe:4008 simultaneously, gdb attach during live CDC traffic) |
| espressif_s3_devkitm |
ONE shared PHY/port |
no — the same hub port flips 303a:1001 → cafe:4008 as the app boots; openocd fails esp_usb_jtag: could not find or open device! |
Flashing works in ANY PHY state: the rig flashes via the boards' CP2102N UART
bridges (hence tinyusb.json esptool uids are CP210x serials, not MACs). The
UART side is also the remote reset: esptool.py --after hard_reset read_mac.
P4 (Function-EV) notes
- The board has no USB-SJ connector — GPIO24 (D−, white) / GPIO25 (D+,
green) / GND are broken out from header J1 to a rig hub port. Swapped
D+/D− enumerates as
new low-speed USB device + error -71; correct shows
new full-speed.
S3 (DevKitM) notes
- Debugging windows: non-USB firmware (
board_test — attach/halt/symbol
resolution verified; usb_new_phy is absent from the ELF when
CFG_TUD/TUH_ENABLED are 0), bootloader/ROM (always stable), or external
JTAG (TODO).
- Keep-alive quirk (verified): with app firmware running and nothing
attached, USB-SJ drops ~4 s after boot (device-side disconnect, then
half-dead
-71 setup failures until reset). Attach a client inside the
window — once it survives the window it stays up. Recover via the UART
reset above.
- PHY mux reference:
RTC_CNTL_RTC_USB_CONF_REG (0x60008120) bits
SW_HW_USB_PHY_SEL/SW_USB_PHY_SEL (TRM 10.56); 0 = eFuse/hardware
control (default). esptool.py read_mem/write_mem peeks and pokes
registers over plain UART with the chip in download mode.
Attach
. "$IDF_PATH/export.sh" # openocd-esp32, riscv32-/xtensa-esp32s3-elf-gdb, esptool
openocd -c 'set ESP_RTOS FreeRTOS' -f board/esp32p4-builtin.cfg \
-c 'adapter serial <MAC-with-colons>' & # S3: board/esp32s3-builtin.cfg
riscv32-esp-elf-gdb -batch -ex 'target extended-remote :3333' \
-ex 'tbreak tud_task_ext' -ex continue -ex bt -ex 'info threads' -ex detach <elf>
# S3 is Xtensa: use xtensa-esp32s3-elf-gdb with the same arguments
adapter serial = the chip MAC with colons — the USB-SJ device's
iSerial exactly as lsusb -v -d 303a:1001 or
/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_<MAC>-if00
prints it. (The tinyusb.json esptool uids are the CP2102N flasher
serials — a different port; never pass those to openocd.)
set ESP_RTOS FreeRTOS must precede the board cfg: with it, info threads
lists every task with name/state/CPU (verified: usbd Running @CPU0, IDLE1
@CPU1, ...); without it, one bare "Remote target".
- The ELF:
idf.py -B <builddir> -DBOARD=<board> build under the example
(CLAUDE.md Espressif notes) — symbolized app backtraces verified
(tud_task_ext ← usb_device_task ← vPortTaskWrapper).
- Attach may reset the target (a boot-fresh FreeRTOS tick observed on a
minutes-old session). Until pinned down, do NOT trust built-in-JTAG attach
for post-mortem autopsy of a wedged board (
target-debug's
attach-and-halt-only rule); capture state via console or treat the reset
as part of the reproduce cycle.
- Halting still stops USB service: the host may drop the DUT during long
halts; after detach the device may need the UART reset to re-enumerate.
Scripted-session gotchas (verified)
- xtensa-gdb batch
continue/interrupt is async-flaky — for scripted
state reads, halt via openocd telnet :4444 first, then attach gdb to the
stopped target. Interactive sessions are unaffected.
- cpu1 debug-logic examination can fail (
OCD_ID = 00000000) —
-c 'set ESP_ONLYCPU 1' degrades to cpu0-only debugging.
- ROM-frame backtraces (
0x4004xxxx on S3, 0x4fc0xxxx on P4, all ??)
mean the core idles in ROM — break in app code (tbreak tud_task_ext)
for symbolized frames.
Technique mapping (vs the target-debug arsenal)
| target-debug technique |
Espressif backend |
| GDB autopsy, bp/wp |
same flow via openocd-esp32 :3333; RISC-V triggers (P4) / Xtensa 2 bp + 2 wp (S3) |
| Vector catch |
none — breakpoint the panic handler; mcause/mepc/mtval on P4 |
| SWO / DWT data trace |
none — apptrace over JTAG is the analog (untested: needs CONFIG_APPTRACE + app init) |
| RTT / TU_LOG |
console on UART0 = the CP2102 flasher tty by default (verified); USB-SJ console needs sdkconfig ESP_CONSOLE_USB_SERIAL_JTAG (untested) |
| FreeRTOS threads |
native — set ESP_RTOS FreeRTOS (see Attach) |
| verifybin |
esptool.py verify_flash (untested) |
Rig deltas
- Locks per
hil skill; reflash-pristine before release applies unchanged.
- One client per USB-SJ: openocd and a terminal on the USB-SJ CDC side
conflict the same way J-Link clients do.
TODO — external JTAG (needs hardware)
S3 JTAG pins GPIO39–42 (MTCK/MTDO/MTDI/MTMS) + any adapter openocd-esp32
supports (ESP-Prog/FT2232-class); would give S3 debugging under live USB
traffic. Mind EFUSE_DIS_PAD_JTAG / JTAG-source strapping. Unverified.
1---2name: esp-target-debug3description: Use when debugging TinyUSB firmware on Espressif boards (ESP32-S3/P4 on the rig — dcd_dwc2 examples, idf.py builds) with the chips' built-in USB-Serial-JTAG — attach/halt/backtrace, breakpoints, FreeRTOS task lists, console capture — or when JTAG "could not find or open device", the 303a:1001 port vanishes, or the S3's debug port turns into the TinyUSB device.4---56# esp-target-debug — Espressif built-in USB-JTAG backend78Methodology — intrusiveness ladder, board locks, dual-side capture, diagnosis9standards — lives in `target-debug`; this skill is the Espressif backend: a10different gdb, a different openocd (fork), no probe serial (the debugger IS a11USB device), and a PHY story that decides whether JTAG exists at all.12Built-in USB-Serial-JTAG only; external JTAG is a TODO (no rig adapter).1314## The PHY map — decides everything (verified on the rig)1516| Board | USB-SJ vs TinyUSB OTG | JTAG while USB device runs? |17|--------------------------|------------------------------------------------------|-----------------------------|18| espressif_p4_function_ev | separate pins: USB-SJ GPIO24/25 (FS), OTG own HS PHY | **yes — coexist** (verified: 303a:1001 + cafe:4008 simultaneously, gdb attach during live CDC traffic) |19| espressif_s3_devkitm | ONE shared PHY/port | **no** — the same hub port flips 303a:1001 → cafe:4008 as the app boots; openocd fails `esp_usb_jtag: could not find or open device!` |2021Flashing works in ANY PHY state: the rig flashes via the boards' CP2102N UART22bridges (hence `tinyusb.json` esptool uids are CP210x serials, not MACs). The23UART side is also the remote reset: `esptool.py --after hard_reset read_mac`.2425### P4 (Function-EV) notes2627- The board has **no USB-SJ connector** — GPIO24 (D−, white) / GPIO25 (D+,28 green) / GND are broken out from header J1 to a rig hub port. Swapped29 D+/D− enumerates as `new low-speed USB device` + error -71; correct shows30 `new full-speed`.3132### S3 (DevKitM) notes3334- Debugging windows: non-USB firmware (`board_test` — attach/halt/symbol35 resolution verified; `usb_new_phy` is absent from the ELF when36 `CFG_TUD/TUH_ENABLED` are 0), bootloader/ROM (always stable), or external37 JTAG (TODO).38- **Keep-alive quirk (verified)**: with app firmware running and nothing39 attached, USB-SJ drops ~4 s after boot (device-side disconnect, then40 half-dead `-71` setup failures until reset). Attach a client inside the41 window — once it survives the window it stays up. Recover via the UART42 reset above.43- PHY mux reference: `RTC_CNTL_RTC_USB_CONF_REG` (0x60008120) bits44 `SW_HW_USB_PHY_SEL`/`SW_USB_PHY_SEL` (TRM 10.56); 0 = eFuse/hardware45 control (default). `esptool.py read_mem/write_mem` peeks and pokes46 registers over plain UART with the chip in download mode.4748## Attach4950```bash51. "$IDF_PATH/export.sh" # openocd-esp32, riscv32-/xtensa-esp32s3-elf-gdb, esptool52openocd -c 'set ESP_RTOS FreeRTOS' -f board/esp32p4-builtin.cfg \53 -c 'adapter serial <MAC-with-colons>' & # S3: board/esp32s3-builtin.cfg54riscv32-esp-elf-gdb -batch -ex 'target extended-remote :3333' \55 -ex 'tbreak tud_task_ext' -ex continue -ex bt -ex 'info threads' -ex detach <elf>56# S3 is Xtensa: use xtensa-esp32s3-elf-gdb with the same arguments57```5859- `adapter serial` = the chip MAC **with colons** — the USB-SJ device's60 iSerial exactly as `lsusb -v -d 303a:1001` or61 `/dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_<MAC>-if00`62 prints it. (The `tinyusb.json` esptool uids are the CP2102N *flasher*63 serials — a different port; never pass those to openocd.)64- `set ESP_RTOS FreeRTOS` must precede the board cfg: with it, `info threads`65 lists every task with name/state/CPU (verified: usbd Running @CPU0, IDLE166 @CPU1, ...); without it, one bare "Remote target".67- The ELF: `idf.py -B <builddir> -DBOARD=<board> build` under the example68 (CLAUDE.md Espressif notes) — symbolized app backtraces verified69 (`tud_task_ext` ← `usb_device_task` ← `vPortTaskWrapper`).70- **Attach may reset the target** (a boot-fresh FreeRTOS tick observed on a71 minutes-old session). Until pinned down, do NOT trust built-in-JTAG attach72 for post-mortem autopsy of a wedged board (`target-debug`'s73 attach-and-halt-only rule); capture state via console or treat the reset74 as part of the reproduce cycle.75- Halting still stops USB service: the host may drop the DUT during long76 halts; after detach the device may need the UART reset to re-enumerate.7778## Scripted-session gotchas (verified)7980- xtensa-gdb batch `continue`/`interrupt` is async-flaky — for scripted81 state reads, halt via openocd telnet :4444 first, then attach gdb to the82 stopped target. Interactive sessions are unaffected.83- cpu1 debug-logic examination can fail (`OCD_ID = 00000000`) —84 `-c 'set ESP_ONLYCPU 1'` degrades to cpu0-only debugging.85- ROM-frame backtraces (`0x4004xxxx` on S3, `0x4fc0xxxx` on P4, all `??`)86 mean the core idles in ROM — break in app code (`tbreak tud_task_ext`)87 for symbolized frames.8889## Technique mapping (vs the `target-debug` arsenal)9091| target-debug technique | Espressif backend |92|------------------------|-------------------|93| GDB autopsy, bp/wp | same flow via openocd-esp32 :3333; RISC-V triggers (P4) / Xtensa 2 bp + 2 wp (S3) |94| Vector catch | none — breakpoint the panic handler; `mcause`/`mepc`/`mtval` on P4 |95| SWO / DWT data trace | none — apptrace over JTAG is the analog (untested: needs CONFIG_APPTRACE + app init) |96| RTT / TU_LOG | console on **UART0 = the CP2102 flasher tty** by default (verified); USB-SJ console needs sdkconfig `ESP_CONSOLE_USB_SERIAL_JTAG` (untested) |97| FreeRTOS threads | native — `set ESP_RTOS FreeRTOS` (see Attach) |98| verifybin | `esptool.py verify_flash` (untested) |99100## Rig deltas101102- Locks per `hil` skill; reflash-pristine before release applies unchanged.103- One client per USB-SJ: openocd and a terminal on the USB-SJ CDC side104 conflict the same way J-Link clients do.105106## TODO — external JTAG (needs hardware)107108S3 JTAG pins GPIO39–42 (MTCK/MTDO/MTDI/MTMS) + any adapter openocd-esp32109supports (ESP-Prog/FT2232-class); would give S3 debugging under live USB110traffic. Mind `EFUSE_DIS_PAD_JTAG` / JTAG-source strapping. Unverified.