Составное устройство USB на STM32. Часть 2: USB Audio Speaker


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

    В первой части публикации были описаны предпосылки запуска проекта по разработке составного устройства USB и приведены общие сведения о назначении и составе устройства.

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

    Подготовка проекта


    Проект был создан в STM32CubeIDE из шаблона для платы NUCLEO-F446ZE. Особенностью платы является наличие двух разъёмов USB, к одному из которых подключены встроенный в плату ST-Link V2.1 и виртуальный COM-порт с подключенным к нему UART3. Через этот разъём USB может также осуществляться электропитание платы.

    К виртуальному COM-порту можно подключиться на скорости 115200 bps любой терминальной программой и использовать этот канал связи для приёма сообщений при отладке. Прерывание для UART3 по умолчанию отключено, его надо включить.

    Пользовательское устройство USB Full Speed использует второй разъём USB. Для корректной работы порта USB в составе проекта опцию «Activate_VBUS» нужно отключить.


    При настройке конфигурации порта была обнаружена интересная особенность: при включении опции «Low power» микроконтроллер терял связь с интерфейсом SWD. К счастью, встроенный в плату ST-Link поддерживает режим «Connect under reset», что позволяет выводить MCU из состояния «кирпича» без применения дополнительных аппаратных средств.

    Создаём Audio Device Class


    Приступим к созданию двухканального дуплексного звукового устройства USB, для чего переходим в раздел «Middleware» и выбираем IP «Audio Device Class». Задаём максимальное количество интерфейсов равное трём. Частоту дискретизации устанавливаем равной 48000 samples/s.


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

    Хочу отметить, что использование STM32CubeMX и библиотеки HAL позволяет прятать «под капот» большой объём кода. Для профессиональной разработки такой подход может быть и не приемлем, но для любительского проекта экономит много времени и сил.

    Из всего проекта нас пока интересуют только файлы, расположенные в папках USB_DEVICE/App и Middlewares/ST/Class/AUDIO.

    Для начала разберём, как в файле usb_device.c происходит процесс формирования и запуска устройства USB:

    #include "usb_device.h"
    #include "usbd_core.h"
    #include "usbd_desc.h"
    #include "usbd_audio.h"
    #include "usbd_audio_if.h"
    
    USBD_HandleTypeDef hUsbDeviceFS;
    
    void MX_USB_DEVICE_Init (void)
    {
      USBD_Init (&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
      USBD_RegisterClass (&hUsbDeviceFS, &USBD_AUDIO);
      USBD_AUDIO_RegisterInterface (&hUsbDeviceFS, &USBD_AUDIO_fops_FS);
      USBD_Start (&hUsbDeviceFS);
    }
    

    Сначала создаётся переменная hUsbDeviceFS. Тип USBD_HandleTypeDef объявлен в usbd_def.h.

    Функция MX_USB_DEVICE_Init вызывается из main.c.

    Вызовом функции USBD_Init задаются начальные значения переменной hUsbDeviceFS.

    Вызовом функции USBD_RegisterClass в hUsbDeviceFS.pClass размещается указатель на созданную в usbd_audio.c переменную USBD_AUDIO, содержащую указатели на обработчики событий, относящихся к классу устройства. Тип USBD_ClassTypeDef объявлен в usbd_def.h.

    Вызовом функции USBD_RegisterInterface в hUsbDeviceFS.pUserData размещается указатель на созданную в usbd_audio_if.c переменную USBD_AUDIO_fops_FS, содержащую указатели на обработчики событий, относящихся к пользовательскому интерфейсу устройства. Тип USBD_AUDIO_ItfTypeDef объявлен в usbd_audio.h.

    Вызовом функции USBD_Start производится запуск устройства USB.

    Читаем дескрипторы


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

    Что именно хост получает от сгенерированного в STM32CubeMX звукового устройства, можно узнать с помощью бесплатной утилиты Thesycon USB Descriptor Dumper.

    Посмотреть листинг дескриптора
    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: 0x000A
    Current configuration value: 0x00
    Number of open pipes: 0
    
    Device Descriptor:
    ------------------------------
    0x12	bLength
    0x01	bDescriptorType
    0x0200	bcdUSB
    0x00	bDeviceClass      
    0x00	bDeviceSubClass   
    0x00	bDeviceProtocol   
    0x40	bMaxPacketSize0   (64 bytes)
    0x0483	idVendor
    0x5740	idProduct
    0x0200	bcdDevice
    0x01	iManufacturer
    0x02	iProduct
    0x03	iSerialNumber
    0x01	bNumConfigurations
    
    Configuration Descriptor:
    ------------------------------
    0x09	bLength
    0x02	bDescriptorType
    0x006D	wTotalLength   (109 bytes)
    0x02	bNumInterfaces
    0x01	bConfigurationValue
    0x00	iConfiguration
    0xC0	bmAttributes   (Self-powered Device)
    0x32	bMaxPower      (100 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:
    ------------------------------
    0x09	bLength
    0x24	bDescriptorType
    0x01	bDescriptorSubtype
    0x0100	bcdADC
    0x0027	wTotalLength   (39 bytes)
    0x01	bInCollection
    0x01	baInterfaceNr(1)
    
    AC Input Terminal Descriptor:
    ------------------------------
    0x0C	bLength
    0x24	bDescriptorType
    0x02	bDescriptorSubtype
    0x01	bTerminalID
    0x0101	wTerminalType   (USB Streaming)
    0x00	bAssocTerminal
    0x01	bNrChannels   (1 channels)
    0x0000	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
    
    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
    
    Microsoft OS Descriptor is not available. Error code: 0x0000001F
    
    String Descriptor Table
    --------------------------------
    Index  LANGID  String
    0x00   0x0000  
    0x01   0x0000  Request failed with 0x0000001F
    0x02   0x0000  Request failed with 0x0000001F
    0x03   0x0000  Request failed with 0x0000001F
    ------------------------------
    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


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

    [2] Universal Serial Bus Audio Device Class Specification for Basic Audio Devices. Release 1.0. November 24, 2006
    [3] Universal Serial Bus Device Class Definition for Audio Devices. Release 1.0. March 18, 1998

    Из раздела Device Descriptor мы видим, что устройство поддерживает USB 2.0, имеет единственную конфигурацию, и что класс, подкласс и протокол устройства определяются классом, подклассом и протоколом интерфейса.

    Из раздела Configuration Descriptor мы видим, что длина дескриптора конфигурации класса устройства составляет 109 байт, что в устройство входят два интерфейса, что устройство «самозапитанное» (self-powered) и не может потреблять от шины USB ток более 100 мА.

    Далее идёт описание интерфейса управления (Audio Control Interface, AC), из которого мы узнаём, что структура устройства выглядит так:


    Подробней об этой структуре можно прочитать в [2] на стр.15, 19 – 26.

    Для связи с хостом интерфейс управления использует конечную точку 0 (EP0).

    Данные дескриптора интерфейса воспроизведения (Audio Streaming Interface, AS) описаны в [2] на стр.30 – 34. Сначала идёт описание интерфейса, затем – описание используемых им конечных точек.

    У интерфейса воспроизведения есть два состояния:

    • в состоянии Alternate Setting 0 интерфейс не использует ни одной конечной точки и имеет нулевую полосу пропускания;
    • в состоянии Alternate Setting 1 интерфейс использует одну конечную точку и принимает поток данных для воспроизведения по двум каналам 16-битного звука с частотой дискретизации 48 кГц.

    Конечная точка с адресом 0x01 работает в асинхронном изохронном режиме и принимает пакеты размером ((48000 Гц * 2 байта * 2 канала) / 1000 мс) = 192 байта с интервалом 1 мс.

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


    Файлы сгенерированного в STM32CubeMX драйвера звукового устройства USB расположены в папках Middlewares/ST/Class/AUDIO и USB_DEVICE.

    Функции, с помощью которых драйвер звукового устройства взаимодействует со своим оконечным оборудованием (например ЦАП или кодеком), содержатся в файле usbd_audio_if.c.

    Во время инициализации устройства функции AUDIO_Init_FS передаются значения частоты дискретизации в герцах и начального уровня громкости. Эти данные могут быть использованы для инициализации оконечного оборудования.

    После первоначального заполнения циклического буфера звукового устройства драйвер формирует команду AUDIO_CMD_START и передаёт оконечному оборудованию указатель типа uint8_t* на начало буфера и число типа uint32_t, равное половине длины буфера в байтах.

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

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

    Обратная связь организована через вызов оконечным оборудованием функции HalfTransfer_CallBack_FS после окончания чтения половины буфера, а затем вызов функции TransferComplete_CallBack_FS после полного окончания чтения буфера.

    При запуске функции TransferComplete_CallBack_FS драйвер звукового устройства формирует команду AUDIO_CMD_PLAY и сравнивает скорости записи в буфер и чтения из буфера по положению указателей чтения и записи. Если расстояние между этими указателями меньше четверти размера буфера, оконечному оборудованию передаётся указатель типа uint8_t* на начало буфера, а также число типа uint32_t, равное уменьшенной на 4 половине длины буфера в байтах, если чтение происходит медленней записи, или увеличенной на 4 половине длины буфера в байтах, если чтение происходит быстрей.

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

    От автора


    В следующей части публикации мы:

    • дополним звуковое устройство USB трактом записи;
    • приведем дескриптор звукового устройства USB в читаемый вид;
    • сохраним доработанный драйвер звукового устройства USB в безопасное место;
    • сгенерируем в STM32CubeMX драйвер виртуального COM-порта.

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

    Комментарии 9

      0
      Интересно увидеть эти искажения в тестировании реального устройства в RMAA.
        0
        На панорамном индикаторе эти искажения присутствуют как кратковременный сдвиг спектра, величина сдвига зависит от разницы скоростей входного и выходного потока данных.
        +3
        Так как у самого стоит задача реализовать составное USB устройство (мышь и Custom HID), то затригерился на название статьи. Однако это самое начало пути… Будем следить за развитием.
        А мой проект следующий...
        Мышь для людей с ограниченными возможностями. По сути джойстик. Идея не нова, но заводские стоят просто бешанных денег. Отклонением стика управляется курсор, кнопками действия кнопок мыши и прокрутка колеса. Выглядит это следующим образом.
        image
        Немного информации можно глянуть на сайте: walhi.ru/archives/284.
        Обращался к местной соц. поддержке в поиске заинтересованных людей, но никто не отозвался. Прототип используется парнем, но с человеком «где-то там далеко» сложно исправлять недочеты.
        В данный момент как раз с композитным девайсом и есть проблема. Он у меня корректно определяется в системе уже, мышиная часть работает, а вот часть Custom HID не обменивается пакетами. Перерыв в разработке уже полгода примерно.
          +1
          Позвольте выразить Вам своё уважение, коллега!

          Посоветую ещё ДО объединения устройств в составное добиться, чтобы они правильно работали каждое само по себе.

          Про нюансы объединения двух работоспособных устройств читайте 11.03.2021 четвёртую часть публикации. Особое внимание обратите на доработку usbd_device.c, «неочевидные нюансы» и дескриптор составного устройства в usbd_desc.c.

          У Вас всё получится!
            0
            Со всеми дескрипторами давно уже разобрался. Иначе корректно отображаться оно не могло в диспетчере. А проблема при чтении custom hid. Ответ не проходит. Почитаю статейку, когда опубликуете. Может чего и забыл сделать. Поштучно то все работает и первая половинка комбинированного тоже.
              0

              Я правильно понимаю, что данные из хоста Custom HID получает, и ничего не передаёт в ответ?
              Попробуйте прописать в usbd_conf.c настройки буфера USB для конечной точки Custom HID, добавив вызов функции HAL_PCDEx_TxFifo для этой точки. Где это сделать, поиском найдете по файлу. Там уже есть настройки для конечных точек 0 и 1.

            0
            Успехов вам в проекте!

            Я конечно не знаю нюансов, но вы можете взять arduino pro micro например, QMK и сделать программируемое устройство без проблем. Сам себе клавиатуру собирал на этом — очень легко программировать, прошивать и пользоваться даже человеку без опыта.

            Так же пришла идея — как в некоторых ритм играх — сделать кнопку ускорения движения курсора
              +1

              Напишите как с вами связаться, я бы мог помочь в этой проблеме. Custom hid делал, в т.ч. и довольно сложные.

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

              Самое читаемое