Проект виртуального сом порта для отладочной платы stm32h107

Проект виртуального СОМ порта для отладочной платы STM32H107

Для изучения работы USB в микроконтроллерах STM32F105, STM32F107 существует определенное количество отладочных плат. Большинство из них имеет высокую стоимость, но, при этом, существуют демо-проекты, написанные и отлаженные производителем STMicroelectronics, которые без дополнительных усилий можно сразу загрузить в эти платы и все будет работать.

Существуют и дешевые отладочные платы, например STM32-H107 фирмы Olimex. Но проблема в том, что для дешевых плат нельзя найти готовые для использования демо-проекты.

Эта статья представляет демо-проект виртуального COM порта, который содержится в библиотеке от STMicroelectronics stm32_f105-07_f2_f4_usb-host-device_lib STM32_USB-Host-Device_Lib_V2.1.0ProjectUSB_Device_ExamplesVCPTrueSTUDIOSTM3210C-EVAL_USBD-FS , адаптированный для работы с отладочной платой STM32-H107 и результаты тестов скорости передачи.

Скачать его можно по ссылке ниже

Распакуйте архив проекта. Запустите Atollic TrueStudio.

Выберите File->Import->General->Existing Projects into workSpace->Next.

Выберите папку проекта VCP.

Установите галку Copy projects into workspace.

Скомпилируйте проект и он готов к загрузке в плату STM32-H107.

При создании проекта установлена конфигурация JTAG порта процессора как ST-Link.

Я использовал ST-Link, имеющийся на отладочной плате STM32VLDISCOVERY. См. рисунок ниже.

Схема соединений следующая:

Предварительно, на персональном компьютере (PC) надо установить драйвер от STMicroelectronics VCP_V1.3.1_Setup.exe.

Загрузите исполняемый код в STM32-H107 и запустите его выполнение.

Через небольшое время, необходимое для инициализации виртуального COM порта, на PC в диспетчере оборудования, в разделе порты COM и LPT появится устройство виртуальный COM порт, как показано на рисунке ниже.

Номер порта назначается первый свободный в системе.

Если этот номер не устраивает, его можно изменить средствами операционной системы ОС.

В этой статье не рассматривается использование виртуального COM порта по его прямому назначению, когда данные, принятые от РС через USB, отправляются на передачу через USARTx (UARTx) микропроцессоров STM32F105 или STM32F107, а данные, принятые USARTx(UARTx), отправляются на передачу через USB микропроцессоров на РС. Это требуется только в случае построения USB-serial преобразователей.

Гораздо более интересное использование этого демо-проекта для обмена по USB в режиме CDC (common device class).

В классическом варианте использования CDC должна быть библиотека функций для использования в программе микроконтроллера STM32F10x (это

библиотека STM32_USB-Host-Device_Lib_V2.1.0 и она свободно распространяется на сайте STMicroelectronics).

Со стороны РС необходим драйвер CDC (можно свободно скачать с сайта STMicroelectronics) и библиотеки функций для работы с USB при написании программы для РС. Последние STMicroelectronics не предоставляет, но можно купить библиотеки, созданные третьей стороной, причем, не дешево.

Обойти вопрос покупки библиотек можно, используя со стороны микроконтроллера режим передачи CDC, а со стороны РС только драйвер виртуального COM порта. Все необходимые функции для работы с виртуальным COM портом такие же, как для обычных СОМ портов и содержатся в WinSDK. Функции объявлены в файле winbase.h, а содержатся в библиотеке kernel32.lib.

Эти функции следующие:

При помощи этой функции открывается СОМ порт.

Ниже дается пример вызова функции:

При помощи этой функции закрывается СОМ порт.

BOOL CloseHandle(HANDLE hObject); // handle to object to close

Функция для передачи данных от PC на COM порт:

Данные от СОМ порта принимаются в отдельном потоке при помощи:

Для того, чтобы на стороне микроконтроллера использовать режим CDC, в библиотечный файл usbd_cdc_vcp.c добавлена функция

uint16_t CDC_DataTx (uint8_t* Buf, uint32_t Len); , которая является аналогом имеющейся там функции uint16_t VCP_DataTx (uint8_t* Buf, uint32_t Len);

, с разницей в источнике данных для передачи. Для первой функции это uint8_t* Buf — массив данных, который требуется передать, второй функции данные поставляет USARTx(UARTx) микроконтроллера.

Таким образом, функция CDC_DataTx является точкой входа для передачи данных по USB от микроконтроллера на РС.

На стороне PC использовалась самодельная программа для приема/передачи байтов по порту RS232, окно которой показано ниже:


Рис.1

Она позволяет:
— оперативно открывать/закрывать COM порт с любым номером
— устанавливать требуемую скорость работы порта
— имеет поток, который принимает байты от порта и выводит их на левый экран.

Для случая проверки максимальной скорости передачи, вывод принятых байтов на экран заблокирован, а добавлен счетчик, который выводит на форму количество принятых байтов в секунду(обведено красным).

— передавать из правого окна одиночные байты, массивы байтов и строки.

Для проверки максимальной скорости передачи от микроконтроллера к PC, на плате STM32-H107 надо нажать кнопку WKUP. Загорается зеленый светодиод, микроконтроллер в цикле main следит за переменной USB_Tx_State. Когда она становится равной 0(т.е.ресурсы USB на передачу свободны), загружает эти ресурсы очередным массивом данных.

Из рис.1 видно, что скорость передачи составила 424.5 килобайт/сек. Такая скорость получена при установке в 0 параметра

#define CDC_IN_FRAME_INTERVAL 0 /* Number of frames between IN transfers */ в файле usbd_conf.h. Если параметр увеличивать, скорость будет уменьшаться.

В этой проверке установленная скорость виртуального порта не играет никакой роли.

Прием байтов от РС

Из программы на РС передается байт 123 (см.рис.2)


Рис.2

На рис.3 видно, в каком месте проекта появляется принятый от РС пакет.


Рис.3

Принятый от РС пакет содержит переданный байт 123(фигурная скобка), USB_Rx_Cnt=1, т.е длина пакета равна 1.

Для проверки максимальной скорости передачи байтов от PC на микроконтроллер программа на РС передавала пакет из 64 байтов и ждала подтверждения от микроконтроллера о приеме пакета. Потом цикл повторялся. Функция usbd_cdc_DataOut в программе микроконтроллера была модифицирована следующим образом:

Т.е функция APP_FOPS.pIf_DataRx(USB_Rx_Buffer, USB_Rx_Cnt); закомментирована, чтобы она не передавала принятые байты в USART микроконтроллера. Поток, работающий на прием на РС, подсчитывал, при этом, 1-байтовые ответы от микроконтроллера.

На рис.4 показан результат теста:


Рис.4

Скорость передачи составила 8926 х 64 = 571264 байт/с.

Следует отметить, что и в этом тесте установленная скорость виртуального порта не имеет никакого значения. Можно поставить малую скорость, например 115200 бит/с, а результат остается тем же.

Можно подвести итог:

  1. Данные по скорости обмена по USB получены при минимальной их обработке со стороны микроконтроллера. Если обработка данных будет более сложная, скорость обмена уменьшится.
  2. Скорость обмена ограничивается быстродействием микроконтроллера, работающего на частоте 72МГц.
  3. Скорость обмена достаточно большая и позволяет использовать описанные в статье методы приема и передачи данных по USB микроконтроллеров STM32F105, STM32F107 при написании программ для широкого круга приложений.

STM Урок 33. HAL. USB. Virtual Com Port

Урок 33

HAL. USB. Virtual Com Port

Отладочную плату ипользуем ту же: 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 и настроим программатор на автоперезагрузку.

Соберем проект. Прошьём контроллер. У нас появится неизвестное устройство, скачаем драйвер на наше виртуальное устройство usb. Для этого зайдем на сайт st.com, в строке поиска там вводим virtual com port, скачиваем и устанавливаем драйвер. Затем желательно зайти в папку с установленным драйвером, выбрать папку, соответствующую разрядности нашей операционной системы, и запускаем также установку и оттуда.

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

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

#define CDC_DATA_HS_MAX_PACKET_SIZE 256 /* Endpoint IN & OUT Packet size */

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

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

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

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

/* It’s up to user to redefine and/or remove those define */

#define APP_RX_DATA_SIZE 64

#define APP_TX_DATA_SIZE 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 */

В бесконечном цикле попробуем эти данные отправить в порт USB, используя функцию, прототип которой мы добавили

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 какие-нибудь строки.

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

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

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

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

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

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

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

Спасибо огромное за ваши материалы по STM32 , подключил TFT 320×240 – все отлично работает . Вернулся к материалу для подключения флешки . Все отладочные средства у меня находятся на VirtualBox ( W7 ) . Скачал по вашей инструкции и поставил драйвер Virtual com port . Он поставился , но в диспетчере задач ничего не появилось ни в разделе com портов , ни в других . Может вы сталкивались с подобной проблемой ? Если нет – в любом случае еще раз спасибо за проделанную работу .

Сначала не смог реализовать данный пример на SystemWorkbench в части приёма данных и передачи их из функции приёма в main посредством массива str_rx с модификатором extern – компилятор ругается на использование неопределённых переменных, а если задать ему какие-нибудь значения, то только эти заданные значения и будут передаваться. Вышел из положения объявив массив обмена str_rx в заголовочном файле usbd_cdc_if.h

Читайте также  Как рассчитать мощность инфракрасного обогревателя на помещение?

Спасибо.Я сделал так.В хидер 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 чистить не нужно от мусора,он сам чистится.

Здравствуйте! Спасибо огромное за ваши уроки, тут пожалуй лучший ресурс с уроками по стм32!
Хочу спросить, а как использовать CDC_Receive_FS в main.c? Я проделал в usbd_cdc_if.c «эхо», но мне нужно принимать из него и гнать дальше. Наверное вопрос больше в целом по си чем по контроллеру, а то иначе мне получается надо много всего переносить в usbd_cdc_if.c.

Думаю, что следует добавить в main.c функцию, а в файле usbd_cdc_if.c – на неё прототип и вызвать её в CDC_Receive_FS, И весь свой пользовательский код затем писать в файле main.c.
Это именно СИ. Так что обязательно подтяните свои знания по языку.

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

При переходе на USB cтолкнулся с такой проблемой. Скажем, конструкция, приведённая в примере, а именно
sprintf(str_tx,»USB Transmitrn»);
CDC_Transmit_FS((unsigned char*)str_tx, strlen(str_tx));
работает без проблем. Но, если я делаю так
sprintf(str_tx,»USB Transmit»);
CDC_Transmit_FS((unsigned char*)str_tx, strlen(str_tx));
CDC_Transmit_FS((unsigned char*)»rn», 2);
то CDC_Transmit_FS((unsigned char*)»rn», 2); не срабатывает (не успевает) и данные летят без переноса строки. Если ставить задержку, то работает как надо. По неопытности, может, это я и принял бы как должное, если бы перед этим не работал бы с UART где такая же конструкция работает без проблем. Для работы с UART уже написана довольно хорошая часть программы и менять её структуру очень не хочется, тем более, что данные передаются не в текстовом формате а в посылке имеется несколько меток. Что можно сделать, чтобы посылки могли идти подряд без задержки?

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

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

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

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

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

