IR + USB HID = очередной пульт для компа (часть 2)


Напоминаю про наличие первой части статьи. В этой части мы разберем на составляющие USB-HID устройство и подготовимся к написанию минимального кода, который по нажатию кнопок на пульте рулит громкостью и проигрывателем.
Поехали…


Разговор про USB длинный, будет несколько частей.

USB HID, немного теории

Ранее я уже описывал создание USB устройств на примере виртуального COM-порта. Глобальных отличий будет немного.
Итак, что у нас самое страшное в USB-устройстве? Правильно, дескрипторы.
Что такое дескрипторы? Когда мы втыкаем что-то в разъем USB, комп должен получить хоть какую-то информацию об устройстве — что это, чем кормить, чего от него ожидать. Определить, какой же драйвер устройству подсунуть. Всем этим заправляют дескрипторы — небольшие массивы данных жестко организованной структуры. Подробности о них — читать в стандартах USB.

Иерархию дескрипторов HID-устройства можно представить в виде дерева:

Устройство может содержать несколько конфигураций, конфигурация может содержать несколько интерфейсов, интерфейс может содержать несколько endpoints (конечные точки, эндпоинты) и несколько HID-дескрипторов. Эндпоинт — это минимальный канал, через который может передаваться информация. Можно наконфигурировать что угодно, но если мы хотим работать без драйверов (вернее с драйверами, входящими с стандартную поставку Windows), то мы должны соответствовать стандарту на устройство, в данном случае HID — Human Interface Device.

Разберем наш дескриптор:

// эти константы вынесены в заголовок для удобства переконфигурирования
#define USB_DEVICE_DESCRIPTOR_TYPE              0x01
#define USB_CONFIGURATION_DESCRIPTOR_TYPE       0x02
#define USB_STRING_DESCRIPTOR_TYPE              0x03
#define USB_INTERFACE_DESCRIPTOR_TYPE           0x04
#define USB_ENDPOINT_DESCRIPTOR_TYPE            0x05

#define HID_DESCRIPTOR_TYPE                     0x21
#define HID_REPORT_DESCRIPTOR_TYPE              0x22

#define My_SIZ_HID_DESC                   0x09
#define My_OFF_HID_DESC                   0x12

#define My_SIZ_DEVICE_DESC                18
#define MY_SIZ_CONFIG_DESC                34
#define My_SIZ_REPORT_DESC                39
#define My_SIZ_STRING_LANGID              4
#define My_SIZ_STRING_VENDOR              38
#define My_SIZ_STRING_PRODUCT             30
#define My_SIZ_STRING_SERIAL              26

/* USB Standard Device Descriptor */
const uint8_t My_DeviceDescriptor[My_SIZ_DEVICE_DESC] =
  {
    My_SIZ_DEVICE_DESC,         // общая длина дескриптора устройства в байтах
    USB_DEVICE_DESCRIPTOR_TYPE, // bDescriptorType - показывает, что это за дескриптор. В данном случае - Device descriptor
    0x00, 0x02,                 // bcdUSB - какую версию стандарта USB поддерживает устройство. 2.0

	// класс, подкласс устройства и протокол, по стандарту USB. У нас нули, означает каждый интерфейс сам за себя
    0x00,                       //bDeviceClass
    0x00,                       //bDeviceSubClass
    0x00,                       //bDeviceProtocol

    0x40,                       //bMaxPacketSize - максимальный размер пакетов для Endpoint 0 (при конфигурировании)
	
	// те самые пресловутые VID и PID,  по которым и определяется, что же это за устройство. В данном примере взяты от балды
	// а в реальных устройствах надо покупать VID, чтобы устройства можно было различать и подсовывать нужные драйвера
    0x83, 0x04,                 //idVendor (0x0483)
    0x11, 0x57,                 //idProduct (0x5711)
    
    0x00, 0x02,                 // bcdDevice rel. 2.00  номер релиза устройства
    
	// дальше идут индексы строк, описывающих производителя, устройство и серийный номер. 
	// Отображаются в свойствах устройства в диспетчере устройств
	// А по серийному номеру подключенные устройства с одинаковым VID/PID различаются системой. 
    1,                          //Index of string descriptor describing manufacturer 
    2,                          //Index of string descriptor describing product
    3,                          //Index of string descriptor describing the device serial number 
    0x01                        // bNumConfigurations - количество возможных конфигураций. У нас одна.
  };


