Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Работа с устройством USB HID в ОС Windows через Windows API

Введение

В прошлой статье я рассказывал как сделать устройство USB HID на микроконтроллере stm32. Но, как вы понимаете, сделать устройство — это только половина дела, вторая половина — как с этим устройством потом работать. Сегодняшняя статья как раз будет посвящена тому, как работать с устройством USB HID из операционной системы Windows с использованием Windows API. Кстати, наиболее полное и толковое описание работы с USB (не только с классом HID) можно найти в книге от Jan Axelson — «USB Complete». Это вообще очень мощная тётка, шикарно пишет (у неё есть книги не только про USB), жаль только нет переводов этих книг с английского на русский (по крайней мере я не нашёл), но и так тоже пойдёт.

В качестве подопытного устройства для тестов я буду использовать как раз USB HID устройство на микроконтроллере stm32 из статьи, ссылка на которую дана выше.

Приведённый в статье пример написан на С++ Builder 10 (из Embarcadero RAD Studio). Для работы с исходниками, на компьютере помимо RAD Studio должен быть установлен Windows DDK (у меня установлен Windows Driver Development Kit Version 7.1.0). Вот ссылка на статью о том, как «подружить» C++ Builder и Windows DDK.

Подключение к устройству USB HID

Так же, как и в случае с COM-портом, файлом и вообще любым другим объектом Windows, — подключение к объекту означает получение указателя (хэндла) на этот объект (возвращается при подключении функцией CreateFile). В свою очередь для получения такого указателя — нужно в функции подключения указать путь к объекту. Образно говоря этот путь является просто какой-то уникальной строкой, однозначно идентифицирующей объект в системе.

В ОС Windows при подключении устройства в систему, операционка выясняет что это за устройство, к какому оно относится классу и какой для него использовать драйвер. После этого драйвер класса или устройства может зарегистрировать для этого устройства один или несколько классов интерфейса. В дальнейшем подключение выполняется к какому либо из этих интерфейсов. То есть, грубо говоря, у устройства может быть несколько разных интерфейсов, подключаться к устройству можно через любой из них и это будут разные подключения, поэтому вместо термина «подключение к устройству» правильнее, насколько я понимаю, говорить о подключении к тому или иному интерфейсу устройства.

Каждый класс интерфейса имеет свой GUID, — уникальное 128-битное значение, разбитое на 5 групп шестнадцатиричных символов, типа вот такого: {50127DC3-0F36-415E-A6CC-4CB3BE910B65}. Информация обо всех когда-либо подключенных устройствах, использующих тот или иной класс интерфейса хранится в системном реестре. GUID-ы зарегистрированных классов интерфейсов для Windows 7 можно найти в ветке
HKEY_LOCL_MACHINE/Microsoft/Windows NT/CurrentVersion/DeviceDisplayObjects/InterfaceInformation,
а список всех когда либо подключенных устройств, использовавших эти интерфейсы — в ветке
HKEY_LOCL_MACHINE/SYSTEM/CurrentControlSet/Control/DeviceClasses.
Для Windows 10 список предопределённых классов интерфейсов устройств можно найти в официальной документации Microsoft.

Специальной API-функцией, вызванной с определёнными флагами, можно получить набор информации обо всех используемых в настоящий момент интерфейсах определённого класса. Вообще, обязательно изучите какие флаги используются при вызове тех или иных приведённых ниже API-функций, — там много интересного.

Таким образом, чтобы найти в системе наше устройства (его интерфейс) и получить заветный путь к нему — нужно пройти небольшой квест:

  • получить GUID интерфейса класса HID
  • получить набор информации об используемых в настоящий момент интерфейсах класса HID
  • перебрать в полученном наборе все интерфейсы и выбрать из них тот, который используется нашим устройством
  • получить путь для подключения к интерфейсу нужного устройства

Выполнить этот квест помогут API-функции, описанные в таблице ниже (в среднем столбце указано из какой библиотеки соответствующая функция вызывается):

имя API-функции библиотека предназначение
HidD_GetHidGuid hid.dll получает GUID интерфейса класса HID
SetupDiGetClassDevs setupapi.dll получает список интерфейсов с указанным GUID и набор информации о каждом из них (грубо говоря кусок реестра со списком нужных интерфейсов и информацией о каждом из них)
SetupDiDestroyDeviceInfoList setupapi.dll освобождает ресурсы, используемые функцией SetupDiGetClassDevs
SetupDiEnumDeviceInterfaces setupapi.dll получает из полученного ранее набора информации об интерфейсах информацию об одном конкретном интерфейсе
SetupDiGetDeviceInterfaceDetail setupapi.dll получает подробную информацию о контретном интерфейсе, включая путь для подключения к нему

Теперь несколько слов о том, как опознать в списке «своё» устройство. Самый простой метод идентификации — по VID/PID, альтернативный вариант — используя элемент Usages (возможности). Usages состоит из двух полей, которые вместе описыавают назначение данных в репортах HID-устройства. Первое поле называется Usage, второе — Usage Page (Page ID). Эти поля могут также использоваться для идентификации функционального назначения устройства (мышь, Джостик, геймпад…) На usb.org лежит pdf-документ, в котором описаны стандартные значения Usage и Usage Page. Дополнительно можно проверять задаваемые при конфигурировании строковые дескрипторы Manufacturer и Product, скажем, комбинируя их с Usages.

Кстати, в созданном нами ранее HID-устройстве на stm32 (ссылка на которое приводится в начале статьи) мы сами не создавали дескрипторы репортов и не прописывали Usages, они были прописаны где-то на уровене middleware. Если бы мы хотели сделать это самостоятельно, то нужно было бы создавать HID-устройство на основе класса Custom.

Учтите, что в системе может быть несколько HID-устройств с одинаковыми Usages. Например, подключив к системе две мышки, мы будем видеть на экране движение каждой из них. Это потому что системе пофиг от какой мыши получать информацию, она получает её от всех устройств, у которых в Usages указано что это мышь. С другой стороны ничего не мешает нам также подключить к системе два устройства с одинаковыми VID/PID, так что… В общем мы будем использовать идентификацию устройства по VID/PID.

Кстати, мы в приведённой в качестве примера программе сможем увидеть USB мышь и клавиатуру, но не сможем к ним подключиться, поскольку к этим устройствам операционка подключается в режиме монопольного доступа и откажет в таком доступе функции CreateFile. Но это так, к слову.

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

имя API-функции предназначение
HidD_GetPreparsedData возвращает указатель на буфер, содержащий информацию о репортах HID-устройства
HidD_FreePreparsedData освобождает ресурсы, выделенные функцией HidD_GetPreparsedData
HidD_GetCaps получает структуру HID_CAPS, содержащую информацию о возможностях устройства, — поддерживаемых устройством типах репортов и типах информации в них (Usage, UsagePage, InputReportByteLength, OutputReportByteLength, FeatureReportByteLength… Размеры репортов указываются с учётом Report ID, то есть на единицу больше длины пользовательской информации в них). Эту структуру формирует парсер HID, который вообще-то может не суметь итерпретировать все полученные дескрипторы.
HidD_GetPhisicalDescriptor1 извлекает дескриптор устройства (сырой, без парсинга)
HidD_GetAttributes возвращает указатель на структуру, содержащую VID, PID и Release Number
HidD_GetManufacturerString1 возвращает строку Manufacturer
HidD_GetProductString1 возвращает строку Product
HidD_GetSerialNumberString1 возвращает строку SerialNumber
1 — только для ОС старше Win98

Обмен данными с устройством USB HID

Для обмена данными с устройствами USB HID могут использоваться следующие API-функции:

имя API-функции предназначение
HidD_GetFeature Читает из устройства Feature-репорт, используя управляющую передачу.
HidD_SetFeature Отправляет устройству Feature-репорт, используя управляющую передачу.
HidD_GetInputReport1 Читает Input-репорт, используя управляющую передачу. Данные читаются в обход буфера, прочитанного драйвером HID
HidD_SetOutputReport1 Отправляяет устройству Output-репорт, используя управляющую передачу.
ReadFile1 Читает Input-репорт, используя передачу типа прерывание. Данные на самом деле читаются из буфера, заполняемого драйвером HID, который с заданным периодом опрашивает устройство. Если буфер пуст, функция просто ждёт, пока в него придут данные.
WriteFile1 Отправляет Output-репорт, используя, если возможно, передачу типа прерывание, в противном случае используется управляющая передача.

Ниже показана структурная схема взаимодействия ПО хоста (в том числе API-функций) и HID-устройства:

Схема

структурная схема взаимодействия ПО хоста и HID-устройства

[свернуть]

Пример программы для работы с устройством USB HID

В исходники добавлена библиотека hid-bcb.lib, — это переделанная для использования в RAD Studio библиотека hid.lib из состава от Microsoft. О проблемах использования библиотек от Microsoft в продуктах Borland и методах их решения я писал вот здесь.

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