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. Создание дескрипторов устройства.: 54 комментария
  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.

  19. Большое спасибо за статью. НЕ подскажете где почитать описание протокола USB (на русском либо англ), чтобы понять назначение многочисленных значений параметров допустим функции — CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] ??

    И может подскажете что в языке Си обозначает это __ALIGN_BEGIN перед типом значения этой функции?

    Спасибо. С этого сайта началось мое знакомство с STM32))

    • Добрый день!
      align вообще используется для выравнивания данных, для оптимизации.

      А по поводу USB… Так даже и не скажу, должны быть по Custom HID какие-то описания протокола не для STM32, а в целом, надо поискать на англ.

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

  21. Приветствую. Подскажите, что может быть не так. Использовал SW4STM32.
    Сделал все как описано, определяется. HID REPORT использовал из своего проекта на PIC18
    Пытаюсь слать данные
    (CreateFile, WriteFile), но внутри static int8_t CUSTOM_HID_OutEvent_FS (uint8_t event_idx, uint8_t state) ничего не происходит, также пробовал HID Terminal от MikroC, также никакой реакции, иногда происходит зависание.

  22. Уважаемый Aveal, спасибо за статью. Очень информативно и доступно.
    Не доводилось ли реализовывать какой-либо кастомный класс USB? STM в своей документации поясняет, что это возможно: http://www.st.com/content/ccc/resource/technical/document/user_manual/cf/38/e5/b5/dd/1d/4c/09/DM00108129.pdf/files/DM00108129.pdf/jcr:content/translations/en.DM00108129.pdf
    Но как-то вскользь. Пытаюсь реализовать кастомный класс на STM32F407. Пока есть затруднения, как отредактировать шаблон.

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

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