/* USB Configuration Descriptor */
const uint8_t My_ConfigDescriptor[My_SIZ_CONFIG_DESC] =
  {
    0x09, 			// bLength: длина дескриптора конфигурации
    USB_CONFIGURATION_DESCRIPTOR_TYPE, // bDescriptorType: тип дескриптора - конфигурация
    My_SIZ_CONFIG_DESC, 0x00, // wTotalLength: общий размер всего дерева под данной конфигурацией в байтах
    
    0x01,         // bNumInterfaces: в конфигурации всего один интерфейс
    0x01,         // bConfigurationValue: индекс данной конфигурации
    0x00,         // iConfiguration: индекс строки, которая описывает эту конфигурацию
    0xE0,         // bmAttributes: признак того, что устройство будет питаться от шины USB
    0x32,         // MaxPower 100 mA: и ему хватит 100 мА

		/************** Дескриптор интерфейса ****************/
		0x09,         // bLength: размер дескриптора интерфейса
		USB_INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType: тип дескриптора - интерфейс
		0x00,         // bInterfaceNumber: порядковый номер интерфейса - 0
		0x00,         // bAlternateSetting: признак альтернативного интерфейса, у нас не используется
		0x01,         // bNumEndpoints - количество эндпоинтов. В данном примере один - на передачу от устроства в комп
		
		0x03,         // bInterfaceClass: класс интерфеса - HID
		// если бы мы косили под стандартное устройство, например клавиатуру или мышь, то надо было бы указать правильно класс и подкласс
		// а так у нас общее HID-устройство
		0x00,         // bInterfaceSubClass : подкласс интерфейса.
		0x00,         // nInterfaceProtocol : протокол интерфейса
		
		0,            // iInterface: индекс строки, описывающей интерфейс
		
			// теперь отдельный дескриптор для уточнения того, что данный интерфейс - это HID устройство
			/******************** HID дескриптор ********************/
			0x09,         // bLength: длина HID-дескриптора
			HID_DESCRIPTOR_TYPE, // bDescriptorType: тип дескриптора - HID
			0x01, 0x01,   // bcdHID: номер версии HID 1.1
			0x00,         // bCountryCode: код страны (если нужен)
			0x01,         // bNumDescriptors: Сколько дальше будет report дескрипторов
				HID_REPORT_DESCRIPTOR_TYPE,         // bDescriptorType: Тип дескриптора - report
				My_SIZ_REPORT_DESC,	0x00, // wItemLength: длина report-дескриптора
				
			
			/******************** дескриптор конечных точек (endpoints) ********************/
			0x07,          // bLength: длина дескриптора
			USB_ENDPOINT_DESCRIPTOR_TYPE, // тип дескриптора - endpoints

			0x81,          // bEndpointAddress: адрес конечной точки и направление 1(IN)
			0x03,          // bmAttributes: тип конечной точки - Interrupt endpoint
			0x04, 0x00,    // wMaxPacketSize: 4 Byte max 
			0x20,          // bInterval: Polling Interval (32 ms)
  }; 
  
//Это разберем чуть позже
const uint8_t My_ReportDescriptor[My_SIZ_REPORT_DESC] =
  {
		    0x05, 0x0c,                    // USAGE_PAGE (Consumer Devices)
		    0x09, 0x01,                    // USAGE (Consumer Control)
		    0xa1, 0x01,                    // COLLECTION (Application)
		    0x05, 0x0c,                    //   USAGE_PAGE (Consumer Devices)
		    0x09, 0xe2,                    //   USAGE (Mute)
		    0x09, 0xe9,                    //   USAGE (Volume Up)
		    0x09, 0xea,                    //   USAGE (Volume Down)
		    0x09, 0xb0,                    //   USAGE (Play)
		    0x09, 0xb1,                    //   USAGE (Pause)
		    0x09, 0xb7,                    //   USAGE (Stop)
		    0x15, 0x01,                    //   LOGICAL_MINIMUM (1)
		    0x25, 0x06,                    //   LOGICAL_MAXIMUM (6)
		    0x75, 0x08,                    //   REPORT_SIZE (8)
		    0x95, 0x01,                    //   REPORT_COUNT (1)
		    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
		    0x95, 0x01,                    //   REPORT_COUNT (1)
		    0x75, 0x10,                    //   REPORT_SIZE (16)
		    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
		    0xc0                           // END_COLLECTION


Структуры дескрипторов жестко фиксированы стандартом, поэтому вкурив их один раз уже будешь знать все.

Report descriptor

Все HID устройства похожи друг на друга, но тем не менее их разнообразие пугает. У мыши три кнопки и она ездит, а у клавиатуры 104 и она не ездит. Но и то и то HID-устройства. Как же их причесать?

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

Итак, вернемся к нашему дескриптору. Забегая вперед, скажу, что он соответствует такому пакету:

Номер байта     Содержимое
------------------------------------------
0x00            Номер нажатой кнопки (1-6)
0x01            Зарезервирован
0x02            Зарезервирован


Номера кнопок такие:
  1. Mute
  2. Volume up
  3. Volume down
  4. Play
  5. Pause
  6. Stop
Длина пакета — три байта. Полезной нагрузки — три бита ))

Но тем не менее, поехали разбирать репорт. Все числа взялись из стандарта, но на них пока не обращаем внимание. Главное — логическая структура.

0x05, 0x0c,                    // USAGE_PAGE (Consumer Devices)

Все устройства HID привязаны к «страницам» — разделам стандарта. Клавиатура, например, страница 0x07. Наша страница 0x0c — это пользовательские устройства. Там собраны все мультимедиа-управляющие функции.

0x09, 0x01,                    // USAGE (Consumer Control)
0xa1, 0x01,                    // COLLECTION (Application)

Коллекция — это способ сгруппировать используемые данные. В пакете должна быть всегда предопределенная коллекция — Application. Usage перед коллекцией показывает, что наше устройство является пользовательским устройством общего типа. От этого зависит, какой из стандартных HID-драйверов будет подцеплен к нашему устройству

0x05, 0x0c,                    //   USAGE_PAGE (Consumer Devices)
0x09, 0xe2,                    //   USAGE (Mute)
0x09, 0xe9,                    //   USAGE (Volume Up)
0x09, 0xea,                    //   USAGE (Volume Down)
0x09, 0xb0,                    //   USAGE (Play)
0x09, 0xb1,                    //   USAGE (Pause)
0x09, 0xb7,                    //   USAGE (Stop)

Внутри коллекции еще раз указываем, к какой странице принадлежит наше устройство и перечисляем все пункты, которые мы хотим задействовать. Полный список пунктов — в стандарте, номера пунктов — оттуда же.

0x15, 0x01,                    //   LOGICAL_MINIMUM (1)
0x25, 0x06,                    //   LOGICAL_MAXIMUM (6)
0x75, 0x08,                    //   REPORT_SIZE (8)
0x95, 0x01,                    //   REPORT_COUNT (1)
0x81, 0x00,                    //   INPUT (Data,Ary,Abs)

вот этот кусок указывает, что надо включить в пакет один элемент (REPORT_COUNT) длиной 8 бит (REPORT_SIZE), который может принимать значения от 1 (LOGICAL_MINIMUM) до 6 (LOGICAL_MAXIMUM). При этом все Usages выстраиваются в массив (Ary). То есть, в первом байте пакета мы передаем код нажатой кнопки числом от 1 до 6. Если нам потребуется расширять функционал, то мы допишем нужные Usages, увеличим LOGICAL_MAXIMUM.

0x95, 0x01,                    //   REPORT_COUNT (1)
0x75, 0x10,                    //   REPORT_SIZE (16)
0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)

Вот это кусок, по аналогии с предыдущим, описывает еще два пустых байта. В них мы пока будем передавать сырой код клавиши из пульта (тот, что возвращает нам функция IR_decodeNEC()). Он как раз два байта.
Поскольку этот код для драйвера HID не важен, то мы ему никакого Usage и не прицепляем.

0xc0                           // END_COLLECTION

Конец коллекции. В принципе без комментариев.

Два неиспользуемых байта позволят нам подсмотреть коды кнопок пультов через USB Monitor — программку, перехватывающую общение устройства с компом.

Вот так выглядит ее лог:

000034: Bulk or Interrupt Transfer (UP), 24.11.2012 22:41:42.266 +0.224. Status: 0x00000000
Pipe Handle: 0x871e08b4 (Endpoint Address: 0x81)
Get 0x3 bytes from the device
01 F0 FF
000036: Bulk or Interrupt Transfer (UP), 24.11.2012 22:41:42.298 +0.032. Status: 0x00000000
Pipe Handle: 0x871e08b4 (Endpoint Address: 0x81)
Get 0x3 bytes from the device
00 00 00
000038: Bulk or Interrupt Transfer (UP), 24.11.2012 22:41:49.401 +7.103. Status: 0x00000000
Pipe Handle: 0x871e08b4 (Endpoint Address: 0x81)
Get 0x3 bytes from the device
00 F3 FF
000040: Bulk or Interrupt Transfer (UP), 24.11.2012 22:41:49.465 +0.064. Status: 0x00000000

Жирным выделены коды кнопок пульта, декодированные и переданные по USB.

В следующей части мы пробежимся по файликам библиотеки USB и соберем-таки наше первое приложение.

  • +8
  • 25 ноября 2012, 01:59
  • steel_ne

Комментарии (11)

RSS свернуть / развернуть
Как всегда, отличный пост, спасибо! Только вот в комментариях наверное должно быть не «REPORT_COUNT (2) и REPORT_SIZE (8)», а " REPORT_COUNT (1)
REPORT_SIZE (10) "?
0
Ога, только REPORT_SIZE(16). Один репорт длиной два байта. Я в программе поменял, а она комменты не обновила. Про программу будет дальше, в практической части.
0
с мультимедиа кнопками понятно. а вот как кнопки sleep|power эмулировать не нашли?
0
  • avatar
  • xar
  • 25 ноября 2012, 20:21
Та вроде и не терял )
вот report для кнопки sleep:

		    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
		    0x09, 0x80,                    // USAGE (System Control)
		    0xa1, 0x01,                    // COLLECTION (Application)
		    0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
		    0x09, 0x82,                    //   USAGE (System Sleep)
		    0x15, 0x01,                    //   LOGICAL_MINIMUM (1)
		    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
		    0x75, 0x08,                    //   REPORT_SIZE (8)
		    0x95, 0x01,                    //   REPORT_COUNT (1)
		    0xc0                           // END_COLLECTION


Эти кнопки просто находятся на другой странице (0x01 — Generic Desktop). Попробовал — комп нормально засыпает. А есть какие-то грабли?
0
ну тут типа табличка *sarcasmic*
хотел спросить почему не добавлено в управление с пульта. удобно же перед сном принудительно отправить спать и комп.
ну и у себя делал mouseMode для пульта, чтоб мышой управлять. но вот тут грабли. не смог нормально клик реализовать.
0
ну и дескриптор клавы тоже неплохо бы бы забить. не все приложения адекватно реагируют на мультимедийные, а хоткеи никто еще не отменял.
0
Понятно, что громкостью все не ограничится, надо же в статье сложность постепенно наращивать. Через пару частей создадим огромный репорт дескриптор с шахматами и библиотекаршами
0
а я нихера не понял
-1
Как перейти на следующий (предыдущий) топик?
0
Никаких специальных средств нет. Если автор не вписал ссылку/оглавление в статью, то придется листать его личный блог и искать требуемое.
0
Зависит ли число коллекций от количества используемых конечных точек? Вообще коллекция описывает все устройство целиком или структуру принимаемых(передаваемых) данных только одного эндпоинта? В каких случаях при описании дескриптора используется несколько коллекций?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.