Полноценная GDB отладка через USB на плате BluePill (STM32F103С8T6)
В данной статье речь пойдет о программировании и полноценной отладке микроконтроллера STM32F103C8T6 через USB.
Однажды, от коллег поступило предложение о участии в IoT проекте. Система предусматривала однопоточный запуск скриптов. Отладка производилась с помощью логов. И тут мне в голову пришла мысль о полноценной удаленной отладке проектов под микроконтроллеры.
Для начала нужно было опробовать все на прототипе. В качестве отладочной платы была выбрана почти всем знакомая BluePill на микроконтроллере STM32F103. Поскольку на данной отладке имеется интерфейс MicroUSB, было принято решение в прототипе использовать именно этот интерфейс. В будущем предполагался переход на UART подключенный к GSM модулю.
Требовалось реализовать загрузчик имеющий несколько функциональных блоков. Задачу на подзадачи и решать их последовательно. Рассмотрим их.
Драйвер интерфейса USB со стороны микроконтроллера.
Код обновления прошивки микроконтроллера с помощью GDB.
GDB сервер.
Вывод отладочных логов.
Обо все по порядку. Для прототипирования был реализован загрузчик (bootloader).
1. Первым был реализован протокол отладочного интерфейса. Т.е. USB. В качестве класса USB-устройств был выбран WinUSB. Для его реализации я использовал воспользовался исходным кодом библиотеки libopencm3. Для этого необходимо описать дескриптор устройства, дескрипторы конфигурации, интерфейса, конечных точек, а так же дескрипторы содержащие стоки «MSFT100» и «WINUSB». Последние два дескриптора требуются для определения устройства как WinUSB. Конфигурация конечных точек (USB-Endpoint) выбрана следующим образом control endpoint 0, bulk out endpoint 1, bulk in endpoint 81, bulk in endpoint 82. Конечная точка с номером ноль присутствует во всех устройствах USB, endpoint 1- применяется для передачи команд в загрузчик микроконтроллера, endpoint 81 — для передачи ответов на команды на компьютер, а 82 — для передачи текстовой информации (логов). Подробнее о USB можно прочитать в публикациях из разряда USB in a NutShell.
2. Требовался код работы с флеш памятью. Вы можете подумать что тут все просто. Это так и не так одновременно. Первая проблема, которая возникла,- невозможность стереть флеш память в обработчике прерывания. Дело в том, что архитектура Cortex M предусматривает два режима работы процессора. Thread и Handler. В первом режиме процессор находится после старта, а так же когда нет активных прерываний. В Handler mode исполняются все обработчики исключений и прерываний. К сожалению, стирание flash-памяти на STM32F103C8T6 в Handler режиме приводит к корректному статусу стирания памяти, но сама память не стирается.
Эта проблема решается посредством запуска кода стирания Flash в Thread режиме. Сделать это можно, так, как обычно происходит в операционных системах. Для этого нужно понимать что такое контекст потока. Это состояние набора регистров процессора, стека, описывающий конкретный момент работы системы. при входе в обработчик прерывания сохраняется контекст текущего работающего потока, а при выходе из обработчика, он восстанавливается и выполнение программы продолжается. Нам при выходе из обработчика нужно лишь восстановить «свой» контекст начала функции стирания Flash памяти. Подобная операция происходит в операционных системах при переключении задач.
Другая проблема является более сложной. Заключается она в том, что при работе с флеш памятью может происходить выполнение обработчика прерывания, той прошивки, которая находится под отладкой. Эта проблема решается несколькими действиями перед стиранием памяти. Первое что требуется сделать,- заблокировать вызов любых обработчиков прерываний, используемых в отлаживаемой прошивке. Или проще говоря тех, которые не используются в Bootloader-e. Но даже в этом случае команда на стирание памяти может поступить в то время, когда один из обработчиков уже выполняется. Для решения этого вопроса и решил воспользоваться пошаговым режимом работы процессора и в таком режиме вывести процессор из всех обработчиков прерываний. После этого флеш-память можно стирать.
3. Требовалось реализовать GDB-сервер. Я воспользовался исходным кодом проекта BlackMagic, для обработки команд приходящих из среды разработки. На самом деле приходящих от приложения arm-none-eabi-gdb. Далее команды транслировались в команды бинарного протокола, который используется для п процессе взаимодействия с микроконтроллером. Нижний уровень GDB-сервера выполнен с использованием библиотеки WinUSB.
4. После того как прототип заработал, я пришел к решению добавить вывод отладочной информации с использованием printf. Для передачи отладочных сообщений использовал endpoint 82. На самом деле 8 — это единица в старшем разряде, указывающая направление передачи данных по шине USB в сторону компьютера (Host-а).
Но таким образом функцией printf можно было пользоваться только в bootloader-е. А как же быть с отлаживаемым приложением? Обычно для взаимодействия с операционной системой используются прерывания/системные вызовы. Так BIOS использова int13, ms-dos int21. Мы же на микроконтроллере воспользуемся системным вызовом, т.е. командой svc. При выполнении данной команды в прошивке, будет вызван обработчик прерывания SVC, находящийся в bootloader-е. Что нам и требовалось сделать.
Bootloader использует 10Kb flash памяти, но зарезервировано 16Kb с целью расширения функционала. Так же используется 4K оперативной памяти. Оперативная память применяется для хранения буферов USB, контекста прерванного процесса, а так же как память стека обработчиков прерываний. Итого. Остается 16Kb из 20Kb оперативной памяти и 48Kb flash памяти. Хотя на самом деле flash-память в контроллере STM32F103C8T6 не 64Kb, а 128Kb,- соответственно остается 112Kb.
В процессе отладки прошивки, возникает один интересный момент. Если, в отладчике делать шаг на потоке, а в это время произойдет вызов обработчика прерывания, то отладчик шагнет в обработчик прерывания. Чтобы такого не происходило, в коде я использовал step режим для выхода из обработчика прерываний. При этом если отладчик в прерывании наткнется на точку останова, будет произведена остановка отладки на точке останова.
И наконец, — что поддерживается:
Загрузка прошивки на плату с использованием GDB.Т. е. непосредственно из среды программирования/отладки. В моем случае это STM32CubeIDE. Адрес вектора прерываний должен находится по адресу 0×8004000.
Просмотр и изменение памяти.
Просмотр и изменение регистров периферии.
Восемь точек останова.
Режим пошаговой отладки.
Принудительная остановка.
В отлаживаемой прошивке нельзя изменять адрес вектора обработчика прерываний. Нельзя изменять приоритеты прерываний. Приоритет должен быть выше или равен 0×40 по значению. Нельзя запрещать прерывания systick, прерывание usb, и прерывания DebugMon, SvcHandler, а так же всех FaultHandler-s.
Код прототипа проекта доступен по ссылке
Видео работы с платой в среде STM32CubeIDE