...

вторник, 9 марта 2021 г.

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


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

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

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

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

Исходные коды публикуемой реализации составного устройства USB, состоящего из виртуального COM-порта и дуплексной звуковой карты находятся здесь: http://github.com/dmitrii-rudnev/selenite-habr


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

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

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

// Размер дескриптора конфигурации
#define USB_AUDIO_CONFIG_DESC_SIZ  192U

// Номера интерфейсов
#define AUDIO_CTRL_IF              0x00U
#define AUDIO_OUT_IF               0x01U
#define AUDIO_IN_IF                0x02U

// Номера конечных точек (EP)
#define AUDIO_OUT_EP               0x01U
#define AUDIO_IN_EP                0x81U

// Размер пакета и размер циклического буфера
#define AUDIO_OUT_PACKET_NUM       4U // 80U
#define USBD_AUDIO_FREQ            48000U
#define AUDIO_OUT_PACKET           (uint16_t)(((USBD_AUDIO_FREQ * 2U * 2U) / 1000U))
#define AUDIO_TOTAL_BUF_SIZE       (uint16_t)(AUDIO_OUT_PACKET * AUDIO_OUT_PACKET_NUM))

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

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


Устройства ID2 и ID5 (Feature Unit) оставлены в структуре звукового устройства «на вырост». Управление ими осуществляется через Class-Specific Requests. При обработке этих запросов драйвер звукового устройства должен передавать оконечному устройству набор команд для управления уровнями громкости, настройками эквалайзера, звукового процессора и т.п. В доработанном дескрипторе набор этот состоит пока из одной только команды – MUTE.
Посмотреть листинг доработанного дескриптора
Information for device STM32 Audio Class (VID=0x0483 PID=0x5740):

Connection Information:
------------------------------
Device current bus speed: FullSpeed
Device supports USB 1.1 specification
Device supports USB 2.0 specification
Device address: 0x0008
Current configuration value: 0x01
Number of open pipes: 0

Device Descriptor:
------------------------------
0x12    bLength
0x01    bDescriptorType
0x0201  bcdUSB
0x00    bDeviceClass      
0x00    bDeviceSubClass   
0x00    bDeviceProtocol   
0x40    bMaxPacketSize0   (64 bytes)
0x0483  idVendor
0x5740  idProduct
0x0200  bcdDevice
0x01    iManufacturer   "STMicroelectronics"
0x02    iProduct   "STM32 Audio Class"
0x03    iSerialNumber   "317C33753434"
0x01    bNumConfigurations

Configuration Descriptor:
------------------------------
0x09    bLength
0x02    bDescriptorType
0x00C0  wTotalLength   (192 bytes)
0x03    bNumInterfaces
0x01    bConfigurationValue
0x00    iConfiguration
0xC0    bmAttributes   (Self-powered Device)
0xFA    bMaxPower      (500 mA)

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x00    bInterfaceNumber
0x00    bAlternateSetting
0x00    bNumEndPoints
0x01    bInterfaceClass      (Audio Device Class)
0x01    bInterfaceSubClass   (Audio Control Interface)
0x00    bInterfaceProtocol   
0x00    iInterface

AC Interface Header Descriptor:
------------------------------
0x0A    bLength
0x24    bDescriptorType
0x01    bDescriptorSubtype
0x0100  bcdADC
0x0046  wTotalLength   (70 bytes)
0x02    bInCollection
0x01    baInterfaceNr(1)
0x02    baInterfaceNr(2)

AC Input Terminal Descriptor:
------------------------------
0x0C    bLength
0x24    bDescriptorType
0x02    bDescriptorSubtype
0x01    bTerminalID
0x0101  wTerminalType   (USB Streaming)
0x00    bAssocTerminal
0x02    bNrChannels   (2 channels)
0x0003  wChannelConfig
0x00    iChannelNames
0x00    iTerminal

AC Feature Unit Descriptor:
------------------------------
0x09    bLength
0x24    bDescriptorType
0x06    bDescriptorSubtype
0x02    bUnitID
0x01    bSourceID
0x01    bControlSize
bmaControls: 
 0x01   Channel(0)
 0x00   Channel(1)
0x00    iFeature


AC Output Terminal Descriptor:
------------------------------
0x09    bLength
0x24    bDescriptorType
0x03    bDescriptorSubtype
0x03    bTerminalID
0x0301  wTerminalType   (Speaker)
0x00    bAssocTerminal
0x02    bSourceID
0x00    iTerminal

AC Input Terminal Descriptor:
------------------------------
0x0C    bLength
0x24    bDescriptorType
0x02    bDescriptorSubtype
0x04    bTerminalID
0x0200  wTerminalType   (Input Undefined)
0x00    bAssocTerminal
0x02    bNrChannels   (2 channels)
0x0003  wChannelConfig
0x00    iChannelNames
0x00    iTerminal

AC Feature Unit Descriptor:
------------------------------
0x09    bLength
0x24    bDescriptorType
0x06    bDescriptorSubtype
0x05    bUnitID
0x04    bSourceID
0x01    bControlSize
bmaControls: 
 0x01   Channel(0)
 0x00   Channel(1)
0x00    iFeature


