Как работает stm32-diag-record

Два маршрута записывают значения прошивки в MCAP или bag без ROS: компьютер опрашивает память через OpenOCD либо заглушка в SRAM снимает кадры по прерыванию таймера и передаёт их через RTT. Флеш не перепрошивается в обоих случаях, но вмешательство в выполнение программы различается.

Выбор маршрута записи

СвойствоA — опрос памяти компьютеромB — выборка на MCU и передача RTT
Ключ--backend openocd--backend rtt-timer
Кто снимает значенияPython на компьютере запрашивает mdwЗаглушка в SRAM читает поля в IRQ свободного таймера
Ритм выборкиПланировщик хоста и задержки SWD; по умолчанию 10 ГцТаймер MCU; на F411CE проверены номинальные 1000 Гц
ВмешательствоБез добавления кода и остановки ядра при опросе; reset только по явному ключуВременный код/буфер SRAM, таймер и перехват вектора; halt при установке и снятии, нагрузка ISR при записи
ВремяМетка цикла от компьютера; счётчик MCU можно записать полемTick прошивки в кадре; его реальную длительность нужно калибровать
ПотериИзменения между опросами не видныИсходные seq/tick и оценка пропусков между принятыми кадрами; не полная трасса событий
ЗавершениеЗакрытие записи и собственных соединений/процессовGDB: выход из IRQ, восстановление регистров и проверка продолжения tick; отчёт teardown

Оба маршрута используют SWD физически. RTT — буферизованная передача кадров из RAM, а не отдельный UART. Renode — вариант чтения памяти без платы, с собственным временем эмуляции.

Путь данных

подготовка, плата не нужна запись, плата работает вариант без платы просмотр
1. Где лежат значения: адреса из ELF и SVD
Выполняется перед каждой записью за секунды. Плата и OpenOCD не участвуют.

fields.toml

что записывать: имя, тип, выражение

expr = "&probe_telemetry.frames"

прошивка.elf

символы и отладочная информация DWARF

сборка с -g

файл SVD по желанию

имена регистров периферии от производителя

читает

resolve_addresses.py

выражения C вычисляет GDB по DWARF, поля svd: вычисляет svd_lookup.py

gdb-py3 -batch, без цели
пишет

addresses.json

для каждого поля: адрес, тип, сдвиг и маска

/probe/frames → 0x20000030
2A. Опрос памяти компьютером и запись файла
Ядро не останавливается: отладочный порт Cortex-M читает память параллельно работе программы.

Микроконтроллер

прошивка работает как обычно; RAM и регистры периферии

STM32, AT32, nRF52, RP2040…
SWD

Отладчик

любой, который понимает OpenOCD

ST-Link, CMSIS-DAP, J-Link
USB

OpenOCD

запускается скриптом без сброса цели; принимает команды по telnet

127.0.0.1:4444, mdw
telnet

record_diag.py

с частотой --rate читает слова, переводит их в значения, ставит метку времени

kind: uint32, int32, float32…
сообщения

Файл

mcap_writer.py или bag_writer.py, только стандартная библиотека Python

diag/record-…mcap | .bag

Без платы: Renode

Вместо микроконтроллера, отладчика и OpenOCD работает эмулятор. Скрипт читает те же адреса через монитор Renode, эмуляция не прерывается.

--backend renode, telnet 127.0.0.1:1234, sysbus ReadDoubleWord
2B. Выборка на MCU по таймеру → RTT → файл
Отдельный маршрут rtt-timer: установка и снятие требуют остановки ядра. Во время записи выполняются прошивка и добавленный ISR.

Таймер MCU

IRQ задаёт моменты выборки

свободный TIM, проверенная частота тактирования
IRQ

Заглушка в SRAM

читает поля и формирует кадр с исходными seq и tick

кадры

Буфер RTT в RAM

OpenOCD забирает накопленные кадры через SWD

TCP

record_diag.py

декодирует кадры, проверяет seq, пишет поля и служебные каналы

запись

MCAP / bag + JSON

данные, статистика RTT и отдельный отчёт снятия перехвата

3. Просмотр
ROS не нужен ни для записи, ни для mcap. Каждое поле в файле — отдельный канал с именем topic.

.mcap

открывается загрузчиком MCAP в PlotJuggler под Windows и Linux

проверка: mcap info
открыть

PlotJuggler

графики каналов, их сравнение, масштаб по времени

Windows, Linux
или

.bag (ROS1)

в PlotJuggler открывается только с плагином ROS1, то есть под Linux с ROS; под Windows такого плагина нет

rosbag info, Python rosbags

Маршрут A: один цикл опроса (10 раз в секунду по умолчанию)

ГруппировкаАдреса, которые лежат не дальше 256 байт друг от друга, читаются одной командой.
Чтениеmdw 0x20000030 6 возвращает шесть 32-битных слов подряд.
СборкаНевыровненное поле собирается из двух соседних слов, у поля регистра применяются сдвиг и маска.
ПереводСлово трактуется по kind: как целое, число со знаком, float или миллисекунды в секунды.
ФильтрЗначения вне min и max не пишутся, а считаются отдельно.
ЗаписьВсе поля цикла получают одну метку времени от часов компьютера.

Откуда синтаксис описания полей

