Работа с Generic HID Class на платформе Windows PC Печать
Добавил(а) microsin   

В статье описывается реализация USB драйверов PC для Generic HID Class. На самом деле здесь имеется в виду не написание обычного драйвера как такового, а описание способа применения уже готового драйвера USB HID, встроенного в операционную систему Windows, с помощью специальной библиотеки Atmel USB HID DLL (AtUsbHid.dll). Текст статьи - почти дословный перевод даташита Atmel "USB PC Drivers Based on Generic HID Class" (doc7645.pdf).

[Возможности библиотеки Atmel USB HID DLL]

• Поддерживаются все операционные системы Windows, начиная от Windows 98® SE (все более поздние версии Windows также поддерживаются).
• Возможен полнодуплексный обмен данными через USB (Full Duplex Communication).
• Передача команд через конечную точку 0 (EP 0, она же конечная точка управления, Control Endpoint).
• Библиотека DLL (Dynamic Link Library) поддерживается любым известным компилятором: VC++, JAVA, VB и т. д.
• Автодетектирование устройства USB HID для программы на VC++ (Visual Studio).
• Обмен данными типа точка-точка (Point-to-Point Communication).

Описанная в статье Atmel USB HID DLL (AtUsbHid.dll) является по сути простой оберткой над функциями HID.dll от Microsoft, написанной для облегчения их использования. Поэтому AtUsbHid.dll имеет некоторые ограничения по функционалу, но зато позволяет легко и просто писать программы для компьютера, осуществляющие обмен данными с устройством USB HID, а также отслеживать в программе события подключения и отключения устройства. К сожалению, исходный код AtUsbHid.dll недоступен. Если же Вы гуру в Windows API, то возможно Вам лучше использовать библиотеку HID.dll напрямую - в SDK от Microsoft есть примеры использования HID.dll. См. также обзор библиотек для работы с USB HID [2].

[1. Введение]

Этот апноут описывает способ интеграции библиотеки USB HID DLL в Ваше приложение (программу для компьютера, ПО хоста USB). Предоставленные примеры основаны на компиляторах VC++ (имеется в составе Visual Studio), JAVA, однако среда программирования и язык программы имеет мало значения, можно использовать и любые другие компиляторы - VB (Visual Basic), Delphi, LabView, и т. п. Простые примеры кода демонстрируют разные варианты реализации программы. Далее, где в тексте статьи упоминается DLL, то имеется в виду AtUsbHid.dll.

[2. Описание функций, встроенных в AtUsbHid.dll]

Как указано в стандарте USB HID, приложение для класса Generic HID использует два репорта report IN и report OUT для приема и отправки данных соответственно (полезная нагрузка). Длина этих репортов назначается в коде firmware микроконтроллера (эта длина задана в дескрипторах устройства USB HID), и она автоматически определяется библиотекой AtUsbHid.dll в точном соответствии с настройками проекта firmware для Вашего микроконтроллера. Подробности см. в [1], там подробно описана структура firmware, его настройки, и как эти настройки менять.

Имейте в виду, что библиотека DLL, описанная в этой статье, позволяет одновременно вести обмен данными с одним и только одним устройством USB Generic HID. Вы не можете обработать одновременный обмен данными с несколькими устройствами USB HID, используя эту DLL. Это сделано для упрощения использования библиотеки (хендл открытого устройства USB для простоты спрятан в коде библиотеки). Для одновременной работы с несколькими устройствами используйте другие библиотеки [2].

AtUsbHid.dll-functions

Рис. 2-1. Упрощенная структура приложения, построенного на AtUsbHid.dll.

2.1 findHidDevice

Эта функция (возвращает значение типа BOOLEAN, т. е. true или false) позволяет найти в системе Windows подключенное устройство Generic HID по его идентификаторам vendor ID (VID) и product ID (PID). Эта функция также открывает хендл (handle) к этому USB-устройству, если оно подключено.

Входные параметры:
const UINT VendorID: тут указан код vendor ID
const UINT ProductID: тут указан код product ID

Выходное возвращаемое значение:
FALSE: устройство не найдено. Больше информации можно получить, если после возврата false вызвать API функцию Windows GetLastError. TRUE: соединение произведено успешно, и к устройству USB HID открыт хендл.

Вызов GetLastError (в случае возврата FALSE) может вернуть:
ERROR_USB_DEVICE_NOT_FOUND если устройство не найдено в системе.
ERROR_USB_DEVICE_NO_CAPABILITIES если устройство найдено, но нельзя запросить от него описатели возможностей устройства. Обычно это говорит об ошибке в дескрипторах, или о том, что устройство не принадлежит классу USB HID.

2.2 closeDevice

Эта функция прекращает обмен с устройством USB HID и закрывает хендл к нему.

2.3 writeData

Функция (BOOLEAN) отправляет данные в устройство (OUT data). Максимальная длина блока данных, поддерживаемая этой функцией, может быть меньше или равна значению, полученному вызовом функции getOutputReportLength (см. секцию 2.9 этой статьи), т. е. не больше максимального размера пакета устройства USB HID. Если длина данных, которые надо передать, превышает это значение, то пользователь библиотеки (программист) должен отправить данные по частям в нескольких отдельных пакетах. То есть нужно вызвать функцию writeData несколько раз, по отдельности для каждого куска данных (каждый кусок не должен превышать максимального размера пакета устройства USB HID).

Когда отправляемый размер данных меньше максимального размера пакета, то передается все равно блок данных максимального размера, просто в хвост данных дописываются нулевые данные (байты 0x00).

Входной параметр:
UCHAR* buffer: указатель на пакет для отправки (буфер в памяти). Размер буфера должен быть не меньше максимального размера пакета, полученного вызовом функции getOutputReportLength.

Выходное возвращаемое значение:
FALSE: ошибка в передаче данных. Вызов GetLastError() вернет код ERROR_WRITE_FAULT.
TRUE: пакет данных успешно передан от компьютера к устройству USB HID.

2.4 readData

Эта функция (BOOLEAN) читает пакеты данных, отправленные устройством USB HID (пакеты IN data). Чтобы предотвратить потери данных, это функция должна вызваться постоянно (с использованием отдельного потока или таймера).

Входной параметр:
UCHAR* buffer: указатель на буфер в памяти, куда будет сохранен принятый пакет.

Выходное возвращаемое значение:
FALSE: если данные от устройства USB HID пока не поступили. 
TRUE: если принятые данные сохранены в буфере.

2.5 setFeature

Дословно название этой функции можно перевести как "установить фичу", т. е. настроить какую-то возможность. На самом же деле функция setFeature (также возвращает BOOLEAN) позволяет пользователю отправить данные команды для управления устройством USB HID (например, запустить бутлоадер, запустить новую задачу и т. п.). Команды должны расшифровываться внутри firmware устройства USB HID, в соответствии с протоколом, полностью определенным пользователем (программистом). Данные, передаваемые функцией setFeature, передаются через конечную точку 0 (endpoint 0, управляющая конечная точка, она же default endpoint, конечная точка по умолчанию) через запрос SetReport. Подробности, если они Вам нужны, см. в [3]. Имейте в виду, что конечная точка 0 не относится к другим конечным точкам IN и OUT, которые применяются для простого обмена пакетами данных.

Длина данных, которые можно передать вызовом функции setFeature, можно определить через вызов функции getFeatureReportLength (см. секцию 2.11 этой статьи). Длина передаваемых данных не должна превышать длины, которую возвратила getFeatureReportLength.

Входной параметр:
UCHAR* buffer: указатель на пакет для отправки (буфер в памяти).

Выходное возвращаемое значение:
FALSE: ошибка в передаче данных.
TRUE: пакет данных успешно передан от компьютера к устройству USB HID.

2.6 hidRegisterDeviceNotification

Внимание: имейте в виду, что эта функция может использоваться только для проекта VC++. Эта функция оповещает ПО хоста USB (приложение для компьютера) о переподключениях устройства USB.

Входной параметр:
HWND hWnd хендл к окну приложения. Обычно это хендл окна программы, которое видит пользователь. Этому хендлу будут передаваться события оповещения об подключении и извлечении устройства USB.

Выходное возвращаемое значение:
FALSE: вызов функции потерпел ошибку. Чтобы получить дополнительную информацию, вызовите GetLastError.
TRUE: успешный возврат из функции.

2.7 hidUnregisterDeviceNotification

Внимание: имейте в виду, что эта функция может использоваться только для проекта VC++. Эта функция закрывает указанный хендл оповещения о переподключениях USB-устройства (device notification handle).

Входной параметр:
HWND hWnd хендл к окну приложения.

Выходное возвращаемое значение:
FALSE: вызов функции потерпел ошибку. Чтобы получить дополнительную информацию, вызовите GetLastError.
TRUE: успешный возврат из функции.

2.8 isMyDeviceNotification

Внимание: имейте в виду, что эта функция может использоваться только для проекта VC++. Эта функция позволяет проверить, пришло или нет настроенное через hidRegisterDeviceNotification оповещение (подключено или отключено устройство) для используемого устройства USB HID.

Входной параметр:
DWORD dwData, здесь указано значение, которое передается обработчику события OnDeviceChange во втором из параметров.

Выходное возвращаемое значение:
TRUE: если подключенное/отключенное устройство USB HID является используемым.
FALSE: если это другое устройство.

2.9 getOutputReportLength

Эта функция позволяет пользователю библиотеки получить длину OUT report (размер пакета, который отправляется от компьютера PC к устройству USB HID). Эта длина задается в firmware устройства USB HID (описана в дескрипторах).

2.10 getInputReportLength

Эта функция позволяет пользователю библиотеки получить длину IN report (размер пакета, который отправляется от устройства USB HID к компьютеру PC). Эта длина задается в firmware устройства USB HID (описана в дескрипторах).

2.11 getFeatureReportLength

Эта функция позволяет пользователю библиотеки получить длину Feature report (пакет управления Control data packet, который оправляется от компьютера PC к устройству USB). Эта длина задается в firmware устройства USB (описана в дескрипторах).

[3. PC demos - демонстрационные примеры программ ПО хоста USB]

3.1 VC++ demo

Демонстрационная программа VC++ показывает, как загрузить AtUsbHid.dll в проект, и как использовать оповещения о переподключениях устройства USB (plug & play notification).

3.1.1 Загрузка DLL в приложении на Visual C++

Файл заголовка AtUsbHid.h предоставляет макросы, которые помогают загрузить и использовать функции, представленные в Atmel USB HID DLL. Когда Вы разрабатываете приложение, использующее DLL, то Вам нужно сделать следующее (это стандартная практика работы с функциями DLL):

• Создать хендлер (handler) для DLL и сбросить его: HINSTANCE hLib = NULL;
• Загрузить DLL в память, используя API функцию Windows: hLib =LoadLibrary(AT_USB_HID_DLL);
• Проинициализировать указатели на каждую используемую функцию DLL, используя loadFuncPointers(hLib).

Как только все эти шаги выполнены без ошибок, DLL и её функции загружены в Ваше приложение и готовы к работе. Теперь Вы можете вызвать нужные функции с помощью макроса DYNCALL(DllFunction()).

Когда приложение завершает свою работу, то неплохо освободить память, выгрузив DLL с помощью функции API FreeLibrary(hLib). Перед выгрузкой DLL из памяти Вы должны убедиться, что хендл устройства USB предварительно был закрыт (через вызов функции closeDevice).

3.1.2 Использование возможности автоматического подключения/отключения (Automatic Device Connection/Disconnection Feature)

DLL предоставляет функции, которые позволяют пользователю детектировать события подключения и отключения устройства. Чтобы добиться этого, проделайте следующие действия:

• Зарегистрируйте программно Ваше приложение, чтобы оно могло получать оповещения от операционной системы. Это делается следующим образом:
DYNCALL(hidRegisterDeviceNotification)((m_hWnd));
• Добавьте в список событий приложения (Message Map) функцию ON_WM_DEVICECHANGE().
• Создайте функцию с именем OnDeviceChange(UINT nEventType, DWORD dwData) которая будет вызваться при возникновении событий изменения статуса устройства USB.
• В теле функции OnDeviceChange вызывайте DYNCALL(isMyDeviceNotification(dwData)), чтобы определить, изменился ли статус Вашего устройства USB (было ли оно подключено или отключено). Пример, как это делается, см. в модуле UsbHidDemoCodeDlg.cpp.
• Когда приложение завершается, отмените регистрацию оповещений вызовом:
DYNCALL(hidUnregisterDeviceNotification(m_hWnd));

3.1.3 Чтение данных от устройства USB HID - использование readData

Устройство USB HID может постоянно передавать от себя данные (например, клавиатурные нажатия или перемещения курсора мыши). Интересна возможность периодически читать данные с использованием таймера. Это позволит организовать опрос принимаемых данных вызовами функции readData. Чтобы добиться этого, сделайте следующее:

• Добавьте функцию ON_WM_TIMER() в список событий приложения (Message Map).
• Создайте функцию, которая называется OnTimer(UINT nIDEvent). Она будет делать вызовы DYNCALL(readData(sbuffer).
• Теперь Вы можете установить таймер на указанный интервал с использованием функции SetTimer(n,x,y); это нужно для того, чтобы вызывать каждые x миллисекунд функцию readData, когда Ваше устройство USB HID подключено.
• Удалите таймер вызовом KillTimer(n), когда Ваше устройство отключено.

3.1.4 Внешний вид программы (интерфейс пользователя)

На рис. 3-1 приведен скриншот предоставленного демонстрационного приложения. Имейте в виду, что PID по умолчанию относится к специфическому demo (демки Atmel, которые имеют интерфейс Generic HID, могут не иметь одинаковые PID). Поэтому проверьте в приложении параметр PID (и на всякий случай и VID), чтобы он соответствовал тому, который используется в Вашем устройстве USB HID. Установленные VID/PID в устройстве USB можно узнать либо через Диспетчер Устройств Windows, либо через просмотр исходного кода firmware Вашего устройства USB (параметры VID и PID указаны в дескрипторах).

AtUsbHid.dll-USB-HID-VCpp-demo-app

Рис. 3-1. VC++ based demo

Описание компонентов интерфейса GUI программы (рис. 3-1):

• Поля ввода Vendor ID, Product ID используются для того, чтобы указать нужные значения VID/PID для подключения к нужному устройству USB HID.
• Кнопка OK должна быть нажата один раз после того, как корректно установлены значения VID и PID.
• Кнопки LED 1 .. LED 4 управляют портами микроконтроллера, и позволяют переключать состояние светодиодов (зажжен/погашен) на отладочной плате (например, плата Atmel STK526. Вместо неё можно использовать плату AVR-USB162).
• Кнопка Firmware Upgrade позволяет пользователю запустить бутлоадер для обновления firmware через интерфейс USB (подробности см. в даташите на bootloader DFU Atmel).
• Кнопка Exit завершает работу приложения.
• Поле статуса предоставляет информацию о состоянии подключения, а также когда устройство подключено, то выдают длины репортов IN report, OUT report и Feature report (эти параметры автоматически используются DLL для отправки/приема данных).

3.1.5 DOS demo (консольное демонстрационное приложение VC++)

Это демо предоставляет простой пример консольного приложения. В нем используются фиксированные значения VID/PID, поэтому при их изменении требуется перекомпиляция приложения. Перед запуском консольного приложения должно быть подключено устройство с загруженным Generic HID firmware.

AtUsbHid.dll-USB-HID-VCpp-demo-console-app

Рис. 3-2. DOS Interface (консольное приложение, запускаемое в среде cmd.exe)

Внимание: консольное приложение может быть скомпилировано с использованием среды MinGw [4]. Командная строка для компиляции приложения:
mingw32-g++ -O2 -Wall UsbHidSmallDemoCode.cpp -o AtUsbHidMinGw.exe -I.

3.2 JAVA demo

Эта демка показывает, как интегрировать AtUsbHid.dll в проект на языке JAVA. Интерфейс между AtUsbHid.dll и JAVA осуществляется через пакет AtUsbHidJni.jar.

3.2.1 Интегрирование AtUsbHid.dll

Чтобы встроить в приложение JAVA использование AtUsbHid.dll, выполните следующие простые шаги:

• Добавьте следующий код в секцию импорта (import section) Вашего файла JAVA:
import com.atmel.atusbhidjni.AtUsbHidJni
• Создайте новый объект для использования DLL:
AtUsbHidJni usbDevice = new AtUsbHidJni();
• Загрузите DLL:
usbDevice.loadLibraryUsbHid();
• Теперь DLL готова к использованию. Как использовать - см. описание функций AtUsbHid.dll.
• Важно перед выходом из приложения выгрузить DLL:
usbDevice.UnloadloadLibraryUsbHid();
• Чтобы скомпилировать проект, добавьте путь до класса (class path) пакета AtUsbHidJni.jar:
JAVAc userhid.JAVA -classpath AtUsbHidJni.jar

Дополнительную информацию можете получить из HTML-документации, предоставленном в пакете DLL [5].

3.2.2 Внешний вид программы (интерфейс пользователя)

Исходный код GUI-интерфейса программы на JAVA находится в папке JNICodeForHIDDLL. На рис. 3.3 показан скриншот пользовательского интерфейса на JAVA.

AtUsbHid.dll-USB-HID-JAVA-demo-app

Рис. 3.3. Интерфейс пользователя программы, написанной на JAVA

Как можно видеть, программа на JAVA имеет интерфейс полностью аналогичный интерфейсу программы на VC++. Чекбокс Auto-Connect используется, чтобы разрешить приложению автоматически детектировать подключение/отключение устройства.

3.2.3 DOS demo (консольное демонстрационное приложение JAVA)

Это демо дает простой пример консольного приложения. Демо использует фиксированные значения VID/PID, и поэтому требуется перекомпиляция после изменения этих параметров в коде программы. Перед запуском консольного приложения должно быть подключено устройство с загруженным Generic HID firmware.

AtUsbHid.dll-USB-HID-JAVA-demo-console-app

Рис. 3-4. DOS Interface (консольное приложение JAVA)

[4. Структура пакета AtUsbHid.dll]

Когда Вы распакуете архив пакета DLL, то Вы увидите несколько папок, которые имеют следующее назначение:

AtUsbHid - эта папка содержит бинарный файл AtUsbHid.dll и файл заголовка AtUsbHid.h.
ExeDemo - эта папка содержит различные исполняемые демонстрационные приложения.
JNICodeForHIDDLL - здесь находится исходный код проекта на JAVA.
UsbHidDemoCode - здесь находится исходный код проекта на VC++.
UsbHidSmallDemoCode - здесь находится исходный код проекта VC++ small demo (DOS demo, консольное приложение).

[Словарик]

Здесь даны краткие описания некоторых терминов, встречающихся в статье - для упрощения понимания. Подробности и значения других терминов ищите в [3] или в Википедии.

endpoint - конечная точка устройства USB. Обычно это аппаратный буфер, через который устройство USB может автоматически отправить (тогда это IN endpoint) или принять (тогда это OUT endpoint) данные. Устройство USB может иметь несколько конечных точек, параметры которых (размер в частности) описаны в дескрипторах устройства USB. Обычно в устройстве USB 3 конечные точки. Минимально возможное количество конечных точек - одна, поскольку в устройстве USB всегда должна быть и имеется конечная точка 0 (она же control endpoint, она же default endpoint). Эта конечная точка control endpoint всегда имеет номер 0 и очень важна, поскольку через нее хост USB получает через дескрипторы информацию об устройстве USB.
firmware - программное обеспечение, записанное во внутреннюю память микроконтроллера. Например (также относиться к этой статье) программа устройства USB HID, записанная в память микроконтроллера макетной платы AVR-USB162 (микроконтроллер AT90USB162).
handle, хендл - числовой идентификатор какого-либо объекта в операционной системе. Этот идентификатор нужен для работы с объектами Windows - окнами, файлами, устройствами.
report - блок данных, который передается через конечные точки.
VIDPID - два 16-битных числовых идентификатора, которые имеет любое устройство USB. По значению этих идентификаторов программа может подключиться к любому нужному ей устройству USB.

[Ссылки]

1AVR328: стандартная (generic) реализация устройства USB HID (USB Generic HID Implementation).
2Библиотеки для управления устройствами USB HID.
3USB in a NutShell - путеводитель по стандарту USB.
4Welcome to MinGW.org - Home of the MinGW and MSYS Projects.
5. Демонстрационные приложения ПО хоста с исходным кодом, исходный код firmware устройства USB HID, AtUsbHid.dll, документация.