Embedded HandbookПриложения
Все разделы › V. Отладка и качество
STM32F407, STM32F103 · уровень средний · 15 мин чтения
Открыть в интерактивном справочнике

35б. Прошивка и отладка из командной строки: OpenOCD, GDB, CubeProgrammer CLI

Среда прошивает и отлаживает одной кнопкой — пока вы за столом с одной платой. Как только нужно прошить десять плат подряд, встроить прошивку в конвейер сборки (раздел 110), отладить на машине без графики или разобрать чужой проект без его среды — оказывается, что за кнопкой стоят обычные программы командной строки, и звать их можно самому. Три из них закрывают почти всё: OpenOCD держит сервер и прошивает, GDB отлаживает через этот сервер, STM32_Programmer_CLI от ST меняет байты конфигурации и говорит с заводским загрузчиком. Раздел — про то, как связать их в рабочую цепочку.

Всё про GDB показано прогоном в QEMU (машина netduinoplus2, Cortex-M4): QEMU поднят как сервер GDB (-S -gdb tcp::1234), а gdb-multiarch из образа stm-arm:1 подключается к нему и выполняет session.gdb. Команды OpenOCD и STM32_Programmer_CLI требуют физического ST-LINK и платы — они приведены по их собственному описанию (OpenOCD 0.12.0, STM32CubeProgrammer 2.23.0) без выдуманных выводов. Сверяет _verify/cli35b_check.py.

Как это называется в документации

Оригинал Перевод
adapter / probe программатор-отладчик (ST-LINK, J-Link, CMSIS-DAP)
interface / transport интерфейс и транспорт (SWD, JTAG)
target config описание камня для отладчика
GDB server / remote сервер GDB, к которому подключается отладчик
monitor command команда не для GDB, а для сервера
breakpoint / watchpoint точка останова / точка наблюдения
SVD (System View Description) XML-описание регистров периферии
option bytes / RDP байты конфигурации / защита чтения
IDCODE / TAP ID опознавательный код отладочного порта

35б.1 Три программы и что умеет каждая

Их путается ровно столько, что стоит развести сразу. Все три говорят с кристаллом через один и тот же разъём SWD, но делают разное.

Три программы командной строки и один разъём SWD до кристалла: OpenOCD как сервер GDB, st-flash и STM32_Programmer_CLI через ST-LINK, GDB подключается к OpenOCD по TCP 3333

Программа Откуда Что умеет Чего не умеет
OpenOCD открытая, любой адаптер прошить, стереть, поднять сервер GDB на порту 3333 сама не отлаживает — только держит сервер
st-flash (stlink-tools) открытая, только ST-LINK прошить .bin, стереть, прочитать ни GDB-сервера, ни .elf, ни отладки
STM32_Programmer_CLI ST, закрытая прошить .elf/.bin/.hex, байты конфигурации, RDP, заводской загрузчик по UART и USB DFU сервер GDB не поднимает
GDB (gdb-multiarch) открытая отладка: точки останова, стек, память, регистры сам к кристаллу не подключается — только к серверу

Отсюда две типовые связки. Прошить и уйти — одна строка OpenOCD, st-flash или CubeProgrammer, без всякого GDB (35б.3). Отлаживать — сервер OpenOCD плюс GDB поверх него (35б.4). Байты конфигурации, RDP и загрузчик по UART — дело STM32_Programmer_CLI (35б.7); у OpenOCD команды stm32f4x options_* тоже есть, но у фирменной программы это ровнее.

Версии, на которых всё проверено: OpenOCD 0.12.0, st-flash v1.7.0, STM32CubeProgrammer 2.23.0, GDB 13.1. Отдельного arm-none-eabi-gdb в наборе arm-none-eabi-gcc может не быть — в образе его нет, и вместо него берут gdb-multiarch (одна программа под все архитектуры).


35б.2 OpenOCD: interface и target, частота SWD, подключение под сбросом

OpenOCD собирается из двух описаний: чем подключаемся (адаптер) и к чему (камень). Каждое — свой файл конфигурации из поставки:

# ST-LINK + любой STM32F4
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
# то же одним файлом платы Nucleo-F4
openocd -f board/st_nucleo_f4.cfg

Файл адаптера interface/stlink.cfg выбирает драйвер hla (adapter driver hla, hla_layout stlink) — «high level adapter», когда ST-LINK сам исполняет последовательности SWD. Файл камня target/stm32f4x.cfg заводит ядро cortex_m и знает его флеш. Для F1 — target/stm32f1x.cfg, плата BluePill покрывается interface/stlink.cfg плюс этим target. Готовые файлы плат (board/st_nucleo_f103rb.cfg, board/stm32f4discovery.cfg) просто подключают нужные interface и target и добавляют transport select hla_swd.

Частота SWD. По умолчанию target/stm32f4x.cfg стартует на adapter speed 2000 — 2 МГц, безопасно на холодном камне, пока тактирование не настроено. Скрипт инициализации после запуска PLL поднимает её до adapter speed 8000. Задать вручную — до init:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "adapter speed 1800"

Слишком высокая частота на длинных проводах даёт Error: init mode failed — первое, что снижают при капризном подключении.

Сброс. Как OpenOCD дёргает NRST, задаёт reset_config. У stm32f4x.cfg это srst_nogate (сброс не глушит такт отладки), а cortex_m reset_config sysresetreq велит сбрасывать через регистр ядра. Если камень сразу после старта уходит в сон или переназначает выводы SWD (частая беда с загрузчиком в приложении или с настроенным энергосбережением), к нему не подключиться на ходу — нужно подключение под сбросом: адаптер держит NRST прижатым, пока устанавливает связь.

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
        -c "reset_config connect_assert_srst" -c "init" -c "reset halt"

⚠️ connect_assert_srst требует разведённого NRST. На плате, где вывод сброса ST-LINK не подключён (только SWDIO, SWCLK, земля), приём не сработает: держать нечего. На такой плате остаётся заводской загрузчик через BOOT0 (раздел 4.9) или полное стирание.


35б.3 Прошить одной строкой: program … verify reset exit

Самый частый сценарий без всякой отладки — залить и перезапустить. У OpenOCD для этого одна команда program, а -c после неё закрывает сервер:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
        -c "program fw.elf verify reset exit"

Четыре слова после имени файла — это шаги: program пишет во флеш (адрес берётся из .elf), verify читает обратно и сверяет, reset перезапускает камень, exit завершает OpenOCD, чтобы команда вернула управление оболочке и код выхода. Без exit сервер останется висеть и ждать подключения.

🔧 st-flash — когда нужен только байт-в-байт образ. Она берёт .bin (не .elf) и явный адрес:

st-info --probe                       # проверить, что ST-LINK видит камень
st-flash erase                        # стереть всю флеш
st-flash write fw.bin 0x08000000      # залить с начала флеш

🔧 STM32_Programmer_CLI — фирменная, берёт .elf. Подключение задаётся -c port=…, запись — -w (он же --download), сброс после — -rst:

STM32_Programmer_CLI -c port=SWD -w fw.elf -rst

По собственному описанию программы, -w принимает «bin, hex, srec, s19, elf, stm32 or tsv file», а начальный адрес нужен только для .bin — из .elf и .hex он читается сам.

⚠️ Забытый verify — прошивка «легла», но не проверена. На плохом контакте или севшей батарее программатора запись проходит частично, а устройство молча не стартует. verify в OpenOCD и -v в CubeProgrammer стоят доли секунды и снимают полдня поисков.


35б.4 GDB без среды: load, monitor reset halt, точки останова, x, info registers

Отладка — это сервер плюс GDB. С настоящим ST-LINK сервер поднимает OpenOCD (без exit, он ждёт на порту 3333); в опыте его роль играет QEMU на порту 1234. GDB подключается к серверу и дальше работает одинаково.

Один сеанс GDB: команды load, break, continue и monitor идут серверу, а он переводит их в запись флеш и остановку ядра по SWD

Ключевое различие двух видов команд. Обычные команды GDB (break, continue, print, x) отладчик исполняет сам, читая память и регистры через сервер. Команды, начинающиеся с monitor, GDB не понимает — он пересылает их серверу дословно: monitor reset halt — сброс с остановкой на первой команде, monitor reset init — плюс настройка тактов из скрипта, monitor flash … — работа с флеш. Это и есть способ дотянуться до того, что умеет OpenOCD, не выходя из GDB.

Полный сеанс в опыте (QEMU как сервер, порт 1234):

target extended-remote localhost:1234
load
compare-sections
break main
continue
info registers pc sp
break mix
continue
backtrace
finish
watch ticks
continue
print/x result
x/8wx &samples

Что напечатал GDB, дословно из прогона:

Loading section .isr_vector, size 0x40 lma 0x8000000
Loading section .text, size 0xd0 lma 0x8000040
Start address 0x08000042, load size 272
Section .isr_vector, range 0x8000000 -- 0x8000040: matched.
Section .text, range 0x8000040 -- 0x8000110: matched.

Breakpoint 1, main () at fw35b.c:50
pc  0x80000da   sp  0x2001fff8

Breakpoint 2, mix (x=1) at fw35b.c:13
#0  mix (x=1) at fw35b.c:13
#1  0x080000c0 in fill () at fw35b.c:23
#2  0x080000ee in main () at fw35b.c:53
Value returned is $1 = 270369

Hardware watchpoint 3: ticks
Old value = 0
New value = 1
$1 = 0xaf422ba3
0x20000000 <samples>: 0x00042021  0x04080601  0x9dcca8c5  0x1255994f
0x20000010 <samples+16>: 0x8ef917d1  0x2c6f5bd0  0x25b2331a  0x19f91cb2

Разберём по строкам. load кладёт секции .elf во флеш через сервер: 272 байта, .isr_vector с адреса 0x08000000 и .text за ним; на настоящем камне это и есть прошивка. compare-sections тут же читает обратно и подтверждает matched — тот же смысл, что verify. break main + continue останавливают на входе в main: pc = 0x80000da, вершина стека sp = 0x2001fff8 (у F4 стек с конца 128 КБ ОЗУ). break mix + continue ловят функцию, а backtrace показывает, откуда пришли: mix вызвана из fill, та — из main. finish доводит до возврата и печатает результат mix(1) — 270369. watch ticks ставит точку наблюдения на переменную: следующий continue встаёт ровно на записи, Old value = 0 / New value = 1. print/x result даёт итог программы 0xaf422ba3, а x/8wx &samples — восемь слов массива в памяти: это последовательность xorshift32 от единицы, и её сумма как раз 0xaf422ba3.

Число аппаратных точек останова у ядра ограничено (у Cortex-M3 и M4 их шесть, раздел 35.1) — GDB не поставит седьмую. Точка наблюдения watch тоже аппаратная и тоже считанная, ставится на адрес переменной.

⚠️ load без живого сервера вешает GDB. Если OpenOCD не запущен или ST-LINK не видит камень, target extended-remote не подключится, а load будет ждать. Сначала — рабочий сервер, потом GDB.

С настоящим ST-LINK всё то же, только первой строкой target extended-remote localhost:3333, а сброс с остановкой даёт monitor reset halt перед load. У QEMU таких monitor-команд нет — там сброс всей машины делает monitor system_reset.


35б.5 Сценарии: .gdbinit, свои команды, gdb в конвейере

Набирать десять строк каждый раз незачем — GDB читает их из файла.

.gdbinit в каталоге проекта подхватывается автоматически, но с оговоркой про безопасность. В опыте GDB отказался выполнить локальный .gdbinit:

warning: File ".gdbinit" auto-loading has been declined by your
`auto-load safe-path' set to "$debugdir:$datadir/auto-load".

Это защита: чужой репозиторий не должен исполнять произвольные команды при открытии. Разрешают либо разово ключом, либо строкой в домашнем ~/.gdbinit:

gdb-multiarch -iex "set auto-load safe-path /" fw35b.elf   # доверять всему
# или в ~/.gdbinit: add-auto-load-safe-path /путь/к/проекту

С разрешением тот же файл выполнился — в опыте он напечатал local .gdbinit loaded.

Свои команды заводят через define — удобно для повторяющихся действий:

define reflash
  monitor reset halt
  load
  compare-sections
end

Теперь reflash в сеансе перешивает и сверяет одной командой.

GDB в конвейере — прошивку гоняют как обычную программу и смотрят на код выхода (раздел 110). Тонкость: в неинтерактивном скрипте continue после далёкой точки останова возвращается раньше, чем ядро встанет, и следующая команда падает с «Cannot execute this command while the target is running». Надёжный приём — не гнать программу из GDB, а подключиться к уже досчитавшей: QEMU (или плату) запускают без остановки, GDB подключается, сервер тут же останавливает ядро в цикле после расчёта, а скрипт лишь читает результат и возвращает код:

target extended-remote localhost:1234
if result == 0xaf422ba3
  quit 0
else
  quit 1
end

В опыте годный образ дал CI: result ok и код выхода 0, а тот же скрипт с подменённым эталоном — CI: result BAD и код выхода 1. Дальше система сборки смотрит на код, как у любой команды.


35б.6 SVD из пака: регистры периферии по имени

Голый GDB знает переменные программы, но не периферию: USART1->SR для него просто адрес 0x40011000. Имена регистров и полей лежат в SVD — XML-описании камня, которое ST кладёт в пакет поддержки (.../Keil/STM32F4xx_DFP/…/CMSIS/SVD/ STM32F405.svd). Небольшой скрипт на Python внутри GDB читает этот файл и добавляет команду per, печатающую регистр с разобранными битами:

source svd35b.py
per USART1 SR CR1

В опыте (после настройки USART в программе) команда напечатала:

USART1.SR  @ 0x40011000 = 0x000000C0  TXE=1 TC=1
USART1.CR1 @ 0x4001100C = 0x00002008  UE=1 TE=1

Адреса и биты не выдуманы: базовый адрес USART1 0x40011000, смещение SR — 0, CR1 — 0x0C, бит TXE — 7-й, TC — 6-й, UE — 13-й, TE — 3-й — всё из того же SVD. Скрипт учитывает, что в SVD от ST регистры USART1 заданы через derivedFrom от USART6, и берёт их оттуда. У сред (CubeIDE, Keil) окно периферии устроено так же — читает SVD; из командной строки то же делается двадцатью строками Python.


35б.7 STM32_Programmer_CLI: байты конфигурации, RDP, заводской загрузчик по UART

То, чего нет у чистого GDB, — работа с байтами конфигурации (раздел 11.6) и заводским загрузчиком. Это дело фирменной программы.

Показать и задать байты конфигурации. По её собственному описанию, -ob (--optionbytes) «displaying or modifying» байтов, -ob displ печатает весь набор, а -ob <имя>=<значение> программирует один:

STM32_Programmer_CLI -c port=SWD -ob displ            # показать все
STM32_Programmer_CLI -c port=SWD -ob RDP=0xAA         # снять защиту чтения (F4)
STM32_Programmer_CLI -c port=SWD -ob nRST_STOP=1      # не сбрасываться в STOP

RDP — защита чтения. Уровень задаётся байтом RDP; значения из заголовков HAL (OB_RDP_LEVEL_*):

Уровень Значение (F4) Что делает Снятие
0 0xAA защиты нет, отладчик читает флеш —
1 любое, кроме 0xAA и 0xCC (напр. 0x55) отладчик флеш не читает полным стиранием флеш
2 0xCC отладка и загрузчик отключены навсегда нельзя

У F1 уровня 2 нет, а «без защиты» — 0xA5 (раздел 11.6). Перевод с уровня 1 на 0 фирменная программа выполняет сама, попутно стирая всю флеш, — иначе защита не имела бы смысла.

⚠️ Уровень 2 необратим. -ob RDP=0xCC навсегда отключает SWD и заводской загрузчик: перепрошить камень будет нечем. На отладке ставят только уровень 1 и только осознанно.

Заводской загрузчик по UART. Когда SWD недоступен (защита, нет разъёма, серийная заливка), прошивают через встроенный загрузчик: BOOT0 = 1, сброс (раздел 4.9) — и камень слушает USART1. CubeProgrammer подключается к нему через переходник USB-UART, задав COM-порт и скорость вместо port=SWD:

STM32_Programmer_CLI -c port=COM5 br=115200 -w app.bin 0x08000000 -v -rst

Тот же -w, но транспорт — UART; загрузчик принимает образ и проверяет контрольную сумму каждого блока сам. У F4 загрузчик умеет ещё и USB DFU — тогда -c port=usb1 без всякого переходника.


35б.8 Грабли

Клон камня — OpenOCD ругается на IDCODE. Дешёвые «STM32» (GD32, CKS и прочие) отвечают отладочным кодом, не совпадающим с ожидаемым, и OpenOCD встаёт на expected-id. Лечится подсказкой настоящего кода до target — у F4 по SWD это 0x2ba01477, у F1 — 0x1ba01477 (по JTAG — 0x4ba00477 и 0x3ba00477):

openocd -f interface/stlink.cfg -c "set CPUTAPID 0x2ba01477" -f target/stm32f4x.cfg

Совсем отключить проверку — set CPUTAPID 0.

monitor-команды уходят серверу, а не GDB. monitor reset halt — это команда OpenOCD; в QEMU её нет, там сброс машины — monitor system_reset. Перепутав сервер, получаешь «Undefined command».

st-flash не берёт .elf. Ей нужен .bin и явный адрес; .elf и .hex понимают только OpenOCD (program) и CubeProgrammer (-w).

load в GDB висит. Сервер не запущен или ST-LINK не видит камень: load ждёт подключения. Сначала поднять OpenOCD, потом GDB.

continue в неинтерактивном скрипте гонит вперёд. В батч-режиме он возвращается раньше остановки — следующая команда падает с «target is running». Подключаться к уже досчитавшей программе или гнать сеанс через Python-API GDB (35б.5).

RDP снят, а флеш пустая. Снятие защиты чтения с уровня 1 обязательно стирает всю флеш — это не сбой, а замысел (раздел 11.6). Прошивку заливать заново.

Забыт verify/-v. Запись прошла частично, устройство молча не стартует. Проверка чтением стоит доли секунды (35б.3).


35б.9 Итоги

Дальше: раздел 35в «Отказ в поле: слепок состояния, переживающий сброс, и разбор после перезагрузки» — как сохранить отказ прибора без отладчика и разобрать его по ELF.

Открыть в интерактивном справочнике