# файл в формате TOML; [[field]] — одно поле
[[field]]
topic   = "/probe/frames"
message = "std_msgs/UInt32"
kind    = "uint32"
expr    = "&probe_telemetry.frames"

[[field]]
topic   = "/probe/pin_frame"
message = "std_msgs/UInt32"
kind    = "uint32"
expr    = "svd:GPIOB.ODR.ODR15"
КлючЧем определяется
файлобщий формат конфигураций TOML
exprвыражение языка C, как в команде GDB print: &var, &obj.field, &arr[3]. GDB вычисляет его по ELF, результат — адрес
svd:соглашение навыка svd:ПЕРИФЕРИЯ.РЕГИСТР[.ПОЛЕ]; имена берутся из SVD-файла производителя
messageимена стандартных типов ROS1 std_msgs: так файл понимают PlotJuggler и инструменты ROS
kindкороткий список навыка: uint32, int32, float32, uint32_seconds
topicлюбое имя; по обычаю ROS — /группа/имя

Маршрут A: не только STM32

Навык проверен на STM32, но работает с любым Cortex-M, с которым работает OpenOCD. Подходит ли MCU, решают инструменты, а не сам навык:

STM32 F1, F4: в CI AT32F403A: план R20 GD32nRF52RP2040другие Cortex-M
  • OpenOCD знает отладчик (--interface) и цель (--target, штатный скрипт или скрипт производителя);
  • память читается без остановки ядра: нет защиты от чтения, скрипт цели не сбрасывает её при подключении;
  • прошивка собрана с отладочной информацией, есть GDB с Python;
  • для полей svd: — SVD производителя, для работы без платы — модель MCU в Renode.

Проверка нового MCU начинается с доктора с теми же ключами, что и у записи:

python record_diag.py --doctor \
  --interface interface/cmsis-dap.cfg \
  --target target/nrf52.cfg \
  --elf app.elf --fields my_fields.toml

Доктор показывает, найдены ли конфигурации, отвечает ли отладчик, какое ядро и в каком состоянии цель, вычисляются ли адреса и читается ли слово на ходу.

Что важно понимать о базовом опросе SWD

Прошивка не меняетсяВ неё не добавляется код. Читается то, что уже есть в памяти.
Ядро не останавливаетсяНет точек останова и сброса (кроме ключа --reset). UART остаётся свободным.
Время — от компьютераДля точных интервалов добавьте в поля счётчик платы, например миллисекунды SysTick.
Это выборка, а не трассаПишется значение в момент чтения. Изменения между чтениями не видны. Таймерный RTT тоже делает выборки; полная трасса событий требует отдельного подхода.
Время в Renode другоеЭмуляция обычно идёт медленнее реального времени, интервалы смотрите по счётчику платы.
Сверяйте с эталономОшибка в смещении на 4 байта даёт правдоподобное, но чужое значение.

Общая база диагностики

Оба навыка используют одинаковые модули чтения OpenOCD, декодирования полей, поиска GDB и управления дочерними процессами. Исправления транспорта применяются к записи в файл и к публикации ROS. Поддерживаются маски, сдвиги и невыровненные поля.

Источник модулей — stm32-ros-alt-channel; каждый навык содержит полную копию нужных скриптов и устанавливается самостоятельно. CI проверяет совпадение копий и оба потребителя, в Emulation используются адреса из ELF stm32-skills-probe.

Назначение маршрутов сохраняется: ROS/WSL и rosserial — в ROS-навыке, запись без ROS, Renode и RTT — в diag-record. В CI тест ROS-публикации подменяет middleware. Локально проверена также публикация через настоящий ROS Noetic в WSL с TCP-имитатором OpenOCD; SWD→ROS и RTT/Ctrl+C после рефакторинга проверены на F411CE; UART требует отдельной проверки.

Отдельный режим RTT на таймере

--backend rtt-timer устанавливает заглушку в SRAM и временно перехватывает IRQ свободного таймера. Флеш не меняется, но ядро останавливается при установке и снятии; этот режим влияет на выполнение прошивки. Время кадров берётся из tick прошивки.

Запись сохраняет исходные seq/tick и статистику потерь между принятыми кадрами. Завершение через GDB проверяет отсутствие active IRQ, восстановление регистров и рост tick. Обычный выход и Ctrl+C проверены владельцем на F411CE при номинальных 1000 Гц до выделения общей базы; после рефакторинга повторён RTT/Ctrl+C: 7971 кадр, снятие успешно, LED продолжает мигать. CI использует прошивку-зонд и Renode.

Описание навыка (SKILL.md) и скрипты · версия 1.8.0

Повтор на F411CE после общей базы

19.10.2026, версия d003ca5: RTT с номинальными 1000 Гц и остановкой Ctrl+C — 7971 кадр, seq без разрывов, шаг tick 1 мс. Код 0, регистры восстановлены, TIM3 не активен, tick после resume вырос на 244 мс, LED продолжает мигать. Завершение началось вне ISR.

Ровный tick не подтверждает абсолютную частоту: отдельное сравнение с Windows показало отставание счётчика этой прошивки на 2,72%. Опыт HSI → HSE → HSI локализовал основную разницу на HSI данного экземпляра. Короткий опыт не заменяет абсолютную калибровку.