В заключительной части публикации о составном устройстве USB я расскажу о том, как заставил заработать составное устройство USB, а также поделюсь некоторыми неочевидными нюансами этого процесса.
Работа составных частей устройства была описана во второй и третьей частях публикации.
Ответы на вопрос, зачем это всё было затеяно, даются в начале первой части и в конце четвёртой.
Ссылки на предыдущие части публикации:
Составное устройство USB на STM32. Часть 1: Предпосылки
Составное устройство USB на STM32. Часть 2: USB Audio Speaker
Составное устройство USB на STM32. Часть 3: Звуковое устройство отдельно, виртуальный СОМ-порт отдельно
Исходные коды публикуемой реализации составного устройства USB, состоящего из виртуального COM-порта и дуплексной звуковой карты находятся здесь: http://github.com/dmitrii-rudnev/selenite-habr
Создаём Composite Device Class
Файлы драйвера составного устройства usbd_comp.c и usbd_comp.h расположены в папках Core/Scr и Core/Inc соответственно.
Структура класса составного устройства аналогична структуре класса звукового устройства и содержит подобный набор функций-обработчиков событий.
Основная функция драйвера составного устройства заключается в том, чтобы определить, драйвер какого устройства нужно подключить для обработки события. При обработке запросов (Requests) это определяется по номеру интерфейса в случае Standard Requests или атрибутам запроса в случае Class-Specific Requests. При обработке пакетов данных переключение производится, как правило, по номеру конечной точки (EP).
Подробно Standard Requests описаны на стр.248 260 документа:
[5] Universal Serial Bus Specification, Revision 2.0, April 27, 2000
Запросы Communication Device Class-Specific Requests подробно описаны на стр.18 30 документа [4], а Audio Device Class-Specific Requests, соответственно, на стр.74 85 документа [3].
Читаем дескриптор
Дескриптор описанного в публикации составного устройства USB состоит из девяти байтов раздела Configuration Descriptor, восьми байтов раздела Interface Association Descriptor (IAD) для двух интерфейсов виртуального COM-порта, 58 байтов дескриптора виртуального COM-порта, восьми байтов раздела IAD для трёх интерфейсов звукового устройства и 183 байтов дескриптора звукового устройства USB.
Виртуальный COM-порт использует интерфейсы 0 и 1, а также конечные точки 1 и 2. Дуплексное звуковое устройство использует интерфейсы 2, 3 и 4, а также конечную точку 3.
Information for device Selenite TRX (VID=0x0483 PID=0x5740):Connection Information:------------------------------Device current bus speed: FullSpeedDevice supports USB 1.1 specificationDevice supports USB 2.0 specificationDevice address: 0x0014Current configuration value: 0x00Number of open pipes: 0Device Descriptor:------------------------------0x12bLength0x01bDescriptorType0x0201bcdUSB0xEFbDeviceClass (Miscellaneous device)0x02bDeviceSubClass 0x01bDeviceProtocol 0x40bMaxPacketSize0 (64 bytes)0x0483idVendor0x5740idProduct0x0200bcdDevice0x01iManufacturer "STMicroelectronics"0x02iProduct "Selenite TRX"0x03iSerialNumber "317C33753434"0x01bNumConfigurationsConfiguration Descriptor:------------------------------0x09bLength0x02bDescriptorType0x010AwTotalLength (266 bytes)0x05bNumInterfaces0x01bConfigurationValue0x00iConfiguration0xC0bmAttributes (Self-powered Device)0xFAbMaxPower (500 mA)Interface Association Descriptor:------------------------------0x08bLength0x0BbDescriptorType0x00bFirstInterface0x02bInterfaceCount0x02bFunctionClass (Communication Device Class)0x02bFunctionSubClass (Abstract Control Model - ACM)0x01bFunctionProtocol (ITU-T V.250)0x00iFunctionInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x00bInterfaceNumber0x00bAlternateSetting0x01bNumEndPoints0x02bInterfaceClass (Communication Device Class)0x02bInterfaceSubClass (Abstract Control Model - ACM)0x01bInterfaceProtocol (ITU-T V.250)0x00iInterfaceCDC Header Functional Descriptor:------------------------------0x05bFunctionalLength0x24bDescriptorType0x00bDescriptorSubtype0x0110bcdCDCCDC Call Management Functional Descriptor:------------------------------0x05bFunctionalLength0x24bDescriptorType0x01bDescriptorSubtype0x00bmCapabilities0x01bDataInterfaceCDC Abstract Control Management Functional Descriptor:------------------------------0x04bFunctionalLength0x24bDescriptorType0x02bDescriptorSubtype0x02bmCapabilitiesCDC Union Functional Descriptor:------------------------------0x05bFunctionalLength0x24bDescriptorType0x06bDescriptorSubtype0x00bControlInterface0x01bSubordinateInterface(0)Endpoint Descriptor:------------------------------0x07bLength0x05bDescriptorType0x82bEndpointAddress (IN endpoint 2)0x03bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)0x0008wMaxPacketSize (1 x 8 bytes)0x10bInterval (16 frames)Interface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x01bInterfaceNumber0x00bAlternateSetting0x02bNumEndPoints0x0AbInterfaceClass (CDC Data)0x00bInterfaceSubClass 0x00bInterfaceProtocol 0x00iInterfaceEndpoint Descriptor:------------------------------0x07bLength0x05bDescriptorType0x01bEndpointAddress (OUT endpoint 1)0x02bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)0x0040wMaxPacketSize (64 bytes)0x00bInterval Endpoint Descriptor:------------------------------0x07bLength0x05bDescriptorType0x81bEndpointAddress (IN endpoint 1)0x02bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)0x0040wMaxPacketSize (64 bytes)0x00bInterval Interface Association Descriptor:------------------------------0x08bLength0x0BbDescriptorType0x02bFirstInterface0x03bInterfaceCount0x01bFunctionClass (Audio Device Class)0x01bFunctionSubClass (Audio Control Interface)0x00bFunctionProtocol 0x00iFunctionInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x02bInterfaceNumber0x00bAlternateSetting0x00bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x01bInterfaceSubClass (Audio Control Interface)0x00bInterfaceProtocol 0x00iInterfaceAC Interface Header Descriptor:------------------------------0x0AbLength0x24bDescriptorType0x01bDescriptorSubtype0x0100bcdADC0x0046wTotalLength (70 bytes)0x02bInCollection0x03baInterfaceNr(1)0x04baInterfaceNr(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:------------------------------0x09bLength0x04bDescriptorType0x03bInterfaceNumber0x00bAlternateSetting0x00bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x02bInterfaceSubClass (Audio Streaming Interface)0x00bInterfaceProtocol 0x00iInterfaceInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x03bInterfaceNumber0x01bAlternateSetting0x01bNumEndPoints0x01bInterfaceClass (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):------------------------------0x09bLength0x05bDescriptorType0x03bEndpointAddress (OUT endpoint 3)0x01bmAttributes (Transfer: Isochronous / Synch: None / Usage: Data)0x00C0wMaxPacketSize (1 x 192 bytes)0x01bInterval (1 frames)0x00bRefresh0x00bSynchAddressAS Isochronous Data Endpoint Descriptor:------------------------------0x07bLength0x25bDescriptorType0x01bDescriptorSubtype0x00bmAttributes0x00bLockDelayUnits (undefined)0x0000wLockDelayInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x04bInterfaceNumber0x00bAlternateSetting0x00bNumEndPoints0x01bInterfaceClass (Audio Device Class)0x02bInterfaceSubClass (Audio Streaming Interface)0x00bInterfaceProtocol 0x00iInterfaceInterface Descriptor:------------------------------0x09bLength0x04bDescriptorType0x04bInterfaceNumber0x01bAlternateSetting0x01bNumEndPoints0x01bInterfaceClass (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):------------------------------0x09bLength0x05bDescriptorType0x83bEndpointAddress (IN endpoint 3)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 "Selenite TRX"0x03 0x0409 "317C33753434"------------------------------Connection path for device: xHCI-??????????? ????-?????????? USBRoot HubSelenite TRX (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_device.c, расположенный в папке USB_DEVICE/App:
#include "usb_device.h"#include "usbd_core.h"#include "usbd_desc.h"#include "usbd_cdc.h"#include "usbd_cdc_if.h"/* USER CODE BEGIN Includes */#include "usbd_conf.h"#include "usbd_comp.h"/* USER CODE END Includes *//* USER CODE BEGIN PV */extern PCD_HandleTypeDef hpcd_USB_OTG_FS;/* USER CODE END PV *//* USB Device Core handle declaration. */USBD_HandleTypeDef hUsbDeviceFS;void MX_USB_DEVICE_Init(void){ /* USER CODE BEGIN USB_DEVICE_Init_PreTreatment */ USBD_Init (&hUsbDeviceFS, &FS_Desc, DEVICE_FS); //HAL_PCDEx_SetRxFiFo (&hpcd_USB_OTG_FS, 0x80); //HAL_PCDEx_SetTxFiFo (&hpcd_USB_OTG_FS, 0, 0x40); HAL_PCDEx_SetTxFiFo (&hpcd_USB_OTG_FS, 1, 0x10); HAL_PCDEx_SetTxFiFo (&hpcd_USB_OTG_FS, 2, 0x10); HAL_PCDEx_SetTxFiFo (&hpcd_USB_OTG_FS, 3, 0xC0); USBD_RegisterClass (&hUsbDeviceFS, &USBD_COMP); USBD_COMP_RegisterInterface (&hUsbDeviceFS, &USBD_COMP_fops_FS); USBD_Start (&hUsbDeviceFS); return; /* USER CODE END USB_DEVICE_Init_PreTreatment */
Сначала создаётся переменная hUsbDeviceFS. Тип USBD_HandleTypeDef объявлен в usbd_def.h.
Функция MX_USB_DEVICE_Init вызывается из main.c.
Вызовом функции USBD_Init задаются начальные значения переменной hUsbDeviceFS.
Затем вызовом функций HAL_PCDEx_SetTxFiFo производится настройка буфера USB для каждой конечной точки составного устройства.
Неочевидный нюанс 1: по умолчанию настройка буфера USB производится при исполнении функции USBD_LL_Init, размещённой в файле usbd_conf.c. В теле этой функции области, помеченной как USER CODE, нет. Т.е. при каждой генерации кода STM32CubeMX будет удалять настройки буфера для конечных точек 2 и 3. Именно поэтому окончательная настройка буфера USB производится уже после того, как функция USBD_LL_Init отработала.
Вызовом функции USBD_RegisterClass в hUsbDeviceFS.pClass размещается указатель на созданную в usbd_comp.c переменную USBD_COMP, содержащую указатели на обработчики событий, относящихся к классу устройства. Тип USBD_ClassTypeDef объявлен в usbd_def.h.
Вызовом функции USBD_RegisterInterface в hUsbDeviceFS.pUserData размещается указатель на созданную в usbd_comp.h пустую переменную USBD_COMP_fops_FS.
В дальнейшем обработчики событий составного устройства USB будут вызывать обработчики событий нужного устройства, входящего в составное, а также подключать нужный интерфейс связи с оконечными устройствами.
Вызовом функции USBD_Start производится запуск устройства USB.
Неочевидный нюанс 2: составное устройство будет упорно определяться как виртуальный COM-порт, если не поменять значения трёх байтов в стандартном дескрипторе устройства USB (USB standard device descriptor), размещённом в файле usbd_desc.c, причём при каждой генерации кода STM32CubeMX эти изменения будет удалять:
/** USB standard device descriptor. */__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END ={ 0x12, /*bLength */ USB_DESC_TYPE_DEVICE, /*bDescriptorType*/#if (USBD_LPM_ENABLED == 1) 0x01, /*bcdUSB */ /* changed to USB version 2.01 in order to support LPM L1 suspend resume test of USBCV3.0*/#else 0x00, /*bcdUSB */#endif /* (USBD_LPM_ENABLED == 1) */ 0x02, //0x02, /*bDeviceClass*/ //0x02, /*bDeviceSubClass*/ //0x00, /*bDeviceProtocol*/ 0xEF, /*bDeviceClass = Misc */ 0x02, /*bDeviceSubClass = Common Class */ 0x01, /*bDeviceProtocol = IAD */ USB_MAX_EP0_SIZE, /*bMaxPacketSize*/ LOBYTE(USBD_VID), /*idVendor*/ HIBYTE(USBD_VID), /*idVendor*/ LOBYTE(USBD_PID_FS), /*idProduct*/ HIBYTE(USBD_PID_FS), /*idProduct*/ 0x00, /*bcdDevice rel. 2.00*/ 0x02, USBD_IDX_MFC_STR, /*Index of manufacturer string*/ USBD_IDX_PRODUCT_STR, /*Index of product string*/ USBD_IDX_SERIAL_STR, /*Index of serial number string*/ USBD_MAX_NUM_CONFIGURATION /*bNumConfigurations*/};
Неочевидный нюанс 3: виртуальный COM-порт в данном решении работает корректно только в случае, когда номер используемой им конечной точки меньше, чем номер конечной точки звукового устройства.
Неочевидный нюанс 4: виртуальный COM-порт в данном решении работает корректно только в случае, когда при инициализации в его буфер прописываются параметры порта (см. USBD_COMP_Init). Без этой записи программы терминалов к COM-порту могут и не подключиться.
Проверка работоспособности драйвера составного устройства USB
Соединяем воедино проверки работоспособности драйвера виртуального COM-порта и дуплексного звукового устройства USB. Убеждаемся, что они отлично уживаются.
Неочевидный нюанс 5: при проверке работоспособности эхо через COM-порт возвращается, когда составное устройство уже переключено на COM-порт. В реальном применении устройства передача может начаться, когда подключено звуковое устройство. Чтобы избежать подобной ситуации, перед началом передачи производится вызов функции COMP_CDC_Transmit_FS для подключения драйвера виртуального COM-порта:
/* USER CODE BEGIN INCLUDE */#include "usbd_comp.h"/* USER CODE END INCLUDE */uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len){ uint8_t result = USBD_OK; /* USER CODE BEGIN 7 */ result = COMP_CDC_Transmit_FS (Buf, Len); //++++++ USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData; if (hcdc->TxState != 0){ return USBD_BUSY; } USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len); result = USBD_CDC_TransmitPacket(&hUsbDeviceFS); /* USER CODE END 7 */ return result;}
Выводы
Автору удалось реализовать составное устройство USB, состоящее из виртуального COM-порта и дуплексной звуковой карты, на ресурсах платы NUCLEO-F446ZE.
Решение оформлено в виде проекта в среде разработки STM32CubeIDE. После генерации кода STM32CubeMX для восстановления работоспособности решения необходимо вручную изменить значения трёх байтов в стандартном дескрипторе устройства USB (USB standard device descriptor), размещённом в файле usbd_desc.c.
От автора
Данный цикл публикаций подводит черту, фиксирует результат проекта, которой мне удалось достичь в одиночку.
Хочу поблагодарить своих читателей за доброжелательность и тёплый приём. Я никогда не был и никогда уже не буду профессиональным разработчиком ПО для микроконтроллеров. И это моя первая публикация про разработку программного обеспечения.
Благодарю Георгия (RX9CIM) за моральную поддержку при запуске проекта.
Отдельная благодарность romanetz_omsk, без которого я бы забросил проект ещё два года назад.
По логике дальнейшего развития MVP нужно приступать к написанию DSP, а это уже достаточно сложная для меня математика. Как это осилить в одиночку, ума не приложу
73! de RD9F