Доброго дня всем посетителям и читателям нашего сайта! Наконец-то, после длительного летнего перерыва, возобновляется работа над новыми статьями ) Тем накопилось очень много, темы абсолютно разные, так что в ближайшее время, надеюсь каждый найдет что-то интересное для себя. И сегодня мы начнем обсуждать реализацию класса USB HID для наших любимых микроконтроллеров STM32. Почему начнем? Просто я решил, что для одной статьи тут будет многовато всего, поэтому их будет две или три, так что давайте приступать.
Итак, по старой доброй традиции разберемся с инструментарием... Я буду использовать отладочную плату с микроконтроллером STM32F103VET6, а для создания проекта мы задействуем набирающий все большую популярность STM32CubeMx.
Собственно, запускаем, создаем новый проект (на этих шагах я не останавливаюсь, все уже разобрано в мини-цикле статей, посвященных этой теме на нашем сайте - их можно найти в рубрике STM32CubeMx). Включаем только то, что нам понадобится для решения непосредственно сегодняшней задачи, ничего лишнего:
Кроме того, на моей плате есть вот такая фишка:
Поэтому для того, чтобы подключение по USB в принципе могло работать мне надо подать на PC13 напряжение низкого уровня, чтобы открыть транзистор.
В настройках тактирования нам необходимо обеспечить 48 МГц для работы USB. Тут ничего сложного, просто подбираем нужные значения умножителей и делителей частоты и не забываем активировать внешний кварц, который мы уже подключили в предыдущем окне:
Переходим на вкладку Configuration, но там все оставляем в первозданном виде. Разве что можно зайти в настройки USB_Device и посмотреть значения PID и VID, они могут нам пригодиться впоследствии для того, чтобы проверить, действительно ли наше устройство успешно подключилось к ПК. Но не будем забегать вперед, а просто запомним или запишем эти значения. По умолчанию они такие:
- PID - 22352 (0x5750)
- VID - 1155 (0x483)
USB дескрипторы устройства.
Работу в CubeMx на этом заканчиваем, генерируем файлы проекта и переходим в IAR (ну или Keil, например, у каждого своя IDE). Мы можем прошить полученную программу в микроконтроллер, но, к сожалению, это нам ничего не даст - устройство определится в системе, но работать не будет, а в диспетчере устройств мы увидим ошибку - "Сбой запроса дескриптора устройства". И эта ситуация как ни парадоксально абсолютно правильна и логична. Дело в том, что дескриптор для нашего Custom HID устройства мы должны создать самостоятельно и добавить в наш сгенерированный проект. Этим мы сейчас и займемся.
Всего есть три USB дескриптора, которые отвечают за подключение к ПК:
- дескриптор устройства
- дескриптор конфигурации
- дескриптор репорта
Дескриптор устройства описан в файле 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_customhid.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).
С двумя из упомянутых USB дескрипторов разобрались, теперь настала очередь третьего. Он определен в файле 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 (2) 0x09, 0x02, // USAGE (Vendor Usage 2) 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 у этого устройства соответствуют тем, которые мы задали в STM32CubeMx при создании и настройке проекта.
Таким образом, на сегодня мы на этом заканчиваем. Сразу расскажу, чем мы будем заниматься в следующей статье. Во второй части, посвященной USB Custom HID мы разберемся как принимать и передавать данные, а в третьей части мы напишем программку для ПК, которая собственно и будет осуществлять подключение и обмен данными с нашим устройством. До скорой встречи!