Black Magic Probe на ESP32-C5: беспроводной отладчик без OpenOCD

В прошлой статье я сделал обзор популярных способов отладки embedded: GDB-клиент -> GDB-сервер (OpenOCD) -> программатор -> целевой МК. Тогда я сформулировал свои субъективные требования к “идеальному” программатору. И на тот момент у меня уже был минимально рабочий вариант такого программатора. И вот спешу представить к чему привели меня изыскания в этом направлении.
Немного контекста
В прошлой статье я закончил списком того, чего хочу от отладочного инструмента:
Отлаживать устройства удаленно
Не запускать gdb-серверы руками
Не настраивать их и не писать конфигурационные файлы
Не ставить драйвера
Использовать кроссплатформенную легковесную IDE
Поддержка широкого спектра целей: ARM Cortex-M/A/R, RISC-V
Black Magic Probe закрывает почти все пункты: gdb-сервер уже внутри прошивки программатора, OpenOCD не нужен, драйвера не нужны. Да, в комметариях прошлой статьи мне написали что список целей нужно вшивать и они могу не поместиться. В ESP32 не поместиться? В общем поместились ;) Ну и по поводу IDE, можно использовать любую. Это слишком простые интерфейсы чтобы было что-то сложное. Беспроводное соединение устанавливается через TCP, которое поддерживает GDB, а проводное - обычный VCP который gdb тоже поддерживает (но об этом чуть позже).
В процессе разработки список хотелок только расширялся.
Тут и возможность отладиться по проводу (почему бы и нет)
И поддержка RTT логирования по тому же
TCP(черерзtelnetилиnc) илиVCPЕще хочу serial чтобы подключиться к цели в uart и видеть это через
telnetилиnc. Тоже черезTCPилиVCPЕсли из веб инетрфейса нельзя закинуть прошивку Drag & Drop чтобы даже кнопок нажимать не пришлось, то зачем это все вообще делать? [Сарказм] Камень определяется автоматически и выставлен дефолтный адрес который можно поменять на нужный.
Стереть тоже надо одним кликом
Запоминать wifi точки доступа чтобы удобно было везде
А если из известных вокруг ни одной не оказалось то расшарить свою с дефолтными кредами. Можно остаться в ней, но интернета не будет, а можно добавить нужную и ребутнуть.
Если бы я все это сделал и искал каждый раз ip устройства, то можно было бы удалить этот проект ибо смысла во всем этом нет. Так что добавил mDNS. Так что как только устройство подключилось к сети, то в этой сети можно зайти на страничку по имени
blackmagic.local. Это же имя используется вtelnet.
Что внутри: сетевые сервисы
Прошивка поднимает на ESP32-C5 четыре сетевых сервиса:
Порт | Сервис | Назначение |
|---|---|---|
80 | HTTP | Веб-интерфейс: OTA, Wi-Fi, конфигурация пинов |
2345 | GDB remote serial protocol | GDB-сервер - основной сервис |
2346 | RTT console | Segger RTT-консоль цели (telnet/nc) |
2347 | Target serial | UART-мост к целевой плате (telnet/nc) |
Дальше я опишу как что-либо делать через GDB терминал, это может быть избыточно, я же себе жизнь наоборт упростить хотел. Так что если у вас IDE то она все сделает за вас.
GDB-сервер (порт 2345)
Сердце проекта. Подключение из GDB выглядит так:
(gdb) target extended-remote blackmagic.local:2345
(gdb) monitor swdp_scan
(gdb) attach 1
То есть ровно те же команды monitor swdp_scan / attach 1, что и у классического BMP, только вместо /dev/ttyACM0 - host:port. Никакого OpenOCD, никаких конфигов.
Имя blackmagic.local можно использоавть и здесь и при подключении к RTT и т.д.
swdp_scan - это SWD. JTAG тоже поддерживается: у цели ESP32-C5 есть и SWD, и JTAG-режимы, все линии (TMS/SWDIO, TCK/SWCLK, TDI, TDO/TRACESWO, TRST) разведены на GPIO и переназначаются на лету через веб-интерфейс.
RTT (порт 2346)
RTT (Real-Time Transfer) от Segger - способ вывода логов из цели в реальном времени через отладочный интерфейс, почти без нагрузки на CPU.
$ nc blackmagic.local 2346
или через telnet. Внутри GDB управление такое:
(gdb) monitor rtt enable
(gdb) monitor rtt status
Target serial (порт 2347)
UART-мост: то, что целевая плата пишет в свой UART, доступно по TCP на порту 2347. Удобно, когда у цели есть лог в UART, а ты физически рядом не стоишь.
Честное признание: порт сейчас конфликтует с логами самой ESP32 (которые тоже валятся в UART0), так что сервис в статусе «работает, но с оговорками». Думаю что в релизной сборке логи не нужны.
Веб-интерфейс (порт 80)
Тут три страницы:
1. OTA-прошивка. Просто перетаскиваешь .bin файл в браузер - и плата перепрошивается. Drag & drop, ничего кроме браузера не нужно. Если на телефоне есть бинарник, то и с телефона можно прошить.
2. Настройки Wi-Fi. Список SSID и пароль точкек доступа, куда плата должна подключаться. Сохраняется в NVS и перживает перезагрузку.
3. Конфигурация пинов. Какие GPIO платы отвечают за SWDIO/SWCLK/TDI/TDO/TRST. Тоже в NVS, тоже переживает перезагрузку. Меняешь на лету, без перепрошивки. Это я сделал на всякий случай если поменяется разводка платы.
Первый запуск
Сборка - стандартный ESP-IDF:
idf.py set-target esp32c5
idf.py build
idf.py flash monitor
Фронтенд собирается автоматически через npm install && npm run build - CMake это делает сам, руками ничего запускать не надо.
После первой прошивки:
Плата пытается подключиться к сохраненному Wi-Fi
Если не получилось - поднимает свою точку доступа с SSID
blackmagicи паролемblackmagicПодключаешься к этой точке, открываешь
blackmagic.localилиhttp://192.168.4.1, вводишь SSID/пароль своей сетиПлата перезагружается и подключается к твоему Wi-Fi
Дальше она доступна как
blackmagic.local
GDB через USB
Wi-Fi - это основная фича, но провод тоже поддерживается. Прошивка умеет работать через USB-Serial-JTAG - встроенный в ESP32-Cx периферийный блок, который торчит в системе как CDC-устройство:
(gdb) target extended-remote /dev/ttyACM0 # Linux
(gdb) target extended-remote /dev/cu.usbmodem11101 # macOS
При этом логи/консоль остаются на UART0 (отдельные пины), чтобы USB-периферия была полностью отдана под GDB и не делилась с консолью. Все фичи (monitor swdp_scan, attach, RTT) работают и по USB, и по сути - код ядра BMP один и тот же, отличается только транспорт.
Собрать без USB-GDB тоже можно: в CMakeLists.txt есть флаги:
ENABLE_USB_GDB- включает в сборку отладку по USBENABLE_UART_SERIAL- пока оставил отдельное включение serial поскольку нужен лог от espENABLE_RTT- этот флаг предоставляет сам BMP для включения RTT
SDK config: грабли, на которые я наступил
Тут будет самая «техническая» часть статьи - те нюансы, которые не описаны в документации и которые пришлось выяснять экспериментально.
1. Раздел PARTITION_TABLE_SINGLE_APP_LARGE. Прошивка с BMP-ядром + Wi-Fi + HTTP-сервером + фронтендом не влезает в дефолтный 1 МБ app-раздел. Пришлось использовать “большой” раздел на 3 МБ.
2. WHOLE_ARCHIVE для компонента esp32-platform. В проекте есть target_probe.c со слабыми (weak) заглушками функций-зондов, а реальные реализации - в отдельных файлах. По умолчанию линкер выкидывает «неиспользуемые» объектные файлы, и сильные символы из отдельных файлов не переопределяют слабые заглушки - probe-функции просто не вызываются, отладчик не видит цели. WHOLE_ARCHIVE заставляет линкер взять весь архив целиком, и сильные символы побеждают. Не совсем понимаю почему это так работает, но это так.
3. Отключенные watchdog’и. Операции прошивки и отладки цели (flash erase, jtag-транзакции, длинные последовательности) могут превышать таймауты ESP_INT_WDT и ESP_TASK_WDT - система ребутается посреди прошивки цели. Поэтому в sdkconfig.defaults оба watchdog’а выключены:
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT_EN=n
4. Консоль на UART0:
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
чтобы USB-Serial-JTAG не перехватывал консоль и оставался целиком под GDB.
Все это уже в sdkconfig.defaults в репозитории - руками ничего настраивать не надо.
А что с целями?
Ядро прошивки - это оригинальный Black Magic Probe (благодаря blackmagic сообществу), поэтому поддерживается весь спектр целей, которые умеет BMP: ARM Cortex-M, Cortex-A, Cortex-R и RISC-V, по SWD и по JTAG.
Распиновка по умолчанию (для ESP32-C5):
Сигнал | GPIO |
|---|---|
TMS / SWDIO | 26 |
TCK / SWCLK | 25 |
TDI | 23 |
TDO / TRACESWO | 24 |
TRST | 27 |
Как я уже говорил, переназначается через веб-интерфейс и сохраняется в NVS.
VS Code без конфигов
Без настройки IDE не обойтись в любом случае. Я пользуюсь VS Code и тут сложностей нет потому что сообщество cortex-debug поддерживает BMP и конфиг написать достаточно просто. Я создал шаблон конфигурации: bm-vscode-configs - репозиторий с готовым launch.json для Cortex-Debug, включая RTT. Копируешь в свой проект, там будет шаблон файла settings.json в котором нужно прописать путь до .elf, путь до .svd если надо, и саму цель. Без этого минимума никак. Можно запускать отладку.
Итог
Цена решения - стоимость чипа ESP32-C5. Проект открыт: blackmagic-esp32-c5. Никакого дополнительного железа. Все мои хотелки закрыты.
Я конечно не сомневался, но многие сомневались в качестве беспроводного соединения. Мол все будет отваливаться и т.п. Возможно, мне повезло, возможно, я ни разу не столкнулся с плохой сетью в 2026 году, но у меня ни разу отладка так и не отвалилась просто так.
Плата: всё в одном USB
Прошивка работает на голом модуле ESP32-C5, но чтобы пользоваться всеми описанными преимуществами связанными с проводной отладкой я развёл плату, которая собирает в себе всё, что нужно для отладки, и заводит это в один USB-разъём - хаб на борту.
Что на плате:
Устройство | Что даёт | Как видно в системе |
|---|---|---|
ESP32-C5 (USB-Serial-JTAG) | Прошивка esp и GDB-сервер | JTAG и CDC-устройство, |
USB-UART #1 | UART цели | второй CDC |
USB-UART #2 | RTT цели | третий CDC |

То есть не нужно три отдельных кабеля и три отдельных переходника. Воткнул один провод - получил и беспроводной BMP, и проводной GDB, и оба UART цели. Порядок /dev/ttyACM* при этом стабильный, потому что устройства на одном хабе и не переподключаются.
Зачем это, если Wi-Fi и так есть?
Wi-Fi - основная фича, но провод остаётся нужен:
первый запуск чтобы прошить esp32;
банально - питание если у цели его нет;
если по каким-то причинам wifi полосы забиты и соединение нестабильно (у меня пока такого не было);
любые другие причины по которым нужно подключиться по проводу.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.