STM32Cube. USB Custom HID. Создание дескрипторов устройства.

Доброго дня всем посетителям и читателям нашего сайта! Наконец-то, после длительного летнего перерыва, возобновляется работа над новыми статьями ) Тем накопилось очень много, темы абсолютно разные, так что в ближайшее время, надеюсь каждый найдет что-то интересное для себя среди новых статей. И сегодня мы начнем обсуждать реализацию класса USB HID для наших любимых микроконтроллеров STM32. Почему начнем? Просто я решил, что для одной статьи тут будет многовато всего, поэтому их будет две или три, так что давайте приступать 😉

STM32Cube USB

Итак, по старой доброй традиции разберемся с инструментарием… Я буду использовать отладочную плату с микроконтроллером STM32F103VET6, а для создания проекта мы задействуем набирающий все большую популярность STM32Cube.

Запускаем Cube, создаем новый проект (на этих шагах я не останавливаюсь, все уже разобрано в мини-цикле статей, посвященных этой теме на нашем сайте — их можно найти в рубрике STM32Cube). Включаем только то, что нам понадобится для решения непосредственно сегодняшней задачи, ничего лишнего =)

Работа с USB HID

Кроме того, на моей плате есть вот такая фишка:

Подключение отладочной платы по USB

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

В настройках тактирования нам необходимо обеспечить 48 МГц для работы USB. Тут ничего сложного, просто подбираем нужные значения умножителей и делителей частоты и не забываем активировать внешний кварц, который мы уже подключили в предыдущем окне:

Переходим на вкладку Configuration, но там все оставляем в первозданном виде. Разве что можно зайти в настройки USB_Device и посмотреть значения PID и VID, они могут нам пригодиться впоследствии для того, чтобы проверить, действительно ли наше устройство успешно подключилось к компьютеру. Но не будем забегать вперед, а просто запомним или запишем эти значения. По умолчанию они такие:

  • PID — 22352 (0x5750)
  • VID — 1155 (0x483)

Работу в Cube на этом заканчиваем, генерируем файлы проекта и переходим в IAR (ну или Keil, у каждого своя IDE 😉 ). Мы можем прошить полученную программу в микроконтроллер, но, к сожалению, это нам ничего не даст — устройство определится в системе, но работать не будет, в диспетчере устройств мы увидим ошибку — Сбой запроса дескриптора устройства. И эта ситуация как ни парадоксально абсолютно правильна и логична. Дело в том, что дескриптор для нашего Custom HID устройства мы должны создать самостоятельно и добавить в наш сгенерированный проект. Этим мы сейчас и займемся.

Всего есть три дескриптора, которые отвечают за подключение к ПК:

  •  дескриптор устройства
  •  дескриптор конфигурации
  • дескриптор репорта

Дескриптор устройства описан в файле usbd_desc.c и выглядит следующим образом:

/* USB Standard Device Descriptor */
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
  {
    0x12,                       /*bLength */
    USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
    0x00,                       /*bcdUSB */
    0x02,
    0x00,                       /*bDeviceClass*/
    0x00,                       /*bDeviceSubClass*/
    0x00,                       /*bDeviceProtocol*/
    USB_MAX_EP0_SIZE,          /*bMaxPacketSize*/
    LOBYTE(USBD_VID),           /*idVendor*/
    HIBYTE(USBD_VID),           /*idVendor*/
    LOBYTE(USBD_PID_FS),           /*idVendor*/
    HIBYTE(USBD_PID_FS),           /*idVendor*/
    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*/
  } ; /* USB_DeviceDescriptor */

В принципе здесь все снабжено комментариями, все значения стандартные, ну и мы собственно, здесь ничего трогать сейчас не будем, а двинемся дальше.
Дескриптор конфигурации мы можем найти в файле usbd_hustomhid.c:

/* USB CUSTOM_HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
  0x09, /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  USB_CUSTOM_HID_CONFIG_DESC_SIZ,
  /* wTotalLength: Bytes returned */
  0x00,
  0x01,         /*bNumInterfaces: 1 interface*/
  0x01,         /*bConfigurationValue: Configuration value*/
  0x00,         /*iConfiguration: Index of string descriptor describing
  the configuration*/
  0xC0,         /*bmAttributes: bus powered */
  0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/
 
  /************** Descriptor of CUSTOM HID interface ****************/
  /* 09 */
  0x09,         /*bLength: Interface Descriptor size*/
  USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  0x00,         /*bInterfaceNumber: Number of Interface*/
  0x00,         /*bAlternateSetting: Alternate setting*/
  0x02,         /*bNumEndpoints*/
  0x03,         /*bInterfaceClass: CUSTOM_HID*/
  0x00,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  0x00,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  0,            /*iInterface: Index of string descriptor*/
  /******************** Descriptor of CUSTOM_HID *************************/
  /* 18 */
  0x09,         /*bLength: CUSTOM_HID Descriptor size*/
  CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/
  0x11,         /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/
  0x01,
  0x00,         /*bCountryCode: Hardware target country*/
  0x01,         /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/
  0x22,         /*bDescriptorType*/
  USBD_CUSTOM_HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
  0x00,
  /******************** Descriptor of Custom HID endpoints ********************/
  /* 27 */
  0x07,          /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
 
  CUSTOM_HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,          /*bmAttributes: Interrupt endpoint*/
  CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */
  0x00,
  0x20,          /*bInterval: Polling Interval (20 ms)*/
  /* 34 */
 
  0x07,	         /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT,	/* bDescriptorType: */
  CUSTOM_HID_EPOUT_ADDR,  /*bEndpointAddress: Endpoint Address (OUT)*/
  0x03,	/* bmAttributes: Interrupt endpoint */
  CUSTOM_HID_EPOUT_SIZE,	/* wMaxPacketSize: 2 Bytes max  */
  0x00,
  0x20,	/* bInterval: Polling Interval (20 ms) */
  /* 41 */
} ;

В общем-то здесь тоже инженеры и программисты ST позаботились о том, чтобы хорошенько все прокомментировать, поэтому я не буду останавливаться на каждом поле (если возникнут вопросы или что-то не будет работать, пишите в комментариях, постараюсь помочь =) ). Отдельно стоит обратить внимание на CUSTOM_HID_EPOUT_SIZE. В файле usbd_customhid.h находим:

#define CUSTOM_HID_EPOUT_SIZE                0x02

Эта константа определяет максимальный размер пакетов, которые будут использоваться при обмене данными с ПК. Давайте поставим 4 байта. Но не забывайте, что по стандарту максимальный размер пакета составляет 64 байта (0x40), больше ставить не рекомендую 😉

С двумя из упомянутых дескрипторов разобрались, теперь настала очередь третьего. Он определен в файле usbd_custom_hid_if.c:

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */ 
  0x00, 
  /* USER CODE END 0 */ 
  0xC0    /*     END_COLLECTION	             */
 
};

Как видите он абсолютно пуст, поэтому наше устройство и не смогло определиться в системе как положено. Так что давайте разберемся и реализуем дескриптор репорта.

Что такое в принципе репорт?

Все дело в том, что ПК и наше устройство не просто кидают друг другу байты данных, а обмениваются пакетами, имеющими четко определенную структуру. Идентификация пакета происходит по первому байту, который представляет из себя ID репорта. Перед нами стоит задача отправлять данные хосту, а также принимать данные, передающиеся в обратном направлении. Поэтому мы определим два репорта — на прием и на передачу. Пусть на передачу у нас будет репорт с ID = 1, а на прием репорт с ID = 2:

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */ 
    0x06, 0x00, 0xff,              // 	USAGE_PAGE (Generic Desktop)
    0x09, 0x01,                    // 	USAGE (Vendor Usage 1)
    // System Parameters
    0xa1, 0x01,                    // 	COLLECTION (Application)
    0x85, 0x01,                    //   REPORT_ID (1)
    0x09, 0x01,                    //   USAGE (Vendor Usage 1)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 4, 	                   //   REPORT_COUNT (4)
    0xb1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
    0x85, 0x01,                    //   REPORT_ID (1)
    0x09, 0x01,                    //   USAGE (Vendor Usage 1)
    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
 
    0x85, 0x02,                    //   REPORT_ID (4)
    0x09, 0x02,                    //   USAGE (Vendor Usage 4)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 4, 	                   //   REPORT_COUNT (4)
    0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)
  /* USER CODE END 0 */ 
  0xC0    /*     END_COLLECTION	             */
 
};

Не забываем изменить размер массива дескриптора — USBD_CUSTOM_HID_REPORT_DESC_SIZE, а заодно и USBD_CUSTOMHID_OUTREPORT_BUF_SIZE.

#define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE     4
#define USBD_CUSTOM_HID_REPORT_DESC_SIZE     38

Ну и в файле usbd_customhid.h также устанавливаем значения, соответствующие нашему режиму передачи и приема (а именно передача и прием по 4 байта):

#define CUSTOM_HID_EPIN_ADDR                 0x81
#define CUSTOM_HID_EPIN_SIZE                 4
 
#define CUSTOM_HID_EPOUT_ADDR                0x01
#define CUSTOM_HID_EPOUT_SIZE                4

И вот теперь уже мы можем попробовать собрать проект и запрограммировать микроконтроллер. В результате устройство должно успешно определиться в диспетчере устройств:

Соответственно, значения PID и VID у этого устройства соответствуют тем, которые мы задали в STM32Cube при создании и настройке проекта.

Таким образом, на сегодня мы на этом заканчиваем. Сразу расскажу, чем мы будем заниматься в следующей статье =) Во второй части, посвященной USB Custom HID мы разберемся как принимать и передавать данные, а в третьей части мы напишем программку для ПК, которая собственно и будет осуществлять подключение и обмен данными с нашим устройством. До скорой встречи!

Понравилась статья? Поделись с друзьями!

STM32Cube. USB Custom HID. Создание дескрипторов устройства.: 47 комментариев
  1. Еще б не помешало поподробнее рассказать про дескриптор репорта, как его формировать и какие поля за что отвечают.

  2. USBD_CUSTOM_HID_REPORT_DESC_SIZE, USBD_CUSTOMHID_OUTREPORT_BUF_SIZE я нашел только в файле usbd_conf.h, т.к. в статье не указывалось в каком файле они именно. И после компиляции vid pid почему то поменялись хотя размещение STM32 Custom Human interface
    За статью большое спасибо!

  3. С удовольствием прочитал и повторил все манипуляции с файлами для для платы STM32F4291-DISCO, запрограммировал контроллер, результат нулевой. Устройство совсем не определяется. Может быть на плате тоже есть какая-либо фишка. Подскажите.
    Буду очень благодарен.
    Сергей

    • Если совсем никак не определяется, значит что-то с «железными» настройками не то. Правильные выводы USB используются? На F429 не так как в статье подключено.

  4. Вы оказались правы. На плате свободен только USB_OTG_HS, а я пытался задействовать USB_OTG_FS, потому ничего и не получалось.
    Спасибо.

  5. Использую stm32f4discovery. У Вас написано, что для Вашей платы необходимо подать небольшое напряжение на PC13. Вопрос как его подать? И второй вопрос, для моей платы куда надо подать напряжение. Я поискал в интернете, нашел вот такую схему вроде как моей платы: http://www.micromouseonline.com/wp/wp-content/uploads/2013/05/stm32f4discovery-usb-schematic.png , получается мне на PD5 надо будет подавать напряжение? Так же при компиляции полученного проекта в IAR ругается на добавленный в дискриптор репорта код вот такими обидными словами «Error[Pe146]: too many initializer values»

    • У меня на плате просто линия данных подключена «нестандартным» способом, как на схеме изображено. Поэтому необходимо открыть транзистор и подать напряжение низкого уровня (не «небольшое» (!) ). Это напряжение составляет 0 В. Для Discovery этого делать не требуется.

      Ругается компилятор видимо потому что размер массива меньше, чем количество элементов в массиве.

  6. Скажите пожалуйста. Что нужно добавить в ваш репорт (который выложен в статье) чтобы расширить размер передаваемых данных?

    • За размер отвечает:
      0x95, 4, // REPORT_COUNT (4)
      Ну и, соответственно, как в статье описано размеры буферов итд надо тоже поменять.

  7. Но вот хочу я передать текст «ABCDE» А репорт позволяет передавать в одном байте только 1 или 0.
    Buffer[0] = d; // передаваемый байт
    d может быть 1 или 0. А если я захочу d сделать больше 1 то репорт не передаст его. Я пока не очень разобрался с репортами но вроде как нужно репорт дополнить LOGICAL_MINIMUM и LOGICAL_MAXIMUM.

    • Любые данные можно передавать, от 0 до 255, в пределах размера типа данных uint8_t. В примере в одной из следующих статей по HID так и передается.

  8. я сделал как написано в статье . у меня stm32f417 . можете подсказать где моя ошибка ?
    Буду очень благодарен.

  9. не устанавливается драйвера Custom HID , отказ выдает . десять раз перепробовал , все равно результат нулевой . (сделал все как по статьи). схему посмотрел, нечего лишного не подключен к USB. (вот моя плата http://starterkit.ru/html/index.php?name=shop&op=view&id=68) спасибо что выделили время и ответили на мой вчерашний вопрос. Буду благодарен.

    • Так, ну давай по шагам… Во-первых какие драйвера? Вот в статье нет ни слова о драйверах )

  10. Возникла проблема при компиляции проекта:

    __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
    {
    /* USER CODE BEGIN 0 */
    0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
    0x09, 0x01, // USAGE (Vendor Usage 1)
    // System Parameters
    0xa1, 0x01, // COLLECTION (Application)
    0x85, 0x01, // REPORT_ID (1)
    0x09, 0x01, // USAGE (Vendor Usage 1)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x25, 0x01, // LOGICAL_MAXIMUM (1)
    0x75, 0x08, // REPORT_SIZE (8)
    0x95, 4, // REPORT_COUNT (4)
    0xb1, 0x82, // FEATURE (Data,Var,Abs,Vol)
    0x85, 0x01, // REPORT_ID (1)
    0x09, 0x01, // USAGE (Vendor Usage 1)
    0x91, 0x82, // OUTPUT (Data,Var,Abs,Vol)

    0x85, 0x02, // REPORT_ID (4)
    0x09, 0x02, // USAGE (Vendor Usage 4)
    0x75, 0x08, // REPORT_SIZE (8)
    0x95, 4, // REPORT_COUNT (4)
    0x81, 0x82, // INPUT (Data,Var,Abs,Vol)
    /* USER CODE END 0 */
    0xC0 /* END_COLLECTION */

    };

    ошибка: ..\Src\usbd_custom_hid_if.c(45): error: #146: too many initializer values

    Подскажите пожалуйста. Использую CubeMX 4.10 и Keil 5.16a.

  11. Разобрался, надо было сначала значения поменять #define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE 4
    #define USBD_CUSTOM_HID_REPORT_DESC_SIZE 38.

  12. Разобрался, надо было сначала значения поменять #define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE
    #define USBD_CUSTOM_HID_REPORT_DESC_SIZE

  13. Уважаемый Aveal. Вот такой вопрос.
    Есть 2 платы stm324x9I и stm3220G. На одной реализовал HID Device (просто HID, не Custom HID), на второй Host Supporting ALL Classes. Все понятно, все работает. А вот когда реализую Custom HID, Host не определяет тип HID устройства: Device not supporting HID class.

    Что делать? как это исправит?

    • Если честно я такое подключение не пробовал реализовать… А при подключении к ПК Custom HID нормально работает?

        • Тогда вполне возможно, что Host поддерживает какой-то ограниченный набор HID устройств. И под All classes ST подразумевают все известные устройства, типа мышей, клавиатур итд.

  14. где можно исходники можно посмотреть? не отображается в диспетчере устройств. работаю с coocox. Ошибок не выдает, но и не работает. Спасибо!

  15. Приветствую. Есть пара вопросов по режимам работы USB
    1) В чем разница междцу HID и custom HID? Где какой нужен?
    2) Что такое режим DFU? В сети по нему мало информации

    • Есть много устройств, поддерживающих стандарт HID — клавиатура, мышь и т. д. Custom — свой собственный HID, без привязки к жестким правилам стандарта.
      DFU (Device Firmware Upgrade) — обновление прошивки через USB.

  16. Уважаемый Aveal! Если можно, то прошу дать пару-другую примеров, как через дескриптор репорта задавать размер передаваемых данных, например, 8, 12, 16, 24, 32 и 64 байта, и заодно прошу поподробнее расписать поля этого дескриптора. Буду очень благодарен, если Вы это сделаете…

  17. Здравствуйте, подскажите пжл.
    Если с этим понятно,
    CUSTOM_HID_EPIN_SIZE 4
    CUSTOM_HID_EPOUT_SIZE 4
    Это wMaxPacketSize, хотим гонять маленькие пачки — ставим поменьше, большие — ставим побольне, например 0x40

    То с этой константой USBD_CUSTOMHID_OUTREPORT_BUF_SIZE 4
    всё мутно и в голове каша.
    Везде пишут «определяет размер буфера приема данных из компьютера», или Maximum report buffer size (OUT ENDPOINT).

    И какой его нужно ставить? Если я собираюсь гонять пачки по 64 байта, мне обязательно увеличивать этот буфер, или может там какая то хитрая схема?
    Если не сложно, поделитесь мыслями и направьте на путь истинный))
    Заранее спасибо!

    • Добрый день!
      Надо по коду пройтись и посмотреть, где это буфер используется. Скорее всего его надо будет увеличивать.

  18. Здравствуйте. Создал проект в CubeMX однако в сгенерированном проекте отсутствую файлы usbd_hustomhid.c и usbd_custom_hid_if.c. Подскажите что можно сделать. И будет ли статься про HID на SPL. Спасибо.

    • У Cube есть такая недобрая особенность, что иногда вместе с обновлениями версий меняются названия файлов или переносятся переменные из одних файлов в другие… Нужно по аналогии смотреть — где-то должно быть объявлено, но файл уже может иначе называться.

      По поводу SPL — скорее всего нет, HAL Driver окончательно заменил SPL.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *