Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

daplink-s3 — a CMSIS-DAP SWD probe on a bare ESP32-S3

Turns an ordinary ESP32-S3 dev board into a CMSIS-DAP debug probe: OpenOCD, pyOCD and PlatformIO talk to it as a standard adapter, and it drives SWD on two GPIOs of your choosing.

It is Seeed's Seeed_Arduino_DAPLink for the CMSIS-DAP protocol — the same code the XIAO Debug Mate ships — on the ESP32 Arduino core's own USB stack rather than Adafruit_TinyUSB. That choice is the whole point of this repository; see Why not Adafruit_TinyUSB below.

Verified against an STM32G071:

CMSIS-DAP: FW Version = 2.0.0
CMSIS-DAP: Serial# = DAPLINK-S3-1
SWD DPIDR 0x0bc11477
[stm32g0x.cpu] Cortex-M0+ r0p1 processor detected — Examination succeed

Which chips work

ESP32-S2 and ESP32-S3 only. The chip has to enumerate as an arbitrary USB device, which needs the native USB-OTG controller. Classic ESP32 has no USB peripheral at all; the C3, C6 and H2 have only the fixed-function USB Serial/JTAG controller, which cannot present a CMSIS-DAP HID interface — no firmware works around that.

Wiring

signal default GPIO note
SWDIO 3 PIN_SWDIO in platformio.ini
SWCLK 1 PIN_SWCLK
GND required
nRESET 38 optional, for connect-under-reset
TDO / TDI 42 / 41 JTAG only, unused for SWD
log UART (Serial) 43 / 44 UART0 — the USB port belongs to CMSIS-DAP

SWD needs only SWDIO, SWCLK and GND. VTref is not used: the target runs from its own supply. Any free GPIOs will do — but keep them off the S3's strapping pins (0, 3, 45, 46) where you can. The defaults deliberately move nRESET and the status LEDs off GPIO45/46, which the library drives as outputs and which select VDD_SPI voltage and ROM-log enable at reset.

Build and flash

pio run -e corehid
pio run -e corehid -t upload --upload-port /dev/cu.usbmodemXXXX

Two ESP32-S3 flashing details that cost real time if you meet them cold:

  • The board manifest hard-codes -DARDUINO_USB_MODE=1. A -D...=0 in build_flags only shadows it, and which one wins depends on command-line order — the build ends up with both. board_build.extra_flags is replaced outright here so OTG mode actually takes effect.
  • USB persist. After esptool writes over the ROM's USB-Serial/JTAG, the S3 latches that peripheral across a soft reset, so correct OTG firmware still cannot take the port. esptool --after watchdog_reset, or a power cycle, clears it; a plain --after hard_reset does not.

Because the firmware takes the native USB port, the board stops being a serial port once it runs. To avoid needing the BOOT button on every iteration it waits USB_TAKEOVER_DELAY_MS (10 s) before claiming USB, during which the ROM's USB-Serial/JTAG is still enumerated. PlatformIO's own start-up eats most of that window, so latch the chip with esptool first and write at leisure:

esptool --port $P --before default_reset --after no_reset flash_id
esptool --port $P --chip esp32s3 --before no_reset --after no_reset write_flash -z \
  0x0 .pio/build/corehid/bootloader.bin \
  0x8000 .pio/build/corehid/partitions.bin \
  0x10000 .pio/build/corehid/firmware.bin

Several probes on one host

Every CMSIS-DAP adapter shares VID:PID 0x0d28:0x0204, so OpenOCD takes whichever it enumerates first. This firmware sets a distinct USB serial and product string (DAPLINK_SERIAL, DAPLINK_PRODUCT), so a specific probe can be selected:

adapter driver cmsis-dap
adapter serial DAPLINK-S3-1      ;# cmsis_dap_serial on OpenOCD < 0.12

OpenOCD identifies an adapter by finding the substring CMSIS-DAP inside the product string, so a name like CMSIS-DAP S3 probe 2 still works.

If the target boots into its ROM bootloader

On STM32 parts where SWCLK doubles as BOOT0 (the G0 family's PA14-BOOT0), boot mode is sampled from that pin at every reset. A probe that idles SWCLK at INPUT_PULLUP — the usual CMSIS-DAP convention, and the library's default — puts the ESP32's ~45 kΩ pull-up against the board's typical 100 kΩ pull-down. The pull-up wins, and the target boots system memory instead of your image, every time, silently: programming succeeds, verify passes, and the firmware never runs. The tell is pc reading 0x1fff_xxxx after a halt.

-DDAPLINK_SWCLK_NO_PULL (on by default here) idles SWCLK floating and lets the target's own pull-down define BOOT0. SWD does not need an idle pull on the clock.

To rescue a target already stuck there, set the vector table as well as the PC — jumping to the reset handler alone leaves VTOR pointing at ROM, so polled code runs for a frame or two and then every interrupt lands in the bootloader:

halt
mww 0xE000ED08 0x08000000
reg msp <word at 0x08000000>
reg pc  <word at 0x08000004, thumb bit cleared>
resume

Why not Adafruit_TinyUSB

The obvious build — Seeed's library with Adafruit_TinyUSB, as the Debug Mate does under the Arduino IDE — enumerates, but as the wrong device: VID:PID 0x303a:0x1001, the board name as product string, and zero interfaces.

On arduino-esp32 the core owns the tud_descriptor_*_cb callbacks and builds the configuration descriptor from interfaces registered through tinyusb_enable_interface(). Adafruit's ESP32 path never calls that — it only fills its own descriptor structs, and the hardware is brought up by the core's USB.begin(). The two libraries also define the same tud_hid_* callbacks, so only one of them can be linked at all.

src/main_corehid.cpp therefore keeps the DAPLink protocol core and takes USB from the core's USBHID. deadend/main_adafruit_tinyusb.cpp is the Adafruit version, kept as the record of that route; it is not built.

env:diag is the bisection tool that settled it: plain core USB CDC, no TinyUSB library, no DAPLink. If that enumerates, the OTG controller is fine and the fault is above it — and it also hands you a console on the native port, which the probe firmware cannot.

Local changes to the vendored library

lib/Seeed_Arduino_DAPLink is Seeed's library (MIT) with two documented edits:

  • DAP_config.h: the ESP32 pin defines are wrapped in #ifndef so platformio.ini can override them, and DAPLINK_NO_ADAFRUIT_TINYUSB drops the hard #include "Adafruit_TinyUSB.h".
  • DAP_config.cpp: SWCLK idles floating under DAPLINK_SWCLK_NO_PULL.
  • DAP.cpp: DAP_ID_SER_NUM answers from DAPLINK_SERIAL instead of Adafruit's TinyUSBDevice.

Licence

MIT — see LICENSE. The vendored library is MIT, Copyright (c) Seeed Technology Co., Ltd.; its own licence text is kept in lib/Seeed_Arduino_DAPLink/LICENSE.

About

CMSIS-DAP SWD probe firmware for a bare ESP32-S3 — Seeed's DAPLink protocol core on the ESP32 Arduino core's own USB stack

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages