Stm32 отладка по usb

Обновлено: 06.07.2024

Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.

Интерфейсы

Устройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.

Интерфейс управления представляет собой расширение базового класса интерфейса с тем отличием, что содержит одну конечную точку (хотя, насколько я понял, без необходимости поддержки всех возможностей можно обойтись вообще без конечной точки) и набор "функциональностей", определяющих возможности устройства. В рамках разрабатываемой библиотеки данный интерфейс представлен следующим классом:

В базовом случае интерфейс должен поддерживать три управляющих (setup) пакета:

SET_LINE_CODING: установка параметров линии: Baudrate, Stop Bits, Parity, Data bits. Некоторые проекты, на которые я ориентировался (основным источников вдохновения стал этот проект), игнорируют данный пакет, однако в этом случае некоторые терминалы (например, Putty), отказываются работать.

GET_LINE_CODING: обратная операция, в ответ на эту команду устройство должно вернуть текущие параметры.

SET_CONTROL_LINE_STATE: установка состояния линии (RTS, DTR и т.д.).

Код обработчика setup-пакетов:

Ключевой момент нумерации, а именно формирование дескрипторов, выполнен по уже привычной схеме раскрытия variadic-ов, что позволяет избавиться от зависимости классов в иерархии:

Поскольку мои познания в CDC-устройствах весьма небольшие, из просмотренных примеров я сделал вывод, что управляющий интерфейс почти всегда одинаковый и содержит 4 функциональности: Header, CallManagement, ACM, Union, поэтому добавил упрощенный шаблон интерфейса:

Применение разработанных классов

Для использования разработанных классов достаточно объявить две конечные точки (Interrupt для первого интерфейса и двунаправленную Bulk для второго), объявить оба интерфейса, конфигурацию с ними и, наконец, инстанцировать класс устройства:

Отладка и тестирование

Написать код правильно с первого раза практически невозможно, поэтому очень полезным оказалось все-таки разобраться с инструментами перехвата USB-пакетов, поэтому кратко опишу особенности и проблемы, с которыми столкнулся лично я.

Так и не удалось применить логический анализатор, он просто ничего не показывает. Полагаю, что дело в том, что это самый дешевый клон Seale Logic и если бы был в наличи нормальный аппарат, то все бы получилось. Главное преимущество логического анализатора заключается в том, что он позволяет отслеживать обмен данными еще в процессе нумерации, в то время как программы на стороне хоста показывают пакеты только для тех устройств, которые эту нумерацию успешно прошли.

WireShark с установленным UsbPcap оказался весьма удобным, он нормально парсит все данные, так что поиск ошибок значительно упрощается. Главное, что нужно сделать - правильно установить фильтры. Не нашел ничего лучше, кроме выполнить следующие две операции:

Сначала отфильтровать по заведомо известному значению. Например, по значению PID, которое присутствует в ответе устройства на запрос GET_DEVICE_DESCRIPTOR. Фильтр: "usb.idProduct == 0x5711". Это позволит быстро определить адрес устройства.


Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: "usb.addr contains "1.19"".


Однако стоит заметить, что UsbPcap может доставить некоторые трудности, под катом опишу ситуацию, в которую недавно попал и потратил кучу времени и нервов.

Проблема с usbpcap

Для большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.


Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания.


В данной статье речь пойдет о программировании и полноценной отладке микроконтроллера STM32F103C8T6 через USB.

Однажды, от коллег поступило предложение о участии в IoT проекте. Система предусматривала однопоточный запуск скриптов. Отладка производилась с помощью логов. И тут мне в голову пришла мысль о полноценной удаленной отладке проектов под микроконтроллеры.

Для начала, нужно было опробовать все на прототипе. В качестве отладочной платы была выбрана почти всем знакомая BluePill на микроконтроллере STM32F103. Поскольку на данной отладке имеется интерфейс MicroUSB, было принято решение в прототипе использовать именно этот интерфейс. В будущем предполагался переход на UART подключенный к GSM модулю.

Требовалось реализовать загрузчик, имеющий несколько функциональных блоков. Задачу можно разбить на следующие подзадачи:

Реализация драйвера интерфейса USB со стороны микроконтроллера.

Разработка кода обновления прошивки микроконтроллера с помощью GDB.

Разработка GDB сервера.

Вывод отладочных логов.

Обо всем по порядку. Для прототипирования был реализован загрузчик (bootloader).

2. Требовался код работы с флеш памятью. Вы можете подумать что тут все просто. Это так и не так одновременно. Первая проблема, которая возникла,- невозможность стереть флеш память в обработчике прерывания. Дело в том, что архитектура Cortex M предусматривает два режима работы процессора. Thread и Handler. В первом режиме процессор находится после старта, а так же когда нет активных прерываний. В Handler mode исполняются все обработчики исключений и прерываний. К сожалению, стирание flash-памяти на STM32F103C8T6 в Handler режиме приводит к корректному статусу стирания памяти, но сама память не стирается.

Эта проблема решается посредством запуска кода стирания Flash в Thread режиме. Сделать это можно, так, как обычно происходит в операционных системах. Для этого нужно понимать что такое контекст потока. Контекст потока, - это состояние набора регистров процессора, стека, описывающее конкретный момент работы системы. При входе в обработчик прерывания контекст текущего работающего потока сохраняется, а при выходе из обработчика, он восстанавливается и выполнение программы продолжается. При переключении задач в ОС, по определенному алгоритму, в определенный момент, восстанавливается контекст текущей активной задачи. Нам, при выходе из обработчика, нужно лишь восстановить "свой" контекст, для вызова функции стирания Flash памяти.

Другая проблема является более сложной. Заключается она в том, что, при работе с флеш памятью может происходить выполнение обработчика прерывания, той прошивки, которая находится под отладкой. Эта проблема решается несколькими действиями перед стиранием памяти. Первое что требуется сделать,- заблокировать вызов любых обработчиков прерываний, используемых в отлаживаемой прошивке. Или проще говоря тех, которые не используются в Bootloader-e. Но даже в этом случае команда на стирание памяти может поступить в то время, когда один из обработчиков уже выполняется. Для решения этого вопроса я решил воспользоваться пошаговым режимом работы процессора и "по шагам" вывести процессор из всех обработчиков прерываний. После этого флеш-память можно стирать.

3. Требовалось реализовать GDB-сервер. Я воспользовался исходным кодом проекта BlackMagic, для обработки команд приходящих из среды разработки. На самом деле приходящих от приложения arm-none-eabi-gdb. Далее команды транслировались в команды бинарного протокола, который используется в процессе взаимодействия с микроконтроллером. Нижний уровень GDB-сервера выполнен с использованием библиотеки WinUSB.

Но таким образом функцией 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.

Касательно исходных кодов. Это был быстро-прототип. Что то в нем сделано на скорую руку, что то возможно не совсем корректно. Но принцип работы проверен.

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

Продолжаем работать с интерфейсом USB, и сегодня пришло время практики! Как вы помните, теоретические аспекты мы уже рассмотрели (вот), так что сегодня возьмем в руки STM32 и напишем небольшой примерчик 🙂 Сразу скажу, что я решил поэкспериментировать с контроллером STM32F303 и, соответственно, с платой STM32F3Discovery.

На плате уже есть два USB разъема, один под ST-Link и второй для пользовательских задач, то есть как раз то, что нам надо!

С платой разобрались, теперь по поводу софта. STMicroelectronics любезно предоставили библиотеки для работы с USB для различных семейств микроконтроллеров, а кроме того, выпустили кучу примеров под разные отладочные платы. Но плат Discovery в этом списке нет, поэтому не станем вносить свои изменения в уже готовые проекты от ST, а лучше создадим свой новый проект, взяв из примеров и библиотек только то, что нам реально понадобится.

Нужный драйвер без проблем можно скачать на официальном сайте STMicroelectronics. Устанавливается тоже без проблем и в итоге в диспетчере устройств появляется следующее:

USB устройство на STM32.

Давайте сразу же посмотрим на файл main.c:

Видим, что в теле цикла while(1) пусто, соответственно вся работа происходит в прерываниях. В функции main() всего лишь вызываются функции инициализации. Все эти функции реализованы в файле hw_config.c, его мы поправим под себя чуть позже 🙂 Для приема и передачи данных по USB в файле usb_endp.c предусмотрены обработчики соответствующих прерываний:

Этот проект вообще по умолчанию адаптирован для контроллеров STM32F10x, да и файлов очень много лишних, так что давайте-ка создадим свой новый пустой проект и в нем уже будем работать. Напоминаю, что я буду работать с STM32F3Discovery, поэтому проект создаю для контроллера STM32F303VC. Забираем из папки с библиотеками и примерами все файлы, которые нам понадобятся. Вот их полный список:

Файлы библиотеки USB.

Проект создан, файлы все на месте, давайте писать код. И начинаем с функций инициализации, расположенных в файле hw_config.c:

Что за пины мы настраиваем тут? А вот:

Выводы для работы с USB в STM32.

И еще я добавил небольшую функцию в этот же файл. Она просто гасит все светодиоды:

Не забываем в файл hw_config.h дописать прототип для этой функции:

С инициализацией вроде бы все. Открываем файл usb_endp.c. Мы будем только анализировать принятые от хоста данные, поэтому обработчик транзакций IN нам не понадобится:

В обработчике транзакций OUT принимаем данные и в зависимости от того, какой байт принят зажигаем определенное количество светодиодов, которые у нас висят на GPIOE.

Прошиваем микроконтроллер и тестируем! Вот, что получилось:

Программа для работы с USB

Когда вы первый раз заходите с помощью соцсетей, мы получаем публичную информацию из вашей учетной записи, предоставляемой провайдером услуги соцсети в рамках ваших настроек конфиденциальности. Мы также автоматически получаем ваш e-mail адрес для создания вашей учетной записи на нашем веб сайте. Когда она будет создана, вы будете авторизованы под этой учетной записью. Когда вы первый раз заходите с помощью соцсетей, мы получаем публичную информацию из вашей учетной записи, предоставляемой провайдером услуги соцсети в рамках ваших настроек конфиденциальности. Мы также автоматически получаем ваш e-mail адрес для создания вашей учетной записи на нашем веб сайте. Когда она будет создана, вы будете авторизованы под этой учетной записью.

А не могли бы Вы дать подсказку как настроить передачу, с помощью функции USB_SIL_Write(), и может ли плата только передавать хосту, не принимая ничего.

В принципе при передаче все точно также, callback само собой для транзакций IN используется

Спасибо за описание, но проект по ссылке не полный нет файла самого проекта и он не собирается сразу.

Сорри. Всё нормально. У меня архив чем-то порезало

Доброе время суток! 🙂
очень хорошая статья! большое спасибо! вот начинаю пробовать работать с ЮСБ, очень интересно! вот только вопросы возникают! можно ли на 10х процессорах сделать преобразователь например в 2 кома, или сделать хид клавиатуры и мыши? или хотябы на 207?! по одному устройству примеры работоспособны. заранее спасибо!

HID можно я думаю без проблем сделать)

Пиши, если будут какие-нибудь трудности )

Видимо да, проблема в контроллере

uint8_t tBuff[4];
uint32_t *tADC;

tADC = (uint32_t) tBuff;
*tADC = ADC->DATA;

Здравствуйте.
Подскажите пожалуйста, программировал через Keil было все в норме потом вдруг стало. Error
No target connected.
Flash Download failed-Target DLL has beeh cancelled. На втором компе уже такое произошло. Дрова переустанавливал не помогло. Через STM32 ST-LINK Utility зашивает без проблем.

