IR + USB HID = очередной пульт для компа (часть 2)
Напоминаю про наличие первой части статьи. В этой части мы разберем на составляющие USB-HID устройство и подготовимся к написанию минимального кода, который по нажатию кнопок на пульте рулит громкостью и проигрывателем.
Поехали…
Разговор про USB длинный, будет несколько частей.
USB HID, немного теории
Ранее я уже описывал создание USB устройств на примере виртуального COM-порта. Глобальных отличий будет немного.
Итак, что у нас самое страшное в USB-устройстве? Правильно, дескрипторы.
Что такое дескрипторы? Когда мы втыкаем что-то в разъем USB, комп должен получить хоть какую-то информацию об устройстве — что это, чем кормить, чего от него ожидать. Определить, какой же драйвер устройству подсунуть. Всем этим заправляют дескрипторы — небольшие массивы данных жестко организованной структуры. Подробности о них — читать в стандартах USB.
Иерархию дескрипторов HID-устройства можно представить в виде дерева:
Устройство может содержать несколько конфигураций, конфигурация может содержать несколько интерфейсов, интерфейс может содержать несколько endpoints (конечные точки, эндпоинты) и несколько HID-дескрипторов. Эндпоинт — это минимальный канал, через который может передаваться информация. Можно наконфигурировать что угодно, но если мы хотим работать без драйверов (вернее с драйверами, входящими с стандартную поставку Windows), то мы должны соответствовать стандарту на устройство, в данном случае HID — Human Interface Device.
Разберем наш дескриптор:
Структуры дескрипторов жестко фиксированы стандартом, поэтому вкурив их один раз уже будешь знать все.
Report descriptor
Все HID устройства похожи друг на друга, но тем не менее их разнообразие пугает. У мыши три кнопки и она ездит, а у клавиатуры 104 и она не ездит. Но и то и то HID-устройства. Как же их причесать?
Вот этой гребенкой и является Report descriptor — описание пакета передаваемой и принимаемой информации. Я сейчас разберу уже составленный мной дескриптор, а о том, как самому создать свой — позже.
Итак, вернемся к нашему дескриптору. Забегая вперед, скажу, что он соответствует такому пакету:
Но тем не менее, поехали разбирать репорт. Все числа взялись из стандарта, но на них пока не обращаем внимание. Главное — логическая структура.
Все устройства HID привязаны к «страницам» — разделам стандарта. Клавиатура, например, страница 0x07. Наша страница 0x0c — это пользовательские устройства. Там собраны все мультимедиа-управляющие функции.
Коллекция — это способ сгруппировать используемые данные. В пакете должна быть всегда предопределенная коллекция — Application. Usage перед коллекцией показывает, что наше устройство является пользовательским устройством общего типа. От этого зависит, какой из стандартных HID-драйверов будет подцеплен к нашему устройству
Внутри коллекции еще раз указываем, к какой странице принадлежит наше устройство и перечисляем все пункты, которые мы хотим задействовать. Полный список пунктов — в стандарте, номера пунктов — оттуда же.
вот этот кусок указывает, что надо включить в пакет один элемент (REPORT_COUNT) длиной 8 бит (REPORT_SIZE), который может принимать значения от 1 (LOGICAL_MINIMUM) до 6 (LOGICAL_MAXIMUM). При этом все Usages выстраиваются в массив (Ary). То есть, в первом байте пакета мы передаем код нажатой кнопки числом от 1 до 6. Если нам потребуется расширять функционал, то мы допишем нужные Usages, увеличим LOGICAL_MAXIMUM.
Вот это кусок, по аналогии с предыдущим, описывает еще два пустых байта. В них мы пока будем передавать сырой код клавиши из пульта (тот, что возвращает нам функция IR_decodeNEC()). Он как раз два байта.
Поскольку этот код для драйвера HID не важен, то мы ему никакого Usage и не прицепляем.
Конец коллекции. В принципе без комментариев.
Два неиспользуемых байта позволят нам подсмотреть коды кнопок пультов через 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 и соберем-таки наше первое приложение.
STM32 и USB-HID — это просто
На дворе 2014 год, а для связи микроконтроллеров с ПК самым популярным средством является обычный последовательный порт. С ним легко начать работать, он до примитивности прост в понимании — просто поток байт.
Однако все современные стандарты исключили COM порт из состава ПК и приходится использовать USB-UART переходники, чтобы получить доступ к своему проекту на МК. Не всегда он есть под рукой. Не всегда такой переходник работает стабильно из-за проблем с драйверами. Есть и другие недостатки.
Но каждый раз, когда заходит разговор о том, применять USB или последовательный порт, находится множество поклонников логической простоты UART. И у них есть на то основания. Однако, хорошо ведь иметь альтернативу?
Меня давно просили рассказать как организовать пакетный обмен данными между ПК и МК на примере STM32F103. Я дам готовый рабочий проект и расскажу как его адаптировать для своих нужд. А уж вы сами решите — нужно оно вам или нет.
Выбор профиля HID
USB-HID — довольно обширный класс устройств, поэтому прежде всего придется выбрать какое именно устройство мы будем создавать.
Мы можем создать эмуляцию клавиатуры, мыши, джойстика и других устройств ввода, а можем создать свое устройство, чтобы не зависеть от довольно жестких рамок стандарта и свободно обмениваться данными с ПК.
Я расскажу как cделать Custom HID device. Это дает максимальную свободу. Чтобы не затягивать статью, постараюсь рассказать максимально кратко — описаний стандарта в сети и без меня много, но лично мне они слабо помогли, когда понадобилось решить конкретную задачу.
Структура проекта
Инициализация USB
В функции Set_System() производится настройка пина подтяжки линии D+ к питанию для программного подключения/отключения устройства от ПК (в нашей плате не используется), настраивается прерывание и инициализируются светодиоды и кнопки для демонстрационного проекта.
В USB_Interrupts_Config() настраиваются прерывания в зависимости от семейства МК (поддерживаются F10x, F37x, L1x).
Функция USB_Init() запускает работу USB модуля. Если временно нужно отключить для отладки работу с USB, просто закомментируйте эту строку.
Далее в бесконечном цикле проверяется, удалось ли сконфигурировать USB модуль при подключении к ПК. Если все сработало верно и устройство успешно подключилось, ПК включен и не находится в режиме энергосбережения, то состояние будет CONFIGURED.
Далее проверяется, была ли закончена предыдущая передача данных в ПК и если да, то готовится к отправке новый пакет в функции RHIDCheckState()
Размер пакета и частота передачи
В комментариях все довольно прозрачно. Обратите внимание на DEVICE_VER_L, DEVICE_VER_H — это константы из usb_desc.h, которые вы можете изменить для идентификации версии своего устройства.
Здесь стоит обратить внимание на константу wMaxPacketSize — она определяет максимальный размер пакета, которым мы будем обмениваться с ПК. Проект так настроен, чтобы при ее изменении менялись и размеры буферов. Но не забывайте, что больше 0x40 по стандарту указывать не стоит. С этой константой будьте осторожны — если передаваемый пакет будет отличаться по размеру — будут проблемы!
Следующая за ним константа с комментарием bInterval — это период опроса устройства в миллисекундах. Для нашего устройства задано 32мс.
Это самый важный дескриптор — он описывает протокол обмена и функционал устройства. Его формирование — не самая простая задача. Если допустить ошибку при формировании дескриптора — устройство перестанет работать. Формат дескриптора очень жесткий. Есть даже специальная утилита HID Descriptor tool. А в корне проекта лежит файл «RHID.hid» с описанным выше дескриптором для редактирования в этой утилите. Но если вы не понимаете, что делаете, лучше не лезть.
Для простоты я сделал две константы:
RPT3_COUNT — размер OUTPUT буфера в байтах для передачи пакета в МК (в примере — 1 байт)
RPT4_COUNT — размер INPUT буфера в байтах для передачи пакета в ПК (в примере — 4 байта)
Размер любого из этих буферов не должен превышать wMaxPacketSize. Меньше — можно.
Кстати, превратить Custom HID в другой HID девайс, например, клавиатуру или джойстик можно фактически только переписав ReportDescriptor и изменив класс и подкласс устройства в дескрипторе конфигурации.
Что такое Report
Цикл обмена
Массив uint8_t Buffer[RPT4_COUNT+1] определен как размер полезных данных входящего (рассматривается всегда с точки зрения хоста) пакета + байт ID. Это важно — если размер буфера будет отличаться — будут проблемы. Поэтому для изменения размеров буфера редактируйте значение константы в usb_desc.h.
В функции мы собираем данные в пакет, устанавливаем флаг PrevXferComplete = 0, говорящий о том, что данные отправляются и вызываем функциии библиотеки USB_SIL_Write и SetEPTxValid для отправки данных хосту.
Все, на этом передача данных хосту закончена.
С приемом данных немного сложнее — есть два способа послать данные девайсу — один из них заключается в использовании описанных в дескрипторе репорта возможностей устройства (Features), с соответствующими параметрами посредством функции SET_FEAUTRE. Это некоторая абстракция, для красивого управления устройством с кучей функций, чтобы можно было вызывать осмысленные функции, а не просто слать поток байт.
Второй способ — это работа с устройством как с файлом — просто записываем в него пакет как в файл. Этот метод называется SET_REPORT. На деле работает чуть-чуть медленнее.
Наше устройство поддерживает оба метода, о чем мы и сказали хосту в дескрипторе репортов.
Обработка SET_FEATURE
Данные, отправленные методом SET_FEAUTRE обрабатываются в usb_prop.c
Здесь мы проверяем первый байт в репорте и в соответствии с ним обрабатываем остаток пакета — управляем светодиодами или просто берем байт, отправленный нам хостом и кладем в пакет для последующей отправки обратно в функции RHIDCheckState.
Под Report_Buf зарезервировано wMaxPacketSize байт, чтобы влез любой пакет, который нам отправит хост.
Данные, отправленные методом SET_REPORT обрабатываются в usb_endp.c
Здесь почти то же самое, только нужно самостоятельно забрать данные вызовом USB_SIL_Read(EP1_OUT, Receive_Buffer) и в конце сообщить, что мы закончили вызовом SetEPRxStatus(ENDP1, EP_RX_VALID);
Настраивать устройство, передавать и принимать данные в пакетах нужного размера с нужной нам периодичностью мы научились.
Собираем проект и прошиваем в устройство.
Работать, это будет примерно так:
Проект поддерживает взаимодействие с утилитой USB HID Demonstrator от ST Microelectronics.
Страница Device capabilities отображает возможности, описанные в Report Descriptor.
Input/Output transfer позволяет вручную поотправлять данные девайсу и посмотреть пакет, который от него приходит.
Graphic view позволяет управлять светодиодами, чекбоксами Led 1, Led 2, настроив соответствующий им Report ID, а также передавать байт ползунком (ReportID=3)
Также я написал маленькую демо-софтинку, которая автоматически определяет подключение к компу и отключение нашего девайса по его VID и PID, отображает статус — подключено/отключено индикатором рядом с чекбоксом Auto Connect
Радиокнока Send using позволяет выбрать метод отправки данных девайсу.
Report: отображает полученный от девайса пакет побайтно, начиная с ReportID.
Щелкая по светодиодам ниже — управляем светодиодами девайса. Их состояние отображает текущее состояние девайса. Считывается из репорта от девайса.
Перемещая ползунок, мы отправляем Report с и значением, соответствующим позиции ползунка. Девайс вернет это значение в 4 байте репорта.
В выпадающем комбобоксе отображаются HID девайсы, найденные в системе и если найден наш девайс, то отображается его название.
Скачать все, что необходимо, можно на GitHub. В составе:
DT — HID Descriptor tool
tstHID-STM32F103 — проект для EmBlocks
USB HID Demonstrator — утилита от ST Microelectronics
HIDSTM32.exe — моя демо-софтинка на Delphi аналогичного фукнционала, но не требующая настройки
Если остались вопросы — пишите в комментариях. Постараюсь ответить. Я постарался не утопить суть в куче мелочей, чтобы сложилось общее понимание. Остальное уже можно понять, изучая проект. Но если вам нужно быстро сделать свое устройство, а лезть в дебри некогда — все, что вам нужно, я описал.
USB на регистрах: interrupt endpoint на примере HID
Продолжаем разбираться с USB на контроллерах STM32L151. Как и в предыдущей части, ничего платформо-зависимого здесь не будет, зато будет USB-зависимое. Если точнее, будем рассматривать третий тип конечной точки — interrupt. И делать мы это будем на примере составного устройства «клавиатура + планшет» (ссылка на исходники).
На всякий случай предупреждаю: данная статья (как и все остальные) — скорее конспект того, что я понял, разбираясь в этой теме. Многие вещи так и остались «магией» и я буду благодарен если найдется специалист, способный объяснить их.
Первым делом напомню, что протокол HID (Human Interface Device) не предназначен для обмена большими массивами данных. Весь обмен строится на двух понятиях: событие и состояние. Событие это разовая посылка, возникающая в ответ на внешнее или внутреннее воздействие. Например, пользователь кнопочку нажал или мышь передвинул. Или на одной клавиатуре отключил NumLock, после чего хост вынужден и второй послать соответствующую команду, чтобы она это исправила, также послав сигнал нажатия NumLock и включила его обратно отобразила это на индикаторе. Для оповещения о событиях и используются interrupt точки. Состояние же это какая-то характеристика, которая не меняется просто так. Ну, скажем, температура. Или настройка уровня громкости. То есть что-то, посредством чего хост управляет поведением устройства. Необходимость в этом возникает редко, поэтому и взаимодействие самое примитивное — через ep0.
Таким образом назначение у interrupt точки такое же как у прерывания в контроллере — быстро сообщить о редком событии. Вот только USB — штука хост-центричная, так что устройство не имеет права начинать передачу самостоятельно. Чтобы это обойти, разработчики USB придумали костыль: хост периодически посылает запросы на чтение всех interrupt точек. Периодичность запроса настраивается последним параметром в EndpointDescriptor’е (это часть ConfigurationDescriptor’а). В прошлых частях мы уже видели там поле bInterval, но его значение игнорировалось. Теперь ему наконец-то нашлось применение. Значение имеет размер 1 байт и задается в миллисекундах, так что опрашивать нас будут с интервалом от 1 мс до 2,55 секунд. Для низкоскоростных устройств минимальный интервал составляет 10 мс. Наличие костыля с опросом interrupt точек для нас означает, что даже в отсутствие обмена они будут впустую тратить полосу пропускания шины.
Логичный вывод: interrupt точки предназначены только для IN транзакций. В частности, они используются для передачи событий от клавиатуры или мыши, для оповещения об изменении служебных линий COM-порта, для синхронизации аудиопотока и тому подобных вещей. Но для всего этого придется добавлять другие типы точек. Поэтому, чтобы не усложнять пример, ограничимся реализацией HID-устройства. Вообще-то, такое устройство мы уже делали в первой части, но там дополнительные точки не использовались вовсе, да и структура HID-протокола рассмотрена не была.
ConfigurationDescriptor
Внимательный читатель тут же может обратить внимание на описания конечных точек. Со второй все в порядке — IN точка (раз произведено сложение с 0x80) типа interrupt, заданы размер и интервал. А вот первая вроде бы объявлена как OUT, но в то же время interrupt, что противоречит сказанному ранее. Да и здравому смыслу тоже: хост не нуждается в костылях чтобы передать в устройство что угодно и когда угодно. Но таким способом обходятся другие грабли: тип конечной точки в STM32 устанавливается не для одной точки, а только для пары IN/OUT, так что не получится задать 0x81-й точке тип interrupt, а 0x01-й control. Впрочем, для хоста это проблемой не является, он бы, наверное, и в bulk точку те же данные посылал… что, впрочем, я проверять не стану.
HID descriptor
Структура HID descriptor’а больше всего похожа на конфигурационных файл «имя=значение», но в отличие от него, «имя» представляет собой числовую константу из списка USB-специфичных, а «значение» — либо тоже константу, либо переменную размером от 0 до 3 байт.
Важно: для некоторых «имен» длина «значения» задается в 2 младших битах поля «имени». Например, возьмем LOGICAL_MINIMUM (минимальное значение, которое данная переменная может принимать в штатном режиме). Код этой константы равен 0x14. Соответственно, если «значения» нет (вроде бы такого не бывает, но утверждать не буду — зачем-то же этот случай ввели), то в дескрипторе будет единственное число 0x14. Если «значение» равно 1 (один байт) то записано будет 0x15, 0x01. Для двухбайтного значения 0x1234 будет записано 0x16, 0x34, 0x12 — значение записывается от младшего к старшему. Ну и до кучи число 0x123456 будет 0x17, 0x56, 0x34, 0x12.
Естественно, запоминать все эти числовые константы мне лень, поэтому воспользуемся макросами. К сожалению, я так и не нашел способа заставить их самостоятельно определять размер переданного значения и разворачиваться в 1, 2, 3 или 4 байта. Поэтому пришлось сделать костыль: макрос без суффикса отвечает за самые распространенные 8-битные значения, с суффиксом 16 за 16-битные, а с 24 — за 24-битные. Также были написаны макросы для «составных» значений вроде диапазона LOGICAL_MINMAX24(min, max), которые разворачиваются в 4, 6 или 8 байтов.
Как и в конфигурационных файлах, здесь присутствуют «секции», называемые страницами (usage_page), которые группируют устройства по назначению. Скажем, есть страница с базовой периферией вроде клавиатур, мышек и просто кнопок, есть джойстики и геймпады (искренне рекомендую посмотреть какие именно! Там и для танков, и для космических кораблей, и для подводных лодок и для всего чего угодно), даже дисплеи есть. Правда, где искать софт, умеющий со всем этим работать, я без понятия.
Внутри каждой страницы выбирается конкретное устройство. Например, для мышки это указатель и кнопки, а для планшета — стилус или палец юзера (что?!). Ими же обозначаются составные части устройства. Так, частью указателя являются его координаты по X и Y. Некоторые характеристики можно сгруппировать в «коллекцию», но для чего это делается я толком не понял. В документации к полям иногда ставится пометка из пары букв о назначении поля и способе работы с ним:
CA | Collection(application) | Служебная информация, никакой переменной не соответствующая |
CL | Collection(logical) | -/- |
CP | Collection(phisical) | -/- |
DV | Dynamic Value | входное или выходное значение (переменная) |
MC | Momentary Control | флаг состояния (1-флаг взведен, 0-сброшен) |
OSC | One Shot Control | однократное событие. Обрабатывается только переход 0->1 |
Есть, разумеется, и другие, но в моем примере они не используются. Если, например, поле X помечено как DV, то оно считается переменной ненулевой длины и будет включено в структуру репорта. Поля MC или OSC также включаются в репорт, но имеют размер 1 бит.
Один репорт (пакет данных, посылаемый или принимаемый устройством) содержит значения всех описанных в нем переменных. Описание кнопки говорит о всего одном занимаемом бите, но для относительных координат (насколько передвинулась мышка, например) требуется как минимум байт, а для абсолютных (как для тачскрина) уже нужно минимум 2 байта. Плюс к этому, многие элементы управления имеют еще свои физические ограничения. Например, АЦП того же тачскрина может иметь разрешение всего 10 бит, то есть выдавать значения от 0 до 1023, которое хосту придется масштабировать к полному разрешению экрана. Поэтому в дескрипторе помимо предназначения каждого поля задается еще диапазон его допустимых значений (LOGICAL_MINMAX), плюс иногда диапазон физических значений (в миллиматрах там, или в градусах) и обязательно представление в репорте. Представление задается двумя числами: размер одной переменной (а битах) и их количество. Например, координаты касания тачскрина в создаваемом нами устройстве задаются так:
Здесь видно, что объявлены две переменные, изменяющиеся в диапазоне от 0 до 10000 и занимающие в репорте два участка по 16 бит.
Последнее поле говорит, что вышеописанные переменные будут хостом читаться (IN) и поясняется как именно. Описывать его флаги подробно я не буду, остановлюсь только на нескольких. Флаг HID_ABS показывает, что значение абсолютное, то есть никакая предыстория на него не влияет. Альтернативное ему значение HID_REL показывает что значение является смещением относительно предыдущего. Флаг HID_VAR говорит, что каждое поле отвечает за свою переменную. Альтернативное значение HID_ARR говорит, что передаваться будут не состояния всех кнопок из списка, а только номера активных. Этот флаг применим только к однобитным полям. Вместо того, чтобы передавать 101/102 состояния всех кнопок клавиатуры можно ограничиться несколькими байтами со списком нажатых клавиш. Тогда первый параметр REPORT_FMT будет отвечать за размер номера, а второй — за количество.
Поскольку размер всех переменных задается в битах, логично спросить: а что же с кнопками, ведь их количество может быть не кратно 8, а это приведет к трудностям выравнивания при чтении и записи. Можно было бы выделить каждой кнопке по байту, но тогда бы сильно вырос объем репорта, что для скоростных передач вроде interrupt, неприятно. Вместо этого кнопки стараются расположить поближе друг к другу, а оставшееся место заполняют битами с флагом HID_CONST.
Теперь мы можем если не написать дескриптор с нуля, то хотя бы попытаться его читать, то есть определить, каким битам соответствует то или иное поле. Достаточно посчитать INPUT_HID’ы и соответствующие им REPORT_FMT’ы. Только учтите, что именно такие макросы придумал я, больше их никто не использует. В чужих дескрипторах придется искать input, report_size, report_count, а то и вовсе числовые константы.
Вот теперь можно привести дескриптор целиком:
Помимо рассмотренных ранее полей тут есть и такое интересное поле, как REPORT_ID. Поскольку, как понятно из комментариев, устройство у нас составное, хосту надо как-то определить, чьи данные он принимает. Для этого данное поле и нужно.
И еще одно поле, на которое хотелось бы обратить внимание — OUTPUT_HID. Как видно из названия, оно отвечает не за прием репорта (IN), а за передачу (OUT). Расположено оно в разделе клавиатуры и описывает индикаторы CapsLock, NumLock, ScrollLock а также два экзотических — Compose (флаг ввода некоторых символов, для которых нет собственных кнопок вроде á, µ или ) и Kana (ввод иероглифов). Собственно, ради этого поля мы и заводили OUT точку. В ее обработчике будем проверять не надо ли зажечь индикаторы CapsLock и NumLock: на плате как раз два диодика и разведено.
Существует и третье поле, связанное с обменом данными — FEATURE_HID, мы его использовали в первом примере. Если INPUT и OUTPUT предназначены для передачи событий, то FEATURE — состояния, которое можно как читать, так и писать. Правда, делается это не через выделенные endpoint’ы, а через обычную ep0 путем соответствующих запросов.
Если внимательно рассмотреть дескриптор, можно восстановить структуру репорта. Точнее, двух репортов:
Отправлять их будем по нажатию кнопок на плате, причем. поскольку пишем мы всего лишь пример реализации, а не законченное устройство, делать это будем по-варварски — посылая два репорта, в первом из которых «нажимая» клавиши, а во втором — «отпуская». Причем с огромной «тупой» задержкой между посылками. Если не посылать репорт с «отпущенными» клавишами, система посчитает что клавиша осталась нажатой и будет ее повторять. Естественно, ни о какой эффективности тут не идет и речи, о безопасности тоже, но для теста сойдет. Ах да, куда ж без очередных граблей! Размер структуры должен совпадать с тем, что описано в дескрипторе, иначе винда сделает вид, что не понимает чего от нее хотят. Как обычно, линукс подобные ошибки игнорирует и работает как ни в чем не бывало.
В процессе тестирования наткнулся на забавный побочный эффект: в Windows7 при нажатии на «тачскрин» вылезает окошко рукописного ввода. Я об этой фиче не знал.
Если к вам попало готовое устройство
… и хочется посмотреть на него изнутри. Первым делом, естественно, смотрим, можно даже от обычного пользователя, ConfigurationDescriptor:
Для HID-дескриптора же я не нашел (да и не искал) способа лучше, чем от рута:
Для полноты картины сюда стоило бы добавить как смотреть подобные вещи в других ОС. Но у меня соответствующих знаний нет, может в комментариях подскажут. Желательно, конечно, без установки стороннего софта.
Заключение
Вот, собственно, и все, что я нарыл по HID. План-минимум — научиться читать готовые дескрипторы, эмулировать несколько устройств одновременно и реализовать планшетный ввод — выполнен. Ну и философию interrupt точек рассмотрели заодно.
Как и в плошлый раз, немножко документации оставил в репозитории на случай если дизайнеры USB-IF снова решат испортить сайт.