...

четверг, 11 марта 2021 г.

Составное устройство USB на STM32. Часть 4: Два-в-одном


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

Работа составных частей устройства была описана во второй и третьей частях публикации.

Ответы на вопрос, зачем это всё было затеяно, даются в начале первой части и в конце четвёртой.

Ссылки на предыдущие части публикации:
Составное устройство USB на STM32. Часть 1: Предпосылки
Составное устройство USB на STM32. Часть 2: USB Audio Speaker
Составное устройство USB на STM32. Часть 3: Звуковое устройство отдельно, виртуальный СОМ-порт отдельно

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


Файлы драйвера составного устройства 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.

Посмотреть листинг дескриптора составного устройства USB
Information for device Selenite TRX (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: 0x0014
Current configuration value: 0x00
Number of open pipes: 0

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

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

Interface Association Descriptor:
------------------------------
0x08    bLength
0x0B    bDescriptorType
0x00    bFirstInterface
0x02    bInterfaceCount
0x02    bFunctionClass      (Communication Device Class)
0x02    bFunctionSubClass   (Abstract Control Model - ACM)
0x01    bFunctionProtocol   (ITU-T V.250)
0x00    iFunction

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x00    bInterfaceNumber
0x00    bAlternateSetting
0x01    bNumEndPoints
0x02    bInterfaceClass      (Communication Device Class)
0x02    bInterfaceSubClass   (Abstract Control Model - ACM)
0x01    bInterfaceProtocol   (ITU-T V.250)
0x00    iInterface

CDC Header Functional Descriptor:
------------------------------
0x05    bFunctionalLength
0x24    bDescriptorType
0x00    bDescriptorSubtype
0x0110  bcdCDC

CDC Call Management Functional Descriptor:
------------------------------
0x05    bFunctionalLength
0x24    bDescriptorType
0x01    bDescriptorSubtype
0x00    bmCapabilities
0x01    bDataInterface

CDC Abstract Control Management Functional Descriptor:
------------------------------
0x04    bFunctionalLength
0x24    bDescriptorType
0x02    bDescriptorSubtype
0x02    bmCapabilities

CDC Union Functional Descriptor:
------------------------------
0x05    bFunctionalLength
0x24    bDescriptorType
0x06    bDescriptorSubtype
0x00    bControlInterface
0x01    bSubordinateInterface(0)

Endpoint Descriptor:
------------------------------
0x07    bLength
0x05    bDescriptorType
0x82    bEndpointAddress  (IN endpoint 2)
0x03    bmAttributes      (Transfer: Interrupt / Synch: None / Usage: Data)
0x0008  wMaxPacketSize    (1 x 8 bytes)
0x10    bInterval         (16 frames)

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x01    bInterfaceNumber
0x00    bAlternateSetting
0x02    bNumEndPoints
0x0A    bInterfaceClass      (CDC Data)
0x00    bInterfaceSubClass   
0x00    bInterfaceProtocol   
0x00    iInterface

Endpoint Descriptor:
------------------------------
0x07    bLength
0x05    bDescriptorType
0x01    bEndpointAddress  (OUT endpoint 1)
0x02    bmAttributes      (Transfer: Bulk / Synch: None / Usage: Data)
0x0040  wMaxPacketSize    (64 bytes)
0x00    bInterval         

Endpoint Descriptor:
------------------------------
0x07    bLength
0x05    bDescriptorType
0x81    bEndpointAddress  (IN endpoint 1)
0x02    bmAttributes      (Transfer: Bulk / Synch: None / Usage: Data)
0x0040  wMaxPacketSize    (64 bytes)
0x00    bInterval         

Interface Association Descriptor:
------------------------------
0x08    bLength
0x0B    bDescriptorType
0x02    bFirstInterface
0x03    bInterfaceCount
0x01    bFunctionClass      (Audio Device Class)
0x01    bFunctionSubClass   (Audio Control Interface)
0x00    bFunctionProtocol   
0x00    iFunction

Interface Descriptor:
------------------------------
0x09    bLength
0x04    bDescriptorType
0x02    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
0x03    baInterfaceNr(1)
0x04    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
0x03    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
0x03    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
0x03    bEndpointAddress  (OUT endpoint 3)
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
0x04    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
0x04    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
0x83    bEndpointAddress  (IN endpoint 3)
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  "Selenite TRX"
0x03   0x0409  "317C33753434"

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

Connection path for device: 
xHCI-??????????? ????-?????????? USB
Root Hub
Selenite TRX (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



Рассмотрим доработанный файл 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-порту могут и не подключиться.


Соединяем воедино проверки работоспособности драйвера виртуального 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

Let's block ads! (Why?)

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

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