Может настройки в Кейле слетели?

Нет все настройки на месте. Сам не пойму почему так.

Здравствуйте. А может уважаемый автор выложить проект под STM32F10x, который, как я понял, он брал за основу, а то чего-то проект собрать не получается.

Да, и где в проекте вышеизложенная функция:
void EP3_OUT_Callback(void). Она в main.c отсутствует.

И еще, можно по подробнее о функциях USB_SIL_Write и USB_SIL_Read, можно ли просто бит считать без всяких буферов?

этот вопрос отпал)

Только пример от ST глюченый, мк без соединения с компом зависает намертво даже отладчик от него отваливается.

Вроде бы, STM32 с нуля, а как его питать, шить и отлаживать статей нет. Все на готовых платах.

Тогда это не STM32 с нуля, а программирование STM32 для начинающих.
С нуля, это как у буржуев, когда закрыта пропасть между МК и готовой платой, а в этой пропасти односторонняя макетка утюгом, прошивка всеми способами, моргание лампочкой, отладка и прочие радости.

Скажите пож что делать Я запрограммировал STM32SISCOVERY проектом который вы выложили, но после этого не могу больше запрограммировать чип Программатор выдает : Internal command error Похоже , что где-то установилась защита по записи в чип. Спасибо

Спасибо за статью. Наконец-то удалось запустить usb на stm32f3discovery. Возникла такая проблема. Если я использую библиотеки из Вашего проекта в своем проекте, то все работает нормально. Если же я беру исходные ST USB Library то проект не собирается, хотя список файлов, прикрепленных к проекту, идентичен. Нужно ли вносить какие-либо изменения в библиотеки, за исключением описанных Вами в статье? Хотел бы разобраться в этом вопросе, т к собираюсь использовать usb библиотеку для stm32f103.

Установил все драйвера с официального сайта. USB ST-LINK работает исправно, а вот при подключении разъема USB USER в диспетчере устройств плата отображается как Unknown Device. Подскажите пожалуйста, в чем может быть проблема ?

А драйвер virtual com-port поставил?

Добрый день, что означают эти строчки ?
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
Где можно почитать это?

В даташите описаны режимы, в которых может работать вывод микроконтроллера. В этих строках вывод просто настраивается.

Могли бы Вы указать страницу даташита

Ставил, даже несколько разных скачивал

Тогда еще вариант ) Может система 64 битная? Были случаи (и не единичные), когда на х64 отказывалось напрочь работать.

Хорошо, что заработало )

Здравствуйте! Пытаюсь собрать проект в Keil, но выходят ошибки. Не подскажите в чём проблема.

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

Отладочную плату ипользуем ту же: STM32F4-DISCOVERY.

Проект создаём из проекта I2CLCD80. Назовем его USB_OTG_CDC. Запустим проект в Cube, включим USB_OTG_FS в режим Device_Only

image00

В USB_DEVICE в разделе Class For FS IP выберем пункт Communication Device Class (Virtual Port Com).

image02

Лапки портов PD4-PD7, PB8, PB9 отключим, это пережиток прошлых занятий

image01

В Clock Configuration выберем следующие делители (нажмите на картинку для увеличения изображения)

image04_0500

В Configuration ничего не трогаем, т.к. прерывания там выставились сами.

Сгенерируем и запустим проект, подключим lcd.c и настроим программатор на автоперезагрузку.

У нас скорей всего устройство установится с ошибкой (код 10)

image03

Есть несколько типов решений, мне понравился именно этот, т.к. более простой: в файле usbd_cdc.h заменим размер пакета, вместо 512 напишем 256 в данной строке:

Соберём, прошьём и увидим, что ошибка исчезла.

Начнём писать код.

Сначала попытаемся передать данные на ПК.

