В третьей части публикации о составном устройстве 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: FullSpeedDevice supports USB 1.1 specificationDevice supports USB 2.0 specificationDevice address: 0x0008Current configuration value: 0x01Number of open pipes: 0Device Descriptor:------------------------------0x12bLength0x01bDescriptorType0x0201bcdUSB0x00bDeviceClass 0x00bDeviceSubClass 0x00bDeviceProtocol 0x40bMaxPacketSize0 (64 bytes)0x0483idVendor0x5740idProduct0x0200bcdDevice0x01iManufacturer "STMicroelectronics"0x02iProduct "STM32 Audio Class"0x03iSerialNumber "317C33753434"0x01bNumConfigurationsConfiguration Descriptor:------------------------------0x09bLength0x02bDescriptorType0x00C0wTotalLength (192 bytes)0x03bNumInterfaces0x01bConfigurationValue0x00iConfiguration0xC0bmAttributes (Self-powered Device)0xFAbMaxPower (500 mA)Interface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x00bInterfaceNumber0x00bAlternateSetting0x00bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x01bInterfaceSubClass (Audio Control Interface)0x00bInterfaceProtocol 0x00iInterfaceAC Interface Header Descriptor:------------------------------0x0AbLength0x24bDescriptorType0x01bDescriptorSubtype0x0100bcdADC0x0046wTotalLength (70 bytes)0x02bInCollection0x01baInterfaceNr(1)0x02baInterfaceNr(2)AC Input Terminal Descriptor:------------------------------0x0CbLength0x24bDescriptorType0x02bDescriptorSubtype0x01bTerminalID0x0101wTerminalType (USB Streaming)0x00bAssocTerminal0x02bNrChannels (2 channels)0x0003wChannelConfig0x00iChannelNames0x00iTerminalAC Feature Unit Descriptor:------------------------------0x09bLength0x24bDescriptorType0x06bDescriptorSubtype0x02bUnitID0x01bSourceID0x01bControlSizebmaControls: 0x01Channel(0) 0x00Channel(1)0x00iFeatureAC Output Terminal Descriptor:------------------------------0x09bLength0x24bDescriptorType0x03bDescriptorSubtype0x03bTerminalID0x0301wTerminalType (Speaker)0x00bAssocTerminal0x02bSourceID0x00iTerminalAC Input Terminal Descriptor:------------------------------0x0CbLength0x24bDescriptorType0x02bDescriptorSubtype0x04bTerminalID0x0200wTerminalType (Input Undefined)0x00bAssocTerminal0x02bNrChannels (2 channels)0x0003wChannelConfig0x00iChannelNames0x00iTerminalAC Feature Unit Descriptor:------------------------------0x09bLength0x24bDescriptorType0x06bDescriptorSubtype0x05bUnitID0x04bSourceID0x01bControlSizebmaControls: 0x01Channel(0) 0x00Channel(1)0x00iFeatureAC Output Terminal Descriptor:------------------------------0x09bLength0x24bDescriptorType0x03bDescriptorSubtype0x06bTerminalID0x0101wTerminalType (USB Streaming)0x00bAssocTerminal0x05bSourceID0x00iTerminalInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x01bInterfaceNumber0x00bAlternateSetting0x00bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x02bInterfaceSubClass (Audio Streaming Interface)0x00bInterfaceProtocol 0x00iInterfaceInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x01bInterfaceNumber0x01bAlternateSetting0x01bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x02bInterfaceSubClass (Audio Streaming Interface)0x00bInterfaceProtocol 0x00iInterfaceAS Interface Descriptor:------------------------------0x07bLength0x24bDescriptorType0x01bDescriptorSubtype0x01bTerminalLink0x01bDelay0x0001wFormatTag (PCM)AS Format Type 1 Descriptor:------------------------------0x0BbLength0x24bDescriptorType0x02bDescriptorSubtype0x01bFormatType (FORMAT_TYPE_1)0x02bNrChannels (2 channels)0x02bSubframeSize0x10bBitResolution (16 bits per sample)0x01bSamFreqType (Discrete sampling frequencies)0x00BB80 tSamFreq(1) (48000 Hz)Endpoint Descriptor (Audio/MIDI 1.0):------------------------------0x09bLength0x05bDescriptorType0x01bEndpointAddress (OUT endpoint 1)0x01bmAttributes (Transfer: Isochronous / Synch: None / Usage: Data)0x00C0wMaxPacketSize (1 x 192 bytes)0x01bInterval (1 frames)0x00bRefresh0x00bSynchAddressAS Isochronous Data Endpoint Descriptor:------------------------------0x07bLength0x25bDescriptorType0x01bDescriptorSubtype0x00bmAttributes0x00bLockDelayUnits (undefined)0x0000wLockDelayInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x02bInterfaceNumber0x00bAlternateSetting0x00bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x02bInterfaceSubClass (Audio Streaming Interface)0x00bInterfaceProtocol 0x00iInterfaceInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x02bInterfaceNumber0x01bAlternateSetting0x01bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x02bInterfaceSubClass (Audio Streaming Interface)0x00bInterfaceProtocol 0x00iInterfaceAS Interface Descriptor:------------------------------0x07bLength0x24bDescriptorType0x01bDescriptorSubtype0x06bTerminalLink0x01bDelay0x0001wFormatTag (PCM)AS Format Type 1 Descriptor:------------------------------0x0BbLength0x24bDescriptorType0x02bDescriptorSubtype0x01bFormatType (FORMAT_TYPE_1)0x02bNrChannels (2 channels)0x02bSubframeSize0x10bBitResolution (16 bits per sample)0x01bSamFreqType (Discrete sampling frequencies)0x00BB80 tSamFreq(1) (48000 Hz)Endpoint Descriptor (Audio/MIDI 1.0):------------------------------0x09bLength0x05bDescriptorType0x81bEndpointAddress (IN endpoint 1)0x01bmAttributes (Transfer: Isochronous / Synch: None / Usage: Data)0x00C0wMaxPacketSize (1 x 192 bytes)0x01bInterval (1 frames)0x00bRefresh0x00bSynchAddressAS Isochronous Data Endpoint Descriptor:------------------------------0x07bLength0x25bDescriptorType0x01bDescriptorSubtype0x00bmAttributes0x00bLockDelayUnits (undefined)0x0000wLockDelayMicrosoft OS Descriptor is not available. Error code: 0x0000001FString Descriptor Table--------------------------------Index LANGID String0x00 0x0000 0x0409 0x01 0x0409 "STMicroelectronics"0x02 0x0409 "STM32 Audio Class"0x03 0x0409 "317C33753434"------------------------------Connection path for device: xHCI-??????????? ????-?????????? USBRoot HubSTM32 Audio Class (VID=0x0483 PID=0x5740) Port: 2Running on: Windows 10 or greaterBrought to you by TDD v2.11.0, Mar 26 2018, 09:54:50
Доработка драйвера звукового устройства 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 документа:
[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-порта
Виртуальный 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 и разберём несколько не совсем очевидных нюансов этого объединения.