AC Output Terminal Descriptor:
------------------------------
0x09    bLength
0x24    bDescriptorType
0x03    bDescriptorSubtype
0x06    bTerminalID
0x0101  wTerminalType   (USB Streaming)
0x00    bAssocTerminal
0x05    bSourceID
0x00    iTerminal

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x01    bInterfaceNumber
0x00    bAlternateSetting
0x00    bNumEndPoints
0x01    bInterfaceClass      (Audio Device Class)
0x02    bInterfaceSubClass   (Audio Streaming Interface)
0x00    bInterfaceProtocol   
0x00    iInterface

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x01    bInterfaceNumber
0x01    bAlternateSetting
0x01    bNumEndPoints
0x01    bInterfaceClass      (Audio Device Class)
0x02    bInterfaceSubClass   (Audio Streaming Interface)
0x00    bInterfaceProtocol   
0x00    iInterface

AS Interface Descriptor:
------------------------------
0x07    bLength
0x24    bDescriptorType
0x01    bDescriptorSubtype
0x01    bTerminalLink
0x01    bDelay
0x0001  wFormatTag   (PCM)

AS Format Type 1 Descriptor:
------------------------------
0x0B    bLength
0x24    bDescriptorType
0x02    bDescriptorSubtype
0x01    bFormatType   (FORMAT_TYPE_1)
0x02    bNrChannels   (2 channels)
0x02    bSubframeSize
0x10    bBitResolution   (16 bits per sample)
0x01    bSamFreqType   (Discrete sampling frequencies)
0x00BB80        tSamFreq(1)   (48000 Hz)

Endpoint Descriptor (Audio/MIDI 1.0):
------------------------------
0x09    bLength
0x05    bDescriptorType
0x01    bEndpointAddress  (OUT endpoint 1)
0x01    bmAttributes      (Transfer: Isochronous / Synch: None / Usage: Data)
0x00C0  wMaxPacketSize    (1 x 192 bytes)
0x01    bInterval         (1 frames)
0x00    bRefresh
0x00    bSynchAddress

AS Isochronous Data Endpoint Descriptor:
------------------------------
0x07    bLength
0x25    bDescriptorType
0x01    bDescriptorSubtype
0x00    bmAttributes
0x00    bLockDelayUnits   (undefined)
0x0000  wLockDelay

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x02    bInterfaceNumber
0x00    bAlternateSetting
0x00    bNumEndPoints
0x01    bInterfaceClass      (Audio Device Class)
0x02    bInterfaceSubClass   (Audio Streaming Interface)
0x00    bInterfaceProtocol   
0x00    iInterface

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x02    bInterfaceNumber
0x01    bAlternateSetting
0x01    bNumEndPoints
0x01    bInterfaceClass      (Audio Device Class)
0x02    bInterfaceSubClass   (Audio Streaming Interface)
0x00    bInterfaceProtocol   
0x00    iInterface

AS Interface Descriptor:
------------------------------
0x07    bLength
0x24    bDescriptorType
0x01    bDescriptorSubtype
0x06    bTerminalLink
0x01    bDelay
0x0001  wFormatTag   (PCM)

AS Format Type 1 Descriptor:
------------------------------
0x0B    bLength
0x24    bDescriptorType
0x02    bDescriptorSubtype
0x01    bFormatType   (FORMAT_TYPE_1)
0x02    bNrChannels   (2 channels)
0x02    bSubframeSize
0x10    bBitResolution   (16 bits per sample)
0x01    bSamFreqType   (Discrete sampling frequencies)
0x00BB80        tSamFreq(1)   (48000 Hz)

Endpoint Descriptor (Audio/MIDI 1.0):
------------------------------
0x09    bLength
0x05    bDescriptorType
0x81    bEndpointAddress  (IN endpoint 1)
0x01    bmAttributes      (Transfer: Isochronous / Synch: None / Usage: Data)
0x00C0  wMaxPacketSize    (1 x 192 bytes)
0x01    bInterval         (1 frames)
0x00    bRefresh
0x00    bSynchAddress

AS Isochronous Data Endpoint Descriptor:
------------------------------
0x07    bLength
0x25    bDescriptorType
0x01    bDescriptorSubtype
0x00    bmAttributes
0x00    bLockDelayUnits   (undefined)
0x0000  wLockDelay

Microsoft OS Descriptor is not available. Error code: 0x0000001F

String Descriptor Table
--------------------------------
Index  LANGID  String
0x00   0x0000  0x0409 
0x01   0x0409  "STMicroelectronics"
0x02   0x0409  "STM32 Audio Class"
0x03   0x0409  "317C33753434"

------------------------------

Connection path for device: 
xHCI-??????????? ????-?????????? USB
Root Hub
STM32 Audio Class (VID=0x0483 PID=0x5740) Port: 2

Running on: Windows 10 or greater

Brought to you by TDD v2.11.0, Mar 26 2018, 09:54:50



При доработке драйвера в структуру звукового устройства был добавлен циклический буфер тракта записи, а состав команд, передаваемых пользовательскому интерфейсу, расширен командами 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 не производит никаких действий и оставлена в составе драйвера только для совместимости.


При генерации драйвера виртуального 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.


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

[4] Universal Serial Bus Communications Class Subclass Specification for PSTN Devices, Revision 1.2, February 9, 2007

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

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

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


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

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

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  CDC_Transmit_FS (Buf, *Len); //++++++

  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

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

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

От автора


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

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий