Работа с USB HID устройствами в Windows
П. ВЫСОЧАНСКИЙ, г. Рыбница, Приднестровье, Молдавия
В связи с постепенным исчезновением СОМ- и LPT-портов из современных компьютеров радиолюбителям приходится искать альтернативные интерфейсы для связи разрабатываемых ими микроконтроллерных устройств с компьютером. Обычно выбирают порт USB, поскольку он присутствует в каждом современном компьютере. Этому способствует наличие недорогих и доступных микроконтроллеров со встроенным модулем USB. A при достаточной производительности и свободных ресурсах можно реализовать работу USB программными средствами микроконтроллера.
Разработка драйвера USB-устройст-ва для компьютера не каждому под силу, так как это требует немалых знаний и специального набора компьютерных программ. Выходом из данной ситуации может быть создание USB-уст-ройств класса HID (Human Interface Device — устройство взаимодействия человека с компьютером), драйверы для которых имеются в операционной системе.
К НЮ относятся, в частности, USB-кла-виатуры, мыши, джойстики и другие средства ручного ввода информации. Тип устройства должен быть задан в специальной логической структуре, называемой дескриптором сообщения. Спецификацией НЮ предусмотрен тип "нестандартное устройство", которым можно пользоваться для обмена произвольной информацией. Это позволяет создавать различные конструкции с интерфейсом USB — термометры, программаторы, вольтметры, которые компьютер воспринимает как НЮ, хотя фактически они таковыми не являются. Для работы с НЮ в операционных системах семейства Windows предусмотрены так называемые API-функции, находящиеся в системных библиотеках "hid.dir, "setupapi.dll", "kernel32.dll" и некоторых других. Эти функции позволяют не только работать с НЮ, но и узнавать некоторые параметры его кон-Г.турации, которые требуются, напри-
мер, для определения оптимального объема буферов приема и передачи.
Чтобы упростить работу с НЮ, был разработан компонент приложения — динамическая библиотека подпрограмм (DLL), названная "HID_Lib_Plus". Она создана в среде PureBasic 4.41 на основе исходного текста разработанной мной ранее библиотеки пользовательских функций "HIDJJb". Собственно, поэтому ей и было дано такое имя. В
отличие от "HID_Lib", которая предназначена исключительно для PureBasic, "HID_Lib_Plus" можно использовать в любой среде программирования, поддерживающей вызовы функций из динамических библиотек, например в Visual Studio или Delphi.
Библиотека HID_Lib_Plus содержит 16 функций, рассматриваемых ниже.
Функция HID DeviceTest(PID, VID, VersionNumber, Index) определяет, подключено ли к компьютеру устройство с требуемыми идентификаторами. Их задают в первых трех аргументах. Эти идентификаторы можно увидеть в окне свойств устройства "Диспетчер устройств" Windows. В приведенном на рис. 1 примере подключенное устройство имеет VID — АВ80, РЮ — F4EF, VersionNumber — 2. Учтите, все эти значения — шестнадцатеричные числа. Аргументом Index указывают требуемое устройство, когда к компьютеру их подключено несколько с одинаковыми иден-
тификаторами. Все аргументы данной функции занимают два байта памяти компьютера. Результат выполнения функции будет равен 1, если устройство подключено и нормально работает, или О в противном случае.
Функция HI DO pen Device (PID, VID, VersionNumber, Index) открывает доступ к устройству перед началом работы с ним. Назначение ее аргументов и их размерность такие же, как и у функции HID_DeviceTest. При успешном получении доступа (устройство должно быть доступно для записи и чтения) функция возвращает целое число, так называемый handle — идентификатор устройства, назначенный операционной системой. Он необходим для работы большинства других функций библиотеки, имеющих одноименный аргумент. Такой подход позволяет одновременно работать с несколькими НЮ.
Функция HID CloseDevice(Handle) завершает работу с НЮ и освобождает все ресурсы компьютера, использовавшиеся при работе с ним. Ее параметр — идентификатор устройства, полученный ранее с помощью функции HID_OpenDevice. В случае успешного выполнения заданной операции будет возвращено число, не равное нулю.
Функция HID_ReadDevice(Handle, "buffer, Len) читает информацию из НЮ, точнее, из его конечной точки типа INPUT. Она помещает полученную информацию в буфер, созданный в оперативной памяти компьютера. Указатель на начало буфера нужно передать этой функции во втором аргументе. Третий аргумент — объем буфера в байтах. Его значение должно быть на единицу больше заданного в дескрипторе сообщения устройства. Если это условие не соблюдено, произойдет ошибка. Функция возвращает число принятых байтов. Как правило, оно равно заданному объему буфера, но может отличаться от него в случае ошибки приема.
Функция HID_WriteDevice(Handle, *buffer, Len) передает информацию устройству через конечную точку типа OUTPUT из указанного вторым аргументом буфера. В остальном логика ее работы аналогична функции HID_Read Device.
Особенность функций HID_ReadDevice и HID_WriteDevice заключается в том, что они не возвращают управление вызвавшей их программе до завершения чтения или записи информации. Это приводит, например, к тому, что при чтении программа "зависает", пока от НЮ не поступит весь ожидаемый информационный пакет. Поэтому для чтения и обработки информации рекомендуется создать в программе отдельный поток.
Функции GetlnputReport(Handle, *buffer, Len) и НID_SetOutputReport (Handle, *buffer, Len) — альтернатива рассмотренным выше функциям НЮ_ ReadDevice и HID_WriteDevice. Они не ожидают завершения чтения или записи, но доступны только в операционных системах, начиная с Windows XR При успешном выполнении функция возвращает число, не равное нулю (как правило, 1).
Функции HID_GetFeature(Handle, *buffer, Len) и HID_SetFeature(Handle, *buffer, Len) выполняют операции соответственно чтения и записи информации, если HID для обмена информацией использует FEATURE-сообщения, передаваемые через нулевую конечную точку. Эти функции ожидают завершения приема и передачи, но, благодаря специальному механизму обмена, зависания программы при приеме не происходит. При успешном выполнении функции возвращают число, не равное нулю (как правило, 1).
Функция HID_GetCaps(Handle, *Ca-pabilities.HIDP_CAPS) позволяет получить некоторые сведения о конфигурации НЮ. Они будут помещены в стандартную структуру HIDP_CAPS, указатель на которую должен быть передан во втором аргументе. В табл. 1 приведено описание структуры на языке C++. На интернет-сайте www.microsoft.com можно найти подробные сведения о ней.
Наибольший интерес в этой структуре представляют члены InputReportByte Length, OutputReportByteLength и FeatureReportByteLength. Они позволят правильно рассчитать размеры буферов приема и передачи информации.
Функция HID_GetNumlnputBuffers (Handle) позволяет узнать размер буфера, в котором временно хранится информация, отправленная устройством, но еще не прочитанная программой.
Функции HID GetManufacturerString (Handle), HID_GetProductString(Handle) и HID_GetSerialNumberString(Handle) позволяют получить сведения соответственно о разработчике устройства, его названии и серийном номере. Каждая из них возвращает указатель на строку содержащего запрошенную информацию текста в кодах ASCII или Unicode (в зависимости от версии использованной библиотеки "HID_Lib_Plus").
Функция HID_Devicelnfo(*Struct. HID_Devicelnfo) помещает в структуру HID_Devicelnfo, указатель на которую должен быть передан в качестве аргумента, список всех НЮ, подключенных к компьютеру и их конфигурацию. Описание структуры HID_Devlcelnfo на языке PureBasic приведено в табл. 2. Переменные с префиксом w имеют тип Word и
занимают по два байта памяти компьютера, а переменные с префиксом s — строкового типа. В приложении к статье можно найти утилиту "HIDJnfo", основанную на использовании данной функции.
В качестве примера рассмотрим работу с несложным USB HID, схема которого показана на рис. 2, собранном на микроконтролле-
ре ATmega8-16PL Программа микроконтроллера была разработана в среде BASCOM-AVR версии 1.11.9.8 с использованием USB-драйвера swusb.LBX. В программную память микроконтроллера следует загрузить файл Demo_ Device.HEX, а конфигурацию установить в соответствии с рис. 3.
С этим устройством работает компьютерная программа, названная Demo_Device, несколько модификаций которой можно найти в приложении к статье. Окно этой программы показано на рис. 4. Нажатиями на имеющиеся в нем экранные кнопки можно включать и выключать светодиод HL1 (см. рис. 2). Контролируется и состояние кнопки SB1 (0 — не нажата, 1 — нажата).
Исходный текст этой программы на языке PureBasic приведен в табл. 3. Константами #USB_PID и #USB_VID заданы идентификаторы устройства. По ним его будет опознавать операционная система компьютера Далее
объявлены две глобальные (доступные в процедурах) переменные RJHandle и WJHandle, предназначенные для хранения идентификаторов, присвоенных системой устройствам чтения и записи.
Прежде всего, функция Open Library пытается открыть библиотеку HID_Lib_Plus.dll. Если сделать это не удается, на экран выводится сообщение об этом и работа программы завершается. При успешном открытии библиотеки имеющиеся в ней функции объявляются с помощью операторов Prototype и вызовов функции GetFunction.
По таймеру каждые 400 мс вызывается процедура FindDevice_Timer, а при каждом ее вызове с помощью функции HID_DeviceTest определяется, подключено ли устройство НЮ к компьютеру. Если подключение зафиксировано, программа с помощью функции HID_OpenDevice получает два идентификатора этого устройства — для чтения и для записи.
Необходимость в отдельных идентификаторах связана с тем, что функция чтения HID_ReadDevice после вызова ждет поступления информации от устройства. Наличие разных идентификаторов позволяет выполнять запись информации, не дожидаясь завершения чтения.
При отключении HID от компьютера выполняются вызовы функции HID_Close Device, освобождающие все ресурсы компьютера, использовавшиеся для работы с рассматриваемым устройством.
Процедура ReadDevice_Thread, работая в отдельном потоке, читает информации из устройства. В начале работы она создает локальный массив переменных байтового типа (на что указывает префикс Ь). Этот массив — буфер для хранения принятой информации. Прием выполняется в бесконечном цикле Repeat—ForEver.
Перед чтением информации в нулевой элемент массива-буфера заносится равный нулю идентификатор сообщения. После этого вызывается функция HID_ReadDevice, которой передается указатель на нулевой элемент массива Эта функция ожидает поступления информации от устройств и, как только она получена, завершает работу, оставляя в буфере принятую информацию, которую и отображает в окне программы функция SetGadgetText.
Процедура SendDevice отправляет информацию устройству. Буфером передачи служит массив OutBuffer. В его нулевой элемент также заносится идентификатор сообщения, равный нулю, а в первый элемент помещается предназначенный для передачи байт из переменной Led. Функция HID_WriteDevice передает информацию из буфера в устройство.
В основной части программы создаются главное окно программы (функцией OpenWindow) и элементы ее пользовательского интерфейса. После этого активируется таймер, который каждые 400 мс в отдельном потоке, созданном с помощью функции CreateThread, запускает процедуру ReadDevice_Thread.
В цикле Repeat—Until располагается обработчик событий программы. Идентификатор текущего события он определяет с помощью функции Wait WindowEvent. Обрабатываются три типа событий:
— срабатывание таймера (вызывается процедура FindDevice_Timer);
— нажатие на экранную кнопку (вызывается процедура SendDevice, передающая информацию HID);
— закрывание окна (выполняется заданное в операторе Until условие выхода из цикла, что прерывает цикл и завершает работу программы).
Библиотека "HID_Lib_Plus" была скомпилирована в четырех вариантах: для 32- и 64-разрядных операционных систем Windows и с кодированием текста ASCII и Unicode Работа библиотек проверена в операционных системах Windows 98 SE (только HID_Lib_Plus.dll), Windows XP и Windows 7. При выборе версии библиотеки следует исходить из типа создаваемого приложения. Например, если требуется создать Unicode-приложение для 64-разрядной операционной системы, следует использовать библиотеку, хранящуюся в файле HID_l_ib_Plus_Unicode_x64.dll.
В папке "Примеры" приложения к статье есть несколько программ с исходными текстами на языке PureBasic, которые демонстрируют основные возможности рассматриваемой библиотеки. Это уже упомянутая программа Demo_Device, разновидность которой есть также на языках Delphi 7 и VB.NET Программа Demo_Device_lnfo отображает сведения о демонстрационном устройстве, используя для этого функции HID_GetCaps, HID_GetManufacturer String, HID_GetProductString и HID_ GetSerialNumberString.
Программа HIDJnfo отображает сведения обо всех USB HID устройствах, подключенных к компьютеру. Для этого она использует функцию HID_Devicelnfo.
Программа PicKit2_Test демонстрирует работу с программатором PICkit 2 фирмы Microchip, который также для компьютера представляет собой HID. После ее запуска на секунду включится светодиод "Target" программатора. Затем он будет выключен, а программа завершит работу, не создавая никаких окон.
Программа USBJHID предназначена для работы с устройством, описанным в статье С. Сурова "Обмен информацией с USB HID устройством" ("Радио", 2010, № 3, с. 25—28). При его повторении не удалось приобрести микроконтроллер ATmega88, поэтому пришлось немного модифицировать программу микроконтроллера, чтобы она работала на имеющемся в наличии ATmega8. Загрузочный файл модифицированной программы USB_HID_ATmega8.hex имеется в приложении. Внесенные изменения не затронули работу USB и протокол обмена информацией, поэтому программа USBJHID будет работать как с оригинальной версией устройства на ATmega88, так и с модифицированной на ATmega8.
От редакции. Файлы-приложения к статье находятся на нашем FTP-сервере по адресу <ftp://ftp.radio.ru/pub/2011/04/HID_Lib_Plus.zip>.