Для этого мы сначала откроем файл usbd_cdc_if.c и исправим там в 2х строчках 4 на 64

В файле main.c закомментируем весь пользовательский код кроме инициализации и очистки дисплея

/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

Также в main.c подключим файл usbd_cdc_if.h для видимости функций приема и передачи

/* USER CODE BEGIN Includes */

Немного изменим в главной функции строковую переменную, убавив в ней размер и добавив префикс tx

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

В файле usbd_cdc_if.c добавим прототип функции передачи, скопировав объявление из реализации данной функции в том же файле

/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);

/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */

В main() внесём данные в строку

/* USER CODE END 2 */

CDC_Transmit_FS((unsigned char*)str_tx, strlen(str_tx));

/* USER CODE END WHILE */

Соберём код, прошьём контроллер и посмотрим результат в терминальной программе.

Вроде передать нам что-то удалось. Теперь попробуем что-нибудь принять. Здесь чуть посложнее, т.к. для этого используется уже обработчик прерывания, коим является в файле usbd_cdc_if.c функция CDC_Receive_FS.

Добавим ещё одну строковую глобальную переменную в main()

/* USER CODE BEGIN PV */

/* USER CODE END PV */

Объявим её также и в файле usbd_cdc_if.c

/* USER CODE BEGIN PRIVATE_VARIABLES */

extern char str_rx[21];

/* USER CODE END PRIVATE_VARIABLES */

В функцию CDC_Receive_FS в этом же файле добавим некоторый код и кое-что закомментируем

static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)

/* USER CODE BEGIN 6 */

Добавим переменную в main()

/* USER CODE BEGIN 1 */

Занесенные в наш буфер данные попробуем вывести на дисплей, для этого в бесконечном цикле в функции main() добавим определённый код

CDC_Transmit_FS((unsigned char*)str_tx, strlen(str_tx));

Соберём проект. Прошьём код и посмотрим результат, вводя в терминальной программе и отправляя в порт USB какие-нибудь строки.

STM32 HAL. USB. Virtual Com Port

22 комментария на “ STM Урок 33. HAL. USB. Virtual Com Port ”

Просто измените размер кучи (Minimum Heap Size) в настройка CubeMX. Вместо значения 0x200 задайте 0x400.

И комп увидит устройство без ошибок.

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

Пардон, очепятка вышла. Не компилятору, а функции malloc.

Спасибо, так действительно проще.

Спасибо.Я сделал так.В хидер usbd_cdc_if.h добавил две строчки
extern uint8_t UserRxBufferFS[1000];
uint8_t receiveBufLen;
В метод CDC_Receive_FS добавил перед return receiveBufLen = *Len;
И в main ловил данные просто одним условием
if(receiveBufLen > 0)// если получены данные от ПК
HAL_Delay(250);
CDC_Transmit_FS((uint8_t*) UserRxBufferFS,receiveBufLen);
// эхо для наглядности
receiveBufLen = 0;// сброс получения
>
Всё просто,а UserRxBufferFS чистить не нужно от мусора,он сам чистится.

может в usbd_cdc_if.c ?

Ох, видимо сперва надо читать коментарии, прочитал тот что выше.

Скорей всего придется делать конкатенацию передаваемых строк с помощью strcat. Была аналогичная проблема при использовании CDC. Автор применял этот метод в одном из уроков.

Здравствуйте
А если я хочу передавать данные с микроконтроллера на компьютер?

Константин:
А мы их туда и передали.

Установил различные драйвера VCP от STM, но при этом плата не определяется при подключении её к компьютеру. только виден STLink Virtual COM Port. Кто уже сталкивался с такой проблемой.

Оказалась, что проблема с дровами. Надо их полностью сносить и устанавливать заново.

You can use(for example):

where ADC_Data is your ADC value.

могу скачать драйвера для виртуального ком порта. У меня STM32F415RG, может есть у кого?

Читайте также: