Stm32 отладка по usb
Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры 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.
Касательно исходных кодов. Это был быстро-прототип. Что то в нем сделано на скорую руку, что то возможно не совсем корректно. Но принцип работы проверен.
Продолжаем работать с интерфейсом USB, и сегодня пришло время практики! Как вы помните, теоретические аспекты мы уже рассмотрели (вот), так что сегодня возьмем в руки STM32 и напишем небольшой примерчик 🙂 Сразу скажу, что я решил поэкспериментировать с контроллером STM32F303 и, соответственно, с платой STM32F3Discovery.
На плате уже есть два USB разъема, один под ST-Link и второй для пользовательских задач, то есть как раз то, что нам надо!
С платой разобрались, теперь по поводу софта. STMicroelectronics любезно предоставили библиотеки для работы с USB для различных семейств микроконтроллеров, а кроме того, выпустили кучу примеров под разные отладочные платы. Но плат Discovery в этом списке нет, поэтому не станем вносить свои изменения в уже готовые проекты от ST, а лучше создадим свой новый проект, взяв из примеров и библиотек только то, что нам реально понадобится.
Нужный драйвер без проблем можно скачать на официальном сайте STMicroelectronics. Устанавливается тоже без проблем и в итоге в диспетчере устройств появляется следующее:
Давайте сразу же посмотрим на файл main.c:
Видим, что в теле цикла while(1) пусто, соответственно вся работа происходит в прерываниях. В функции main() всего лишь вызываются функции инициализации. Все эти функции реализованы в файле hw_config.c, его мы поправим под себя чуть позже 🙂 Для приема и передачи данных по USB в файле usb_endp.c предусмотрены обработчики соответствующих прерываний:
Этот проект вообще по умолчанию адаптирован для контроллеров STM32F10x, да и файлов очень много лишних, так что давайте-ка создадим свой новый пустой проект и в нем уже будем работать. Напоминаю, что я буду работать с STM32F3Discovery, поэтому проект создаю для контроллера STM32F303VC. Забираем из папки с библиотеками и примерами все файлы, которые нам понадобятся. Вот их полный список:
Проект создан, файлы все на месте, давайте писать код. И начинаем с функций инициализации, расположенных в файле hw_config.c:
Что за пины мы настраиваем тут? А вот:
И еще я добавил небольшую функцию в этот же файл. Она просто гасит все светодиоды:
Не забываем в файл hw_config.h дописать прототип для этой функции:
С инициализацией вроде бы все. Открываем файл usb_endp.c. Мы будем только анализировать принятые от хоста данные, поэтому обработчик транзакций IN нам не понадобится:
В обработчике транзакций OUT принимаем данные и в зависимости от того, какой байт принят зажигаем определенное количество светодиодов, которые у нас висят на GPIOE.
Прошиваем микроконтроллер и тестируем! Вот, что получилось:
А не могли бы Вы дать подсказку как настроить передачу, с помощью функции 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
В USB_DEVICE в разделе Class For FS IP выберем пункт Communication Device Class (Virtual Port Com).
Лапки портов PD4-PD7, PB8, PB9 отключим, это пережиток прошлых занятий
В Clock Configuration выберем следующие делители (нажмите на картинку для увеличения изображения)
В Configuration ничего не трогаем, т.к. прерывания там выставились сами.
Сгенерируем и запустим проект, подключим lcd.c и настроим программатор на автоперезагрузку.
У нас скорей всего устройство установится с ошибкой (код 10)
Есть несколько типов решений, мне понравился именно этот, т.к. более простой: в файле 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 какие-нибудь строки.
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, может есть у кого?
Читайте также: