В данной статье речь пойдет о программировании и полноценной отладке микроконтроллера 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. Адрес вектора прерываний должен находится по адресу 0x8004000.
Просмотр и изменение регистров процессора в контексте отлаживаемой прошивки.
Просмотр и изменение памяти.
Просмотр и изменение регистров периферии.
Восемь точек останова.
Режим пошаговой отладки.
Принудительная остановка.
Отладочная печать в консоль GDB-сервера.
В отлаживаемой прошивке нельзя изменять адрес вектора обработчика прерываний. Хотя можно добавить системный вызов setVectorBase, что решит вопрос. Нельзя изменять приоритеты прерываний на произвольные значения. Приоритет должен находиться в диапазоне 0x40 - 0xF0. Нельзя запрещать прерывания systick, прерывание usb, и прерывания DebugMon, SvcHandler, а так же всех FaultHandler-s.
Касательно исходных кодов. Это был быстро-прототип. Что то в нем сделано на скорую руку, что то возможно не совсем корректно. Но принцип работы проверен.
IgorPie
А можно чуть подробнее, чем в известных IDE отладка плоха? В той же бесплатной и довольно жирной STM32CubeIDE, или все ради того, чтобы SWD не использовать, а только USB?
dmitryrf
К IDE описываемое решение никак не относится, более того, именно STM32CubeIDE автор упомянул. Здесь идея в том, чтобы избавиться от отдельного отладчика (ST-Link, JLink), перенеся его функции прямо в отлаживаемое устройство.
IgorPie
Спасибо. Было неочевидно.
При этом, кто-то все равно на начальном этапе должен произвести прошивку данного загрузчка, на который тратится 25% флэша + другие ограничения.
EvgenySbl Автор
Конечно, ограничения имеются. Но, при этом, не требуется внешнего аппаратного отладчика. Представьте, что интерфейс USB будет заменен на UART, подключенный к сети GSM (GSM-модуль). В таком случае, отладка может производится на устройстве, которое находится в любой точке планеты. Не будете же в каждое устройство добавлять st-link и сервер к нему. Или, например, Вы пожелаете в массы выпустить что то arduino-подобное, но с возможностью отладки. В одной из статей, читал, что загрузчик Arduino для STM32 занимал 20Kb, а тут отладчик вместе с загрузчиком всего 10Kb. Могу привести еще примеры, но сначала хочу выпустить изделие использующее данный функционал. А это было просто прототипирование.
Касательно причины ограничений. Приоритеты прерываний доступны в диапазоне 0x40-0xF0. Это связано с тем, что обработчик прерывания отладочного интерфейса (USB), должен уметь прерывать обработчик DebugMon, а DebugMon — любые прерывания в прошивке. Еще задействован SysTick, который отсчитывает системное время в миллисекундах и SVC, который позволяет делать системные вызовы «printf», intEnable/intDisable, millis.
imitron
Отличная работа!
«Нижний уровень GDB-сервера выполнен с использованием библиотеки WinUSB.» — а что работает на кристалле? Вот за этим наверняка стоит куча всего интересного, что стоило бы рассмотреть. Тогда каждый смог бы по такому туториалу написать себе remote debugger хоть на AVR. Я бы хотел прочитать такой туториал!
EvgenySbl Автор
На процессорах с архитектурой ARM используется отдельный отладочный модуль, который доступен программно. Он поддерживат точки останова, пошаговый режим. Для каждого из этих событий вызывается прерывание. С AVR будет несколько сложнее. Но, если поискать в интернете, имеются статьи на эту тему. Читал, как специалисты использовали прерывание таймера, настроив его таким образом, чтобы оно вызывалось через каждую команду процессора и таким образом эмулировали step-режим. Насколько я помню, для AVR, это один такт для команд не связанных с переходами. Команды ветвления вызывают нарушение работы конвейра, что приводит к выполнению команды за два такта.
Ксательно статей. У меня имеется желание разработать цикл статей по обучению программированию и радиоэлектронике в целом. Начиная от того как работает транзистор, и заканчивая циклом статей по клмпьютерным сетям информационной безопасности и.т.д. Я потратил всю свою сознательную жизнь на изучение такого обьема инфомации. Постараюсь изложить все в сжатом виде. Но это по наличию свободного времени.