Hello, I’m new to STM32. How do I send int32_t value via usb CDC from ADC input ? or How to convert int32_t to char?

You can use(for example):

sprintf(str_tx,»ADC:%d rn»,ADC_Data);
CDC_Transmit_FS((unsigned char*)str_tx, strlen(str_tx));

where ADC_Data is your ADC value.

Спасибо за примеры. С USB в базовой библиотеке что-то не так. При первом подключении ком порт работает, но при передергивании USB – становится неизвестным устройством, иногда не сразу а через 5-10 секунд после повторного подключения…
Сейчас копаю в сторону функций вызываемых на отключение и подключение USB. Первое что кажется подозрительным, то что на подключение вызывается инициализация а на отключение USBD_LL_Suspend, затем на подключение снова инициализация, хотя есть USBD_LL_Resume. Пока дальнейших идей нет. Может что-то подскажете?

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

STM32 — организация виртуального COM-порта

Раз у камня есть аппаратный USB, то грех им не пользоваться. Один из способов плюнуть байтом в компьютер и чтобы он при этом не очень обиделся — это организация виртуального COM-порта.
Все в железе пробовалось на камне STM32F103ZE, на аналогах тоже должно взлететь.

Забиваем.

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

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

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

Также нам пригодятся следующие стандарты
USB 2.0 Specification
USB CDC

В составе библиотеки есть пример как раз организации виртуального COM-порта. Правда там он сделан как мост USART-USB, но мы же не боимся трудностей?

Цель этой статьи — разобрать приложение, делающее следующее:

Поджигаем

Собираем проект из нужных нам библиотек: CMSIS, SPD, USB. Дерево проекта прилагаю.

Как обычно, самое интересное в папочке /src. Вот её-то и будем разбирать.

Неторопливо курим

Начнем с раскуривания библиотеки от STM

Эта библиотека предназначена и для connectivity-line устройств (это STM32F105/107), у них не просто USB FS, а USB OTG. Поскольку камней с OTG у меня пока нет, сконцентрируемся на простом USB FS.
В примере, который мы взяли за основу, есть куча дефайнов, как раз на случай отличить connectivity-line от других демоплат. Я эти дефайны повырезал, чтобы глаза не мозолили.

Cо всей картинки нас интересует High Layer — синенькие квадратики, которые и составляют, собственно, пользовательскую часть библиотеки. Их мы меняем под свои нужды, все остальное остается неизменным. Если заглянуть в папочку /src, то видно, что все эти файлики там и собраны.

Первая затяжка — usb_conf.h

Самые общие настройки библиотеки.
Указано сколько у нас будет endpoints (а нам их надо 4 штуки — нулевой для начального конфигурирования устройства, один для команд, один для приема и один для передачи).
Также расписаны, какие нам будут нужны коллбэки. Все взаимодействие библиотеки и пользовательской программы построено на обратных вызовах: в случае возникновения какого-либо события вызывается функция с заданным названием. А уже содержание этой функции — проблема разработчика.
Нам будет нужен SOF_CALLBACK – сигнал начала фрейма (Start Of Frame). По нему будем выплевывать то, что накопилось в буфере передачи.
Также резервируем еще два коллбэка — на прием и передачу. По приему символа будем мигать светодиодами, чтобы уж как-нибудь задействовать канал приема.

Упс, кончился файл. Короткая получилась затяжка.

Файл берем из примера, ничего не меняем.

Вторая затяжка — usb_desc.h / usb_desc.c

В этих файлах находятся дескрипторы устройства и эндпоинтов.
Информация по стандартным дескрипторам есть в стандарте USB, в разделе 9.6 Standard USB Descriptor Definitions
Специфические дескрипторы описаны в USB CDC, раздел 5 (так и называется Descriptors),

Эти все тонны текста в стандартах для того, чтобы USB стала действительно Universal. Поэтому тщательно выведена классификация устройств и интерфейсов — чтобы глянув на дескриптор хост сразу понял, что с этим делать.

Особо подробно разбирать смысла не вижу — это не характерно для STM32, это общая боль разработчиков USB устройств.

Файл берем из примеров, ничего не меняем.

Продолжение дескрипторов — usb_prop.h / usb_prop.c

В этих файлах описана таблица реакции на запросы. Все запросы к устройству пронумерованы и стандартизованы. В этих файлах определяется массивы функций Device_Property, User_Standard_Requests и просто массивы String_Descriptor, например. Это все используется ядром. Когда в устройство приходит определенный запрос, например «Выдай мне дескриптор устройства», то ядро посмотрит в таблице, какая функция обрабатывает этот запрос, вызовет эту функцию, а оно уже отработает:

Опять же, берем файл из примеров.

Прерываемся — usb_istr.h / usb_istr.c

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

Прерывание будет настраиваться в файле hw_config.c, обработчик выглядит очень просто:

void USB_LP_CAN1_RX0_IRQHandler(void)
<
USB_Istr();
>

его можно разместить в файлах stm32f10x_it.*, а в данном проекте, чтобы не растекаться мысью по древу, я его включил в файл main.c

И тут особо ничего не меняем, все как в примере.

Питание — usb_pwr.h / usb_pwr.c

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

Ничего не меняем, файл из примеров.

Коллбэки — usb_endp.c

Этот файл я переписал, поскольку у нас не будет работы с USART, как это было в примере. Приведу код с комментариями:

Работа с железом — hw_config.h / hw_config.c

В этом файле собраны функции инициализации периферии, прерываний, функции управления светодиодами и работы с буфером отправки.

USB-порт и подтяжки на моей плате сделаны по такой схеме:

И USB_EN заведен на пин PF11. Значит надо не забыть его проинициализировать и дернуть вовремя.

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

По приему символов ‘A’, ‘B’, ‘C’, ‘D’ – зажигаются соответствующие светодиоды, ‘a’, ‘b’, ‘c’, ‘d’ – гасятся.
‘1’ и ‘0’ — соответственно зажигают и гасят все светодиоды.

Ну и все, что будет передано функции USB_Send_Data() попадает в буфер, а затем и через USB – в комп.

Уфф. С USB вроде закончили.

Теперь в головной программе можно просто вызывать USB_Send_Data() и посимвольно передавать данные в комп.

Если мы на этом остановимся, то размер кода будет порядка 11 кБ:

Более двух килобайт bss – это буферы приема и передачи. Понятное дело, их можно уменьшить.

Выдыхаем — printf()

Но мы же хотим, чтобы вывод функции printf() перенаправлялся в наш свежесозданный COM-порт. Подключаем и офигеваем от количества ошибок линковки:

А тут все просто — стандартная библиотека ввода-вывода подразумевает работу с файлами, но в больших системах есть ОС, которая собственно и занимается организацией файлов для программ пользователя. А поскольку у нас нет никакой ОС, то библиотека вполне справедливо недоумевает «А что же мне делать-то?»

Вот, чтобы это обойти, в проект включается файл newlib_stubs.c
В нем находятся функции-заглушки ко всем вышеперечисленным ошибкам. Но и кроме этого, есть одна функция, которую нам и надо переписать, чтобы весь вывод шел в USB:

Видим, что как раз и вызывается наша функция USB_Send_Data()

Все, компилим, собираем, запускаем.

Архив с проектом прилагаю — это на случай, если кто не заметит маленькие буковки внизу 🙂

Составное устройство USB на STM32. Часть 3: Звуковое устройство отдельно, виртуальный СОМ-порт отдельно

В третьей части публикации о составном устройстве USB я расскажу о том, как переделать сгенерированный в STM32CubeMX USB Audio Speaker, описанный во второй части публикации, в дуплексное звуковое устройство.

Читайте также  Однопороговый прибор пожарной сигнализации

Затем мы создадим в STM32CubeMX драйвер виртуального COM-порта.

Зачем мы всё это делаем, подробно описано в первой части публикации.

Ссылки на первую и вторую части публикации:

  • Составное устройство USB на STM32. Часть 1: Предпосылки
  • Составное устройство USB на STM32. Часть 2: USB Audio Speaker

Исходные коды публикуемой реализации составного устройства USB, состоящего из виртуального COM-порта и дуплексной звуковой карты находятся здесь.

Доработка дескриптора

Дорабатываемый дескриптор размещается STM32CubeMX в файле usbd_audio.c. В работе использовались документы [2] и [3].

Обилие в сгенерированном дескрипторе макросов, заданных в файле usbd_audio.h, на мой взгляд, затрудняет работу с ним. Поэтому я заменил большую часть макросов на шестнадцатеричные значения, чтобы части дескриптора выглядели подобно их описанию в таблицах из [2] и [3], а также как в листинге, сгенерированном утилитой Thesycon USB Descriptor Dumper.

Однако, для удобства конфигурирования отдельные макросы пришлось оставить, а также добавить несколько новых:

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

Доработанный дескриптор описывает дуплексное звуковое устройство USB со структурой, приведённой на рисунке ниже:

Устройства ID2 и ID5 (Feature Unit) оставлены в структуре звукового устройства «на вырост». Управление ими осуществляется через Class-Specific Requests. При обработке этих запросов драйвер звукового устройства должен передавать оконечному устройству набор команд для управления уровнями громкости, настройками эквалайзера, звукового процессора и т.п. В доработанном дескрипторе набор этот состоит пока из одной только команды – MUTE.

Доработка драйвера звукового устройства USB

При доработке драйвера в структуру звукового устройства был добавлен циклический буфер тракта записи, а состав команд, передаваемых пользовательскому интерфейсу, расширен командами AUDIO_CMD_STOP и AUDIO_CMD_RECORD.

Тракт записи дуплексного звукового устройства USB начинает работу при установке интерфейса AUDIO_IN_IF в состояние Alternate Setting 1, после чего драйвер ожидает событие SOF, во время обработки которого формирует команду AUDIO_CMD_RECORD, по которой заполняет половину буфера тракта записи пакетами из буфера DSP. Далее эти пакеты передаются из циклического буфера тракта записи вовне через конечную точку AUDIO_IN_EP.

После того, как тракт записи был запущен, команда AUDIO_CMD_RECORD формируется драйвером после окончания передачи каждой половины циклического буфера тракта записи.

Особенностью использования конечной точки AUDIO_IN_EP драйвером звукового устройства является необходимость запуска USBD_LL_FlushEP(pdev, AUDIO_IN_EP) для очистки буфера конечной точки после окончания передачи каждого пакета.

Тракт воспроизведения дуплексного звукового устройства USB включается при установке интерфейса AUDIO_OUT_IF в состояние Alternate Setting 1. Команда AUDIO_CMD_PLAY формируется драйвером звукового устройства по событию заполнения каждой половины циклического буфера тракта воспроизведения, после чего эти пакеты передаются в буфер DSP.

Команда AUDIO_CMD_STOP формируется драйвером при установке интерфейса AUDIO_OUT_IF в состояние Alternate Setting 0, после чего DSP включает в тракте воспроизведения режим тишины.

Синхронизация потоков данных доработанным драйвером не производится, поэтому функция USBD_AUDIO_Sync не производит никаких действий и оставлена в составе драйвера только для совместимости.

Проверка работоспособности драйвера дуплексного звукового устройства USB

При генерации драйвера виртуального COM-порта STM32CubeMX удалит из проекта файлы драйвера звукового устройства. Поэтому переносим usbd_audio.c и usbd_audio_if.c в папку Core/Scr, а usbd_audio.h и usbd_audio_if.h – в Core/Inc.

Пересобираем проект, перепрошиваем устройство. Подключаем устройство к компьютеру, включаем тракт записи через панель управления звуком. Назначаем тракты воспроизведения и записи дуплексного звукового устройства USB устройствами по умолчанию.

Для демонстрации работоспособности драйвера звукового устройства USB данные с выхода тракта воспроизведения поступают на вход тракта записи через шлейф, организованный в буфере DSP (см. файл dsp_if.c). Программная реализация шлейфа выбрана, чтобы не подключать к отладочной плате никаких дополнительных устройств и не синхронизировать никакие потоки.

Включаем в панели управления звуком для тракта записи нашего устройства прослушивание через звуковую карту компьютера. Убеждаемся в работоспособности драйвера дуплексного звукового устройства USB.

Создаём Communication Device Class

Приступаем к созданию виртуального COM-порта, для чего переходим в раздел «Middleware» и выбираем IP «Commucation Device Class». Задаём максимальное количество интерфейсов равное пяти. Размер буферов задаём равным 64 Bytes.

Интерфейсов пять, т.к. в составе дуплексного звукового устройства их три, а в составе виртуального COM-порта – два.

Сохраняем проект. Генерируем код. Смотрим, что получилось в результате.

Разбираем работу устройства

Файлы сгенерированного в STM32CubeMX драйвера виртуального COM-порта расположены в папках Middlewares/ST/Class/CDC и USB_DEVICE.

Функции, с помощью которых драйвер виртуального COM-порта взаимодействует со своим оконечным оборудованием, содержатся в файле usbd_cdc_if.c.

Во время инициализации устройства функция CDC_Init_FS задаёт настройки буферов трактов приёма и передачи.

При получении команд управления драйвер виртуального COM-порта запускает функцию CDC_Control_FS. Список команд управления приводится на стр.19 документа:

В описании функции следует обратить внимание на структуру переменных типа USBD_CDC_LineCodingTypeDef, объявленного в usbd_cdc.h. При дальнейшем использовании драйвера мы можем с помощью переменной такого типа жёстко задать параметры COM-порта, которые он передаёт вовне по запросу.

Функция CDC_Receive_FS запускается по событию получения данных по виртуальному COM-порту.

Для передачи данных по виртуальному COM-порту используется функция CDC_Transmit_FS.

Проверка работоспособности драйвера виртуального COM-порта

Виртуальный COM-порт начинает работать прямо «из коробки». Для контроля работоспособности организуем функцию «эха».

Открываем в папке USB_DEVICEApp файл usbd_cdc_if.c и добавляем в функцию CDC_Receive_FS шлейф, как показано ниже:

Собираем проект, прошиваем устройство. Подключаем устройство к компьютеру, обнаруживаем новый COM-порт, при необходимости устанавливаем на компьютер драйверы.

Подключаемся к новому COM-порту любой терминальной программой. Передаём данные, принимаем «эхо». Убеждаемся в работоспособности драйвера виртуального COM-порта.

От автора

В следующей части публикации мы объединим виртуальный COM-порт и дуплексное звуковое устройство в составное устройство USB и разберём несколько не совсем очевидных нюансов этого объединения.

Программирование STM32. Часть 5: Порты ввода-вывода GPIO

В этой части мы разберемся с порами ввода-вывода GPIO микроконтроллера STM32F103C8 и напишем «Hello, World!» с мигающим светодиодом, а так же научимся читать состояние выводов микроконтроллера и использовать встроенный подтягивающий резистор. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1.

Введение

General-purpose input/output (GPIO) — важный компонент любого микроконтроллера, с помощью которого он взаимодействует с окружающим миром. В микроконтроллерах STM32 порты именуются буквами A, B, C и так далее: GPIOA, GPIOB, GPIOC... Каждый GPIO имеет 16 линий ввода/вывода, причем каждая линия может быть настроена независимо от других. Вот варианты настройки:

  • Input floating — вход с отключенными подтягивающими резисторами
  • Input pull-up — вход с подтяжкой к логической единице
  • Input-pull-down — вход с подтяжкой к логическому нулю
  • Analog — аналоговый вход (например, для АЦП)
  • Output open-drain — выход с открытым коллектором (записали 1 — выход в высокоимпедансном состоянии, записали 0 — выход прижат внутренним транзистором к земле)
  • Output push-pull — выход «тяни-толкай» (записали 1 — на выходе лог. 1, записали 0 — на выходе лог. 0)
  • Alternate function push-pull — альтернативная функция в режиме «тяни-толкай»
  • Alternate function open-drain — альтернативная функция в режиме открытого коллектора

Дам несколько пояснений по поводу режимов.

Режим Analog. Внутри микроконтроллера есть аналогово-цифровые преобразователи, которые, как известно, должны иметь аналоговые входы. Так вот, в режиме Analog ножка микроконтроллера подключается к аналоговому входу АЦП внутри микроконтроллера. Кроме того, отключается вся цифровая обвязка этой ножки для уменьшения цифрового шума и энергопотребления.

Alternate function. В этом режиме ножкой микроконтроллера управляет внутренняя цифровая периферия, например, модуль USART.

Регистры GPIO

Давайте рассмотрим регистры портов GPIO.

Port configuration register low (GPIOx_CRL)

Рис. 1. Регистр CRL

Это конфигурационный регистр для выводов порта с номерами от 0 до 7. Каждому выводу предоставлено 4-ре бита конфигурации: 2 бита MODEy и 2 бита CNFy.

MODEy[1:0]: Режим ножки порта, вход или выход. В режиме выхода нужно выбрать максимальную частоту переключения данной ножки, насколько понял это является оптимизацией энергопотребления порта.

  • 00: Вход (значение после сброса)
  • 01: Выход, максимальная частота 10 MHz.
  • 10: Выход, максимальная частота 2 MHz.
  • 11: Выход, максимальная частота 50 MHz.

CNFy[1:0]: Конфигурация режима.

В режиме входа (MODEy[1:0]=00):

  • 00: Analog mode — аналоговый режим (подключен к АЦП или ЦАП-у)
  • 01: Floating input — вход с отключенными подтягивающими резисторами (значение после сброса)
  • 10: Input with pull-up / pull-down — вход с подтяжкой вверх или вниз
  • 11: Reserved — не используется

В режиме выхода (MODEy[1:0]>00):

  • 00: General purpose output push-pull — выход в режиме тяни/толкай
  • 01: General purpose output Open-drain — выход с открытым коллектором
  • 10: Alternate function output Push-pull — выход альтернативной функции режиме тяни/толкай
  • 11: Alternate function output Open-drain — выход альтернативной функции с открытым коллектором

Port configuration register high (GPIOx_CRH)

Рис. 2. Регистр CRH

Это конфигурационный регистр для выводов порта с номерами от 8 до 15. Тут все то же, что и для регистра GPIOx_CRL.

Port input data register (GPIOx_IDR)

Рис. 3. Регистр IDR

IDRy: в этих битах содержится входное значение соответствующего порта ввода-вывода.

Port output data register (GPIOx_ODR)

Рис. 4. Регистр ODR

ODRy: выходные данные порта.

Port bit set/reset register (GPIOx_BSRR)

Рис. 5. Регистр BSRR

С помощью этого регистра можно сбросить или установить любой бит регистра ODR без операций чтение-модификация-запись.

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

BSy: Установить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Устанавливает в единицу соответствующий бит ODRx

Port bit reset register (GPIOx_BRR)

Рис. 6. Регистр BRR

С помощью этого регистра можно сбросить любой бит регистра ODR без операций чтение-модификация-запись.

BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)

  • 0: не оказывает влияние на соответствующий бит ODRx
  • 1: Сбрасывает в ноль соответствующий бит ODRx

Port configuration lock register (GPIOx_LCKR)

Рис. 7. Регистр LCKR

Этот регистр используется для блокировки конфигурационных битов порта после записи корректной последовательности в 16 бит (LCKK) регистра. Значения битов [15:0] используется для блокировки конфигурации GPIO. Во время блокирующей последовательности в LCKK значения LCKR [15: 0] не должны меняться. Когда блокирующая последовательность была записана, конфигурация выбранных портов ввода/вывода может быть изменена только после сброса микроконтроллера. Каждый LCKy бит блокирует возможность изменения четырех битов конфигурации порта (CRL, CRH).

LCKK[16]: Ключ блокировки.

  • 0: Блокировка конфигурации порта не активна.
  • 1: Блокировка конфигурации порта активна. GPIOx_LCKR заблокирован до следующего сброса микроконтроллера.

Для блокировки необходимо выполнить следующую последовательность:

  • Записать 1
  • Записать 0
  • Записать 1
  • Прочитать 0
  • Прочитать 1 (эта операция чтения не является обязательной, а всего лишь подтверждает успешность установки блокировки)

LCKy: Эти биты могут быть прочитаны и записаны, но запись можно произвести только если бит LCKK равен нулю.

  • 0: Конфигурация пина номер y не заблокирована
  • 1: Конфигурация пина номер y заблокирована
Читайте также  Активная и реактивная электроэнергия

Настройка порта GPIO

Итак, с регистрами разобрались, настало время практики. Все примеры в этой статье для микроконтроллера STM32F103C8. В моем распоряжении есть вот такая отладочная плата:

На ней установлен кварцевый резонатор на 8 МГц и светодиод на порту PB12. Вот с помощью этого светодиода мы и устроим Hello, World!

Задача ясна: настраиваем PB12 на выход в режиме push-pull и с помощью регистра ODR дергаем 12-й пин порта GPIOB туда-сюда! Но мы забыли об одной маленько детали: RCC. Дело в том, что по-умолчанию после сброса микроконтроллера все периферийные модули отключены от источника тактового сигнала, в том числе и GPIO. А подать тактирование можно с помощью регистров RCC. В 3-ей части я про это говорил. Для начала нужно определить, к какой шине у нас подключен GPIOB. Открываем даташит на микроконтроллер, ищем вот эту таблицу:

Рис. 8. Таблица шин и периферийных устройств

GPIOB у нас подключен к шине APB2. Идем в Reference manual, открываем раздел про RCC, переходим к пункту 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). С помощью этого регистра можно подать тактовый сигнал на устройства шины APB2:

Рис. 9. Регистр RCC_APB2ENR

В регистре RCC_APB2ENR много флагов для разной периферии, в том числе и для нашего GPIOB, флаг называется IOPBEN. Перед началом инициализации PB12 нам надо установить этот бит в единицу.

Поехали программировать! За основу возьмем проект из 2-й части: https://github.com/DiMoonElec/stm32f103c8_empty_project. Создадим функцию инициализации порта:

Первым делом включаем тактирование порта GPIOB:

Далее настройка порта. Нам нужен пин PB12. Его конфигурационные биты находятся в регистре CRH:

Все Настройка завершена! Вот полный код функции:

Теперь управление. Для этого нам понадобится регистр ODR (рис. 4). Вот функция установки высокого уровня на PB12:

Ни чего сложного, почти как на AVR-ках! Однако, у нас все же более серьезный микроконтроллер, и у него есть некоторые фишки. Выражения вида GPIOB->ODR |= (1 Метки: CMSIS, IAR, STM32, Обучалка. Закладка Постоянная ссылка.

36 комментариев: Программирование STM32. Часть 5: Порты ввода-вывода GPIO

А почему мы не используем регистр BSRR для записи нуля а пользуемся BBR? Из тех же соображений что и ODR. И что будет если в него в двух позициях записать 1 и на установку 1 и на установку 0

Можно и BBR, разницы ни какой нет. Можно использовать и ODR, но в этом случае нужно выполнять операцию типа Чтение-Модификация-Запись, а для BSRR и BBR только запись — это быстрее и нет возможности возникновения нарушения атомарности операции

Что будет, если в BSRR записать единицы на установку и сброс одного и того же пина GPIO я без понятия, надо попробовать)) В документации такого вроде не попадалось

Нашел в даташите: If both BSx and BRx are set, BSx has priority.

Здравствуйте. у меня вопрос — в проекте для задержки между вкл. и выкл. светодиода используется цикл for.а как обстоят дела в стм32 насчет служебных ф-ий типа Delay_ms() как в GCC для 8-ми битных АВРок?

Сам использую компилятор от IAR, насколько знаю, для ARM-ов ни чего подобного нет, хотя для тех же AVR-ок есть функция __delay_cycles(). В реальных проектах если надо генерить сигнал с периодом больше 1ms и с не очень большой точностью поддержания периода, то реализую это на программных таймерах и конечных автоматах, если периоды меньше и/или нужна большая точность — то аппаратные таймеры. Если же нужно выдать короткий управляющий импульс для какой-то микры на плате — то использую задержку на for, что-то типа for(i=0; i Den пишет:

Нашел на просторах инета такой интересный код

Никаких таймеров, все быстро и компактно. Только у меня в include надо core_cm3.h вместо stm32f10x_tim.h. Использую STM32CubeIDE

и функцию delta желательно переписать с учетом переполнения счетчика, чтобы не получить непонятные глюки на граничных значениях

Здравствуйте. еще вопросик — например я затактировался от HSE через PLL x9. в итоге частота 72 Мгц. Далее например подключаю GPIOB : RCC->APB2ENR|=RCC_APB2ENR_IOPBEN; .Но ведь выход порта В я могу настроить только на макс. 50Мгц , Значит ли это что и делитель АРВ2 в этом случае настроить как минимум на HCLK/2 поскольку частота на этой шине 72 Мгц?

То, что настраивается в регистрах GPIO как максимальная скорость работы, как ни странно, не имеет ни какого отношения к тактированию. Этими битами настраивается максимальный потребляемый ток данного пина. Пример: если настроить пин на максимальную частоту 2 МГц, но при этом подать 72 МГц, то сигнал на этом пине будет больше похож на синус, чем на меандр. Если же настроить на 50 МГц, то в этом случае на выходе будет уже что-то более-менее похожее на меандр

Где-то обсуждали (ссылаясь на доки), что частота переключения пинов в STMках отражает на самом деле скорость нарастания/спада фронта, а значение частоты — это максимальная частота, при которой сумма времени нарастания и спада не более двух третей от стабильного уровня импульса. Быстрое переключение выводов может оказывать электромагнитное влияние на работу самого микроконтроллера. Поэтому, если нет необходимости в высоких частотах, предлагается устанавливать опцию более низкой частоты (т.е. более медленные изменения уровня сиглала на ножках) в целях более высокой помехоустойчивости. При этом, если повышенная частота нужна, то такая опция такая есть.

Digitrode

цифровая электроника вычислительная техника встраиваемые системы

  • Вычислительная техника
    • Микроконтроллеры микропроцессоры
    • ПЛИС
    • Мини-ПК
  • Силовая электроника
  • Датчики
  • Интерфейсы
  • Теория
    • Программирование
    • ТАУ и ЦОС
  • Перспективные технологии
    • 3D печать
    • Робототехника
    • Искусственный интеллект
    • Криптовалюты

Чтение RSS

Как запрограммировать STM32 с помощью Arduino IDE

С момента своего появления Arduino IDE демонстрирует желание поддерживать все виды платформ, от клонов Arduino и вариаций разных производителей до плат сторонних производителей, таких как ESP32 и ESP8266. По мере того, как все больше людей знакомятся с Arduino IDE, среда начинает поддерживать больше плат, которые не основаны на чипах ATMEL, и в сегодняшнем уроке мы рассмотрим одну из таких плат. Мы рассмотрим, как программировать плату на основе STM32, а именно STM32F103C8T6, с помощью Arduino IDE.

Плата STM32, которая будет использоваться в этом учебном материале, является не чем иным, как платой разработки на основе микросхемы STM32F103C8T6 на базе микроконтроллера STM32F1, обычно называемой «Blue Pill» в соответствии с синим цветом печатной платы. Blue Pill работает на мощном 32-битном процессоре ARM STM32F103C8T6 с тактовой частотой 72 МГц. Плата работает с логическими уровнями 3,3 В, но ее контакты GPIO были протестированы на устойчивость к 5 В. Хотя она не поставляется с WiFi или Bluetooth, как варианты ESP32 и Arduino, все же она предлагает 20 КБ ОЗУ и 64 КБ флэш-памяти, что делает данное решение достаточным для крупных проектов. Она также имеет 37 выводов GPIO, 10 из которых можно использовать для аналоговых датчиков, поскольку они имеют АЦП, а также другие для SPI, I2C, CAN, UART и DMA. Для платы, которая стоит около 3 долларов это впечатляющие характеристики.

Частота, с которой работает Blue pill, примерно в 4,5 раза выше, чем у Arduino UNO. В качестве примера того, как использовать плату STM32F1, мы подключим ее к TFT-дисплею 1,44 дюймов и запрограммируем ее рассчитать константу «Пи». Мы отметим, сколько времени понадобилось микроконтроллеру, чтобы получить значение, и сравнить его со временем, которое требуется Arduino Uno для выполнения той же задачи.

Схема подключения для этого примера следующая:

Как и в случае с большинством плат, выпущенных не Arduino, необходимо выполнить небольшую настройку, прежде чем плату можно будет использовать с Arduino IDE. Это включает в себя установку файла платы либо через менеджер плат Arduino, либо загрузку из Интернета и копирование файлов в папку оборудования. Маршрут к Board Manager является менее утомительным, и поскольку STM32F1 входит в число перечисленных плат, мы пойдем по этому пути. Начнем с добавления ссылки для платы STM32 в списки предпочтений Arduino. Перейдите в Файл — Настройки, (File — Preferences) затем введите этот URL (http://dan.drown.org/stm32duino/package_STM32duino_index.json) в поле, как указано ниже, и нажмите кнопку ОК.

Теперь зайдите в Инструменты — Плата — Менеджер плат (Tools — Board — Board manager), откроется диалоговое окно с панелью поиска. Найдите STM32F1 и установите соответствующий пакет.

Процедура установки займет несколько секунд. После этого плата должна быть доступна для выбора в списке плат Arduino IDE.

Код будет написан так же, как мы писали бы любой другой скетч для проекта Arduino, с той лишь разницей, что здесь немного иначе ссылаются на контакты. Чтобы иметь возможность легко разработать код для этого проекта, мы будем использовать две библиотеки, которые представляют собой модификации стандартных библиотек Arduino, чтобы сделать их совместимыми с STM32. Мы будем использовать модифицированную версию библиотек Adafruit GFX (https://github.com/rogerclarkmelbourne/Arduino_STM32/tree/master/STM32F1/libraries/Adafruit_GFX_AS) и Adafruit ST7735 (https://github.com/KenjutsuGH/Adafruit-ST7735-Library).

Код для STM32, написанный в Arduino IDE, выглядит следующим образом:

Загрузка скетча в STM32f1 выполняется немного сложнее по сравнению со стандартными платами, совместимыми с Arduino. Чтобы загрузить код на плату, нам нужен конвертер данных USB в данные последовательного интерфейса на основе FTDI. Подключите конвертер USB к последовательному порту STM32, как показано на схеме далее.

Затем мы затем изменим положение перемычки состояния платы в положение 1 (как показано на рисунке ниже), чтобы перевести плату в режим программирования. Нажмите кнопку сброса на плате один раз после этого, и мы будем готовы загрузить код.

На компьютере убедитесь, что вы выбрали «Generic STM32F103C board» и выбрали последовательный порт для метода загрузки, после чего вы можете нажать кнопку загрузки. После завершения загрузки установите перемычку состояния в положение «0», это переведет плату в режим работы, и теперь она должна начать работать на основе загруженного кода. В этот момент вы можете отключить FTDI и подать питание на плату через USB. Если код не запускается после включения питания, убедитесь, что вы правильно восстановили перемычку и подали питание на плату.

Вы должны увидеть состояние дисплея, как показано на следующем изображении.

Нажмите кнопку, чтобы начать расчет. Вы должны видеть, как индикатор выполнения постепенно скользит до конца. В конце процесса значение числа Пи отображается вместе со временем, которое потребовалось для расчета.

Тот же код реализован на Arduino Uno. Результат показан на изображении ниже.

Сравнивая эти два значения, мы видим, что «Blue Pill» более чем в 7 раз быстрее, чем Arduino Uno. Это делает ее идеальным для проектов, которые включают в себя тяжелую вычислительную обработку и временные ограничения. Небольшой размер этой платы также служит здесь преимуществом, так как она только немного больше, чем Arduino Nano, и ее можно использовать там, где Nano не будет достаточно быстрым.