Программирование простейших USB-устройств на Delphi. Книга: Интерфейс USB (практика использования и программирования) Что такое HID-устройство

Но ведь мало только физически подсоединить устройство к компьютеру, нужно еще и наладить обмен данными между ними. Как же выбрать порт и организовать подключение? Несколько лет назад стандартным решением было использование COM-порта. Кстати, до сих пор различные специалисты доустанавливают на промышленные компьютеры по 8, по 16, а то и по 32 COM-порта (есть целая категория различных PCI-плат расширения последовательных портов, контроллеров и т. д.). Таким образом, если нужно подключить несколько внешних устройств с интерфейсом RS-232, могут потребоваться дорогие адаптеры и экзотические платы расширения, которые по старой традиции неделями плывут в Россию на пароходах. Кстати, название обычного переходника «адаптер DB9m/DB25f» у менеджера компьютерного магазина может вызвать разве что раздражение.

Что такое HID-устройство

Сейчас практически все устройства подключаются к компьютеру через USB-интерфейс. Поэтому во многих новых ПК COM-порт отсутствует вообще.

USB-интерфейс - типовое решение по сопряжению нового внешнего устройства с компьютером, точнее, это HID-интерфейс, базирующийся на протоколе USB 1.1.

Хотя многие и считают, что HID-интерфейс (Human Interface Device) предназначен исключительно для клавиатуры, мыши и джойстика, он годится для множества решений, связанных с сопряжением внешних устройств и компьютера.

Если пользователю необходимо производить низкоскоростной обмен данными (до 64 кбит/c) и при этом желательно сократить время на утомительной разработке собственных драйверов, то ему вполне подойдет HID. На выходе же получится простое и вполне современное решение на базе стандартного программного USB-интерфейса с гарантированной поддержкой на всех распространенных программных платформах.

Свойства HID-устройства

С точки зрения организации программной поддержки HID-устройства, все выглядит достаточно привлекательно: для работы под управлением Windows можно быстро создавать понятный компактный код на базе готовых проверенных алгоритмов. При этом у разработчика останется масса времени на реализацию собственного протокола обмена данными верхнего уровня, поскольку необходимый уровень абстрагирования уже организован за счет HID-протокола (см. таблицу). Кроме того, программисту легко проводить отладку написанного протокола обмена (разумеется, при наличии работающего HID-устройства) - благодаря относительной жесткости самого протокола достаточно просто разработать программу поддержки устройства компьютером. Еще бы! Массу работы уже взял на себя создатель HID-устройства.

Организация обмена данными между HID-устройством и компьютером

Чтобы описать взаимодействие HID-устройства с компьютером, употребим термин «хост». В данном случае под ним понимается управляющее устройство в общей физической архитектуре взаимодействия по USB-протоколу. Так, все порты в компьютере - хосты. К ним можно подключать различные USB-устройства (флэшки, мыши, веб-камеры, фотоаппараты и проч.), которые хоста не имеют. Хост обеспечивает обнаружение, подключение, отключение, конфигурирование устройств, а также сбор статистики и управление энергопотреблением.

HID-устройство может само установить частоту опроса, во время которого выясняется наличие в нем каких-либо новых данных. Значит, даже на таком низком уровне программист может довериться системе, поскольку частота опроса и другие параметры обмена данными должны быть заранее заданы в программе контроллера HID-устройства. Этим протокол HID отличается от общего описания USB 1.1 или USB 2.0, в котором нет жестких требований к организации протокола. Однако при специфических задачах, требующих повышенного уровня безопасности, может оказаться довольно сложно избавиться от циклических опросов, когда постоянно передаются почти одни и те же блоки данных.

Особенности программирования HID-устройств

HID-устройства имеют специальные дескрипторы. Когда хост определит, что устройство принадлежит к классу HID, он передает управление им соответствующему драйверу. Предполагается, что дальнейший обмен данными ведется под его руководством.

В Windows за доступ к HID-устройствам отвечает системная служба HidServ. Подробнее о функциях запросов к HID-устройствам и других особенностях работы с HID-драйвером рассказывается в работе П. В. Агурова «Интерфейс USB. Практика использования и программирования» (СПб.: БХВ-Петербург, 2005).

Программирование HID-устройств на «верхнем уровне»

Нелегкую жизнь «прикладных» программистов, работающих на Паскале, облегчает проверенный модуль HID. PAS, программная оболочка для hid. dll (Hid User Library - как указано в свойствах файла). В комментариях к файлу сообщается, что в основе его лежат модули hidsdi.h и hidpi.h корпорации Microsoft. А сам файл HID. PAS - часть пакета JEDI ().

Для работы с HID-устройством в среде Delphi for win32 применяется компонент TJvHidDeviceController, представляющий собой удобный глобальный менеджер для доступа к HID-устройствам. А уже на его базе можно получить объектный экземпляр для работы с конкретным устройством.

Основные свойства и события компонента TJvHidDeviceController

Рассмотрим компонент TJvHidDeviceController более подробно. Событие OnArrival срабатывает на поступление (подключение) в систему HID-устройства, доступ к устройству предоставляется в обработчике этого события через экземпляр класса TJvHidDevice. Простое событие OnDeviceChange реагирует на изменение состояния устройства, оно только сигнализирует об изменениях в системе. Событие OnDeviceData срабатывает при поступлении данных от одного из HID-устройств и передает обработчику следующее: HidDev: TJvHidDevice; - устрой-ство, от которого были получены данные;

Событие OnDeviceDataError уведомляет об ошибке передачи данных, передавая в процедуру обработки параметры HidDev: TJvHidDevice; - HID-устройство и Error: DWORD; - код ошибки. Событие OnDeviceUnplug уведомляет об извлечении устройства из списка установленных в системе. Типы обработчиков событий на Plug и Unplug одинаковы (в исходном тексте: TJvHidUnplugEvent = TJvHidPlugEvent). В обработчик передается объект класса TJvHidDevice, соответствующий HID-устройству.

Для последовательного перечисления имеющихся в системе HID-устройств по вызову метода Enumerate предназначено событие OnEnumerate, т. е. в обработчике события найденные устройства последовательно передаются в виде объектов. Это событие принудительно инициируется методом Enumerate, использующимся для «проведения» имеющихся HID-устройств через обработчик, например при ревизии состояния HID-устройств по инициативе хоста (компьютера).

Событие OnRemoval срабатывает на физическое извлечение устройства из системы и имеет тот же тип обработчика TJvHidUnplugEvent, что и для OnDeviceUnplug. Функция CountByProductName выдает количество устройств, удовлетворяющих указанному в аргументе имени продукта, а CountByVendorName - указанному в аргументе имени производителя.

Основные свойства и события класса TJvHidDevice

Класс TJvHidDevice - виртуальное представление отдельно взятого HID-устройства. Новый объект этого класса можно получить, как было уже сказано, из события OnArrival или OnEnumerate. Функционал классов TJvHidDeviceController и TJvHidDevice частично дублируется, поскольку в первом из них интегрированы общий инструментарий для работы с набором имеющихся в системе HID-устройств и механизм доступа к одному из них. Устройство можно однозначно идентифицировать по свойствам SerialNumber, ProductName и VendorName. Чтобы получить сведения о поступлении данных с применением такого объекта, можно воспользоваться событием OnData. Отсылка данных ведется через метод WriteFile (в строгом смысле - через функцию). WriteFile - это оболочка системной функции WriteFile (kernel32).

Чтобы проконтролировать факт извлечения устройства, следует присвоить свой обработчик событию OnUnplug. Перед началом обмена данными с HID-устройством нужно удостовериться в самой возможности такого обмена с помощью HasReadWriteAccess. В этом классе на возникновение ошибки обмена данными даже есть отдельное событие OnDataError.

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

Метод ScanDevices (листинг 1) предназначен для инициирования процесса поиска в системе необходимого HID-устройства. Большая часть кода, за исключением вызова метода Enumerate, необязательна и обеспечивает гибкость приложения, например, для того, чтобы в эту же тестовую программу можно было добавить возможность работы по интерфейсу, отличному от HID. Метод AddError выводит в окно отладочную информацию в процессе работы программы.

В листинге 2 приведен обработчик события OnEnumerate для поиска необходимого внешнего устройства. Для простоты будем считать, что программа может работать только с одним устройством нужного ей типа.

Прежде чем рассматривать дальнейшую реализацию проекта, следует немного рассказать о принятом формате обмена данными верхнего уровня, т. е. о структуре, призванной быть посредником между методами приема-передачи данных и конкретной решаемой прикладной задачей. Дело в том, что здесь разработчику предоставляется возможность реализовать свои творческие способности. Вернее, разработчикам, потому что процесс создания нового протокола очень часто бывает двусторонним, и при этом первую скрипку играет тот, кому труднее реализовывать алгоритм обмена. В общем, каким бы ни был протокол обмена, всегда приятно делать каждую программную сущность максимально наглядной и самодостаточной, пусть даже в ущерб некоторым общепринятым традициям. Ибо лучшее решение - то, которое будет реализовано в сжатые сроки с минимальной привязкой к программной среде и с большими возможностями дальнейшего развития. На основе этих принципов был создан протокол обмена верхнего уровня, где главное понятие - «команда». Из листинга 3 видно, насколько автор любит строковые данные, не раз спасавшие его при отладке программных модулей. Как же замечательно, что у нас вообще есть тип String! Все команды протокола делятся на категории (классы), внутри которых существует код команды, однозначно характеризующий ее назначение. Параметр edParam служит для отсылки данных в устройство, а параметр edAnswerData содержит в себе полученные от устройства данные. Строковый тип описанных членов записи позволяет свободно и наглядно манипулировать данными в формате HEX-строки. И что самое приятное, формат описанной записи идеологически стоит где-то посередине между ее непосредственным назначением и различными формами ее представления (INI, HEX, XML и т. д.)

Выполнение команды, т. е. отсылка данных в устройство, реализовано с применением отсылки пакетов данных длиной 8 байт (листинг 4). Эта длина - не единственное решение, такой выбор продиктован требованиями протокола верхнего уровня и в каждом конкретном случае может быть другим. Это, что называется, дело вкуса. Странный флаг IsUSBMode в методе ExecuteCommand (листинг 5 на «Мир ПК-диске») оставлен как напоминание о том, что вместо работы с USB нам может потребоваться использовать COM-порт или какой-то другой интерфейс. В начале отсылаемой группы данных в устройство передается синхросерия произвольно выбранного формата (например, 3E3E3E2B), сообщающая устройству, что у него на входе вполне легальные данные. Напомню, что в данном случае речь идет не столько о HID, сколько о специфическом протоколе верхнего уровня, идеологически оторванном от «железа» и предназначенном для решения особых прикладных задач.

В обработчике GetDataExecutor полученных от устройства данных (пакет по 8 байт) использовано специально созданное событие OnNewInputData для передачи первично обработанных данных на дальнейшую обработку, причем с указанием их старого и нового значений (листинг 6 на «Мир ПК-диске»). Таким образом, события поступления необработанных данных и указание на дальнейшую обработку развязываются, позволяя добавлять какой-то специфический алгоритм предупреждения на раннем этапе ошибочной, повторной или ненужной входной информации.

Представленные здесь примеры работы с HID-устройством иллюстрируют общую идею статьи - относительную простоту программирования нестандартных HID-устройств средствами Delphi.


рис.1 Иллюстрация работы Android устройства в режимах USB Host и Accessory (рисунок с сайта http://developer.android.com)

Отметим, что использование USB - не единственный способ связи с тем же самодельным устройством. Android позволяет использовать еще , NFC, Wi-Fi P2P, SIP, а также стандартное сетевое подключение . Так что в арсенале разработчика достаточно возможностей для осуществления своих самых смелых замыслов.

Другим распространенным вариантом связи с различными устройствами до сих пор является использование переходника USB-COM. Материал в сети по применению переходника USB-COM в Android есть - см., например, . Популярность такого подключения обусловлена наличием большого количества уже разработанных с использованием различных микроконтроллеров устройств, связь с которыми осуществляется с помощью COM-порта (последовательного порта), что лет 10 назад являлось почти стандартным способом передать данные от компьютера к самодельной «железке».

В сравнении с COM-портом, использование USB позволяет существенно повысить скорость передачи данных и сделать этот процесс удобным для пользователя. Cкорость передачи, которая даже в случае низкоскоростных устройств (клавиатуры, мыши, джойстики), составляет 10-1500 Кбит/c, простота и невысокая стоимость кабельной системы и подключений, самоидентификация устройств с автоматическим конфигурированием, скрытие подробностей электрического подключения от конечного пользователя (плюс возможность отключения кабеля без выключения устройств), контроль ошибок и их восстановление на уровне протокола - вот неоспоримые преимущества данной технологии (см. , с.12).

Вообще, говоря об использовании USB для передачи данных, нелишним будет упомянуть книгу П.Агурова «Интерфейс USB» . Она, хотя часто критикуется в сети и выпущена последний раз в 2006 году, не раз помогла найти верное решение при поиске информации по различным аспектам применения этой технологии. В книге рассмотрены вопросы: от выбора микросхемы и схемотехники для контроллера до написания программы микроконтроллера и примеров программирования передачи данных по протоколу USB со стороны компьютера. Нельзя не указать и "первоисточник" данных по этому вопросу - сайт некоммерческой организации USB IF (USB Implementers Forum), занимающейся разработкой спецификаций этого интерфейса - , правда данный материал на английском языке. Однако именно там вы найдете исчерпывающие сведения об устройстве интерфейса USB. Есть неплохой перевод частей спецификации - . Интересующимся программными решениями со стороны микроконтроллера также можно посмотреть ссылку .

Данная статья адресована прежде всего тем, у кого есть какое-либо электронное устройство (разработанное самостоятельно или кем-то еще), протокол обмена данными с которым хорошо известен (например, уже есть программа, работающая с этим устройством в Windows/Linux) и хотелось бы иметь программу, работающую с ним в Android.

Немного о классах USB-устройств

Необходимо отметить, что разработка программного обеспечения для обмена данными с конкретным устройством сильно зависит от его реализации на уровне микроконтроллера. Привести примеры программ связи для всех типов USB-устройств в рамках одной статьи, по понятным причинам, невозможно (начальные сведения о программировании различных типов устройств можно почерпнуть в ). Однако, мы ограничимся тем, что приведем код, реализующий поиск устройства и доступ к его контрольным точкам для обмена информацией. Также разберем отправку данных на примере одного из типов USB-устройств, а именно, класса устройств HID (human interface device - класс устройств для взаимодействия с человеком). Этот класс включает в себя «медленные» устройства, такие как клавиатура, мышь, джойстик и примеров его реализации с помощью различных микроконтроллеров в сети достаточно (есть, например, и в ).

Почему именно класс HID так полюбился изготовителям различных самодельных устройств? Процитируем Википедию : «Помимо детальных спецификаций классических устройств ввода (типа клавиатур и мышек) стандарт HID определяет особый класс устройств без детальных спецификаций. Этот класс именуется USB HID Consumer Control и представляет собой по сути нерегламентированный канал связи с устройством. При этом устройство пользуется теми же стандартными для операционной системы драйверами что и мышка с клавиатурой. Таким образом, можно создать USB устройство которое не требует создания и инсталляции специальных драйверов в большинстве распространенных компьютерных операционных систем». Остается добавить только, что работает эта спецификация и в ОС Android (не исключая прошивок CyanogenMod).

Одним из вариантов обмена данными с HID-устройством является передача по прерываниям (interrupt transfer), которая используется в том случае, когда необходимо передать пакеты данных небольшого размера (максимальный размер пакета зависит от скорости передачи и составляет от 64 до 1024 байт) через заданный временной интервал. Пакет для передачи называется репортом (англ. - report, см. с.71, 95). Такой длины репорта обычно вполне хватает для обмена информацией с самодельным устройством, 64 байта информации в одном пакете, например, - это довольно много для контроллера, ведь для передачи состояний светодиода или простейшего датчика достаточно 1 бита информации.

Необходимые инструменты

Итак, нам понадобятся - планшет или телефон с Android версии не ниже 3.1. Здесь необходимо отметить, что вышеуказанный USB Host API полностью реализован не на всех мобильных устройствах (об этом упоминается и на сайте developer.android.com, см. ссылку ). В некоторых планшетах/телефонах разъем USB используется только для зарядки и связи с персональным компьютером. Еще раз отправлю читателя к списку мобильных устройств, пригодных или непригодных для наших опытов (см. ).

Понадобится также какое-либо USB-устройство (для первых опытов будет достаточно обычного USB-флеш-накопителя), переходник OTG (On-The-Go - см. рис. 2) и/или шнур USB для связи с устройством. В Википедии по поводу OTG говорится: «При подключении через USB OTG ранг устройства (ведущий или ведомый) определяется наличием или отсутствием перемычки между контактами 4 и 5 в штекере соединительного кабеля. В USB OTG кабеле такая перемычка устанавливается лишь в одном из двух разъемов (см. ).» Соответственно, нам необходима такая перемычка со стороны мобильного устройства.


Рис.2 Различия в схеме обычного USB-кабеля и OTG-кабеля (рисунок с сайта http://tech.firstpost.com)

Такой OTG-кабель для Вашего устройства можно спаять и самостоятельно. Для этого необходимо купить в радиомагазине подходящий разъем, плюс автор, например, использовал старый кабель от переносного жесткого диска:

Неплохим подспорьем в работе будет также программа USB Device Info, установленная из хранилища Google Play Market. Программа умеет определять подключенные к USB-разъему планшета/телефона устройства как с помощью Java API так и с помощью ядра Linux. То есть, если Ваше устройство не определилось с помощью Java USB Host API в USB Device Info, то, с большой вероятностью, тщетно будет использовать для этого мобильного устройства какую-либо (в том числе и свою) Android-программу, написанную с помощью Java и USB Host API.

Иногда, также, очень полезной бывает информация, выводимая командой lsusb операционной системы Linux. С ключами -v и -d lsusb выводит о USB-устройстве все, или почти все, что необходимо разработчику программного обеспечения для устройств этого класса (см. рис.3).


Рис.3 Пример вывода команд lsusb и lsusb -v -d

Далее, необходим компьютер с установленным Android SDK и интегрированной средой разработки (IDE) Eclipse с плагином ADT (хотя можно обойтись и только SDK). Как создать и установить приложение для Android можно посмотреть, например, в , или в сети Интернет.

Ну и, конечно, необходимо хотя бы а также желание добиться результата, без него никак! Отмечу, что на выяснение некоторых технических вопросов применения USB в Android автором, потребовались недели кропотливого поиска информации.

Классы Java для работы с USB в Android API

Итак, как говорится на сайте разработчиков USB Host API для Android (см. ) - «прежде чем начать, важно понять какие классы вы будете использовать в работе». В таблице 1 приведено описание важнейших классов для работы с USB Host API (попытка перевода информации с http://developer.android.com).

Таблица 1. Описание классов для работы с USB в Android

Название класса Описание
UsbManager Allows you to enumerate and communicate with connected USB devices.
Позволяет определять подключенное USB-устройство и обмениваться с ним данными.
UsbDevice Represents a connected USB device and contains methods to access its identifying information, interfaces, and endpoints.
Представляет подключенное USB-устройство и содержит методы для доступа к его идентификационной информации, интерфейсам, и конечным точкам.
UsbInterface Represents an interface of a USB device, which defines a set of functionality for the device. A device can have one or more interfaces on which to communicate on.
Представляет «интерфейс» USB-устройства, который определяет набор функций для данного устройства. Одно устройство может иметь один или несколько интерфейсов для обмена информацией.
UsbEndpoint Represents an interface endpoint, which is a communication channel for this interface. An interface can have one or more endpoints, and usually has input and output endpoints for two-way communication with the device.
Представляет «конечную точку» интерфейса, которая и является каналом связи для этого интерфейса. Интерфейс может иметь одну и более конечных точек, и обычно имеет конечные точки для получения информации и для ее передачи.
UsbDeviceConnection Represents a connection to the device, which transfers data on endpoints. This class allows you to send data back and forth sychronously or asynchronously.
Представляет «подключение» к данному устройству. Необходимо для передачи данных в конечную точку. Этот класс позволяет получать данные или передавать синхронно или асинхронно.
UsbRequest Represents an asynchronous request to communicate with a device through a UsbDeviceConnection.
Представляет асинхронный запрос на обмен данными с устройством через UsbDeviceConnection.
UsbConstants Defines USB constants that correspond to definitions in linux/usb/ch9.h of the Linux kernel..
Определяет константы, которые соответствуют определениям в linux/usb/ch9.h ядра Linux.

Практически во всех случаях применения USB Host API программист использует эти классы в своей работе. Алгоритм их применения выглядит примерно так: определяем устройства (цель - программный доступ к классу UsbDevice), подключенные к хосту (мобильному устройству), с помощью UsbManager. Когда программный доступ устройству получен, необходимо определить соответствующие UsbInterface и UsbEndpoint для общения с ним. Как только вы получили в свое распоряжение конечную точку, откройте UsbDeviceConnection, чтобы общаться с USB-устройством. Если конечная точка работает в режиме асинхронной передачи, используем класс UsbRequest.

Давайте попробуем со всем этим разобраться создав простое приложение, которое, используя этот API, определит подключенное к хосту с ОС Android устройство и выведет о нем некоторую информацию на экран телефона или планшета.

Создаем проект

В Eclipse проект создается с помощью пунктов меню File->New->Android Application Project. Заметим также, что код, приведенный ниже заимствован из приложений-примеров, поставляемых с Android SDK (папка android sdk samples/android-N(API Level)/USB) речь идет о программе управления USB-игрушкой Missile Launcher (см. рис. 4) Примеры приложений загружаются через Android SDK Manager (нужно отметить пункт - Samples for SDK ). В листингах, приведенных ниже, примеры кода снабжены комментариями, которые объясняют суть происходяшего.


Рис.4 Забавная игрушка "Ракетозапускалка"

Создавая проект не забудьте отметить нужный API Level в опции Minimum Requared SDK (API Level 12, соответствующий версии Android 3.1 /Honeycomb/, или выше). В проекте будет очень простой интерфейс пользователя - главное окно (Activity) и TextView для вывода информации. Подобный проект подробно рассмотрен в .

В созданном автоматически классе для Activity нашего проекта необходимо определить следующие экземпляры классов для работы с USB:

private TextView lgView;
private UsbManager mUsbManager;
private UsbDevice mDevice;
private UsbDeviceConnection mConnection;
private UsbEndpoint mEndpointIntr;

LgView = (TextView) findViewById(R.id .logTextView ) ;

и получаем доступ к классу UsbManager

MUsbManager = (UsbManager) getSystemService(Context .USB_SERVICE ) ;

Создадим еще обработчик события onResume(). Добьемся цели - чтобы информация о подключенных устройствах обновлялась при активизации окна нашего приложения (см. Листинг 1).

Листинг 1. Обработчик события onResume()

public void onResume() {
super .onResume () ;

//заполняем контейнер списком устройств
HashMap< String , UsbDevice> deviceList = mUsbManager.getDeviceList () ;
Iterator< UsbDevice> deviceIterator = deviceList.values () .iterator () ;

lgView.setText ( "Devices Count:" + deviceList.size () ) ;

while (deviceIterator.hasNext () ) {
UsbDevice device = (UsbDevice) deviceIterator.next () ;

//пример определения ProductID устройства
\n " + "Device ProductID: " + device.getProductId () ) ;
}
//определяем намерение, описанное в фильтре
// намерений AndroidManifest.xml
Intent intent = getIntent() ;
lgView.setText ( lgView.getText () + "\n " + "intent: " + intent) ;
String action = intent.getAction () ;

//если устройство подключено, передаем ссылку в
//в функцию setDevice()
UsbDevice device = (UsbDevice) intent.getParcelableExtra (UsbManager.EXTRA_DEVICE ) ;
if (UsbManager.ACTION_USB_DEVICE_ATTACHED .equals (action) ) {
setDevice(device) ;
lgView.setText ( lgView.getText () + "\n " + "UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action) is TRUE" ) ;
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED .equals (action) ) {
if (mDevice != null && mDevice.equals (device) ) {
setDevice(null ) ;
lgView.setText ( lgView.getText () + "\n " + "UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action) is TRUE" ) ;
}
}

Далее, для Activity создадим функцию setDevice(), необходимую для работы с нашим устройством (см. Листинг 2). В обработчике onResume() и в функции setDevice() мы в точности выполнили алгоритм применения USB Host API, описанный в предыдущем разделе.

Листинг 2. Функция setDevice()

private void setDevice(UsbDevice device) {
lgView.setText ( lgView.getText () + "\n " + "setDevice " + device) ;

//определяем доступные интерфейсы устройства
if (device.getInterfaceCount () != 1 ) {

LgView.setText ( lgView.getText () + "\n " + "could not find interface" ) ;
return ;
}
UsbInterface intf = device.getInterface (0 ) ;

//определяем конечные точки устройства
if (intf.getEndpointCount () == 0 ) {

LgView.setText ( lgView.getText () + "\n " + "could not find endpoint" ) ;
return ;
} else {
lgView.setText ( lgView.getText () + "\n " + "Endpoints Count: " + intf.getEndpointCount () ) ;
}

UsbEndpoint epIN = null ;
UsbEndpoint epOUT = null ;

//ищем конечные точки для передачи по прерываниям
for (int i = 0 ; i < intf.getEndpointCount () ; i++ ) {
if (intf.getEndpoint (i) .getType () == UsbConstants.USB_ENDPOINT_XFER_INT ) {
if (intf.getEndpoint (i) .getDirection () == UsbConstants.USB_DIR_IN ) {
epIN = intf.getEndpoint (i) ;
lgView.setText ( lgView.getText () + "\n " + "IN endpoint: " + intf.getEndpoint (i) ) ;
}
else {
epOUT = intf.getEndpoint (i) ;
lgView.setText ( lgView.getText () + "\n " + "OUT endpoint: " + intf.getEndpoint (i) ) ;
}
} else { lgView.setText ( lgView.getText () + "\n " + "no endpoints for INTERRUPT_TRANSFER" ) ; }
}

MDevice = device;
mEndpointIntr = epOUT;

//открываем устройство для передачи данных
if (device != null ) {
UsbDeviceConnection connection = mUsbManager.openDevice (device) ;
if (connection != null && connection.claimInterface (intf, true ) ) {

LgView.setText ( lgView.getText () + "\n " + "open device SUCCESS!" ) ;
mConnection = connection;

} else {

LgView.setText ( lgView.getText () + "\n " + "open device FAIL!" ) ;
mConnection = null ;
}
}
}
}

В дополнение к приведенному коду, который, как уже наверняка догадался внимательный читатель, открывает устройство для приема-передачи данных, остается лишь задействовать протокол обмена данными, который, повторюсь, должен быть хорошо известен разработчику. Приведем лишь, как было обещано, код, который отправит HID устройству некоторый пакет данных message используя передачу по прерываниям, класс UsbRequest и соответствующую конечную точку - см. Листинг 3.

Листинг 3. Пример кода для отправки данных устройству

//определение размера буфера для отправки
//исходя из максимального размера пакета
int bufferDataLength = mEndpointIntr.getMaxPacketSize () ;

lgView.setText ( lgView.getText () + "\n " + mEndpointIntr.getMaxPacketSize () ) ;

ByteBuffer buffer = ByteBuffer.allocate (bufferDataLength + 1 ) ;

UsbRequest request = new UsbRequest() ;

buffer.put (message) ;

request.initialize (mConnection, mEndpointIntr) ;

request.queue (buffer, bufferDataLength) ;

if (request.equals (mConnection.requestWait () ) )

//отправка прошла успешно
//lgView.setText(lgView.getText() + "\n" + "sending CLEAR!!!");

catch (Exception ex)

//что-то не так...
//lgView.setText(lgView.getText() + "\n" + "sending not clear...");

Фильтрация устройств в AndroidManifest.xml

Хотя в нашем приложении нет нужды в поиске конкретного устройства с известными VID (Vendor-ID) и PID (Product-ID), инженеры Google не приводят примеров приложений без секции intent-filter в манифест файле, и автору не удалось добиться работы программы без фильтрации устройств в AndroidManifest.xml .

Напомню, что Vendor-ID и Product-ID это уникальные идентификаторы USB-устройств. То есть, используя фильтрацию, можно создать приложение, которое взаимодействует лишь с определенным устройством или каким-то классом устройств. Отметим, что производители устройств должны согласовать эти номера с организацией USB IF.

Приложение, манифест файл которого приведен в листинге 4, а файл с условием фильтрации в листинге 5, например, с успехом распознает подключенные к мобильному устройству USB-флеш-накопители, но не распознает клавиатуру и мыши, имеющиеся у автора. Это приложение вместе с исходным кодом можно скачать по ссылке .

Листинг 4. Файл AndroidManifest.xml


" > http://schemas.android.com/apk/res/android"
> package="ru.learn2prog.usbhostexample"
android:versionCode="1"
android:versionName="1.0" >


android:minSdkVersion ="12"
android:targetSdkVersion ="14" />


android:allowBackup ="true"
android:icon ="@drawable/ic_launcher"
android:label ="@string/app_name"
android:theme ="@style/AppTheme" >

android:name ="ru.learn2prog.usbhostexample.MainActivity"
android:label ="@string/app_name" >
>

"android.intent.category.DEFAULT" />

"android.intent.category.LAUNCHER" />

>

>

>
"android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource ="@xml/device_filter" />
>
>

>

Листинг 5. Файл фильтра device_filter.xml (каталог /res/xml)

>

>

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

Литература/Ссылки: 11.
12.
13. http://developer.android.com/guide/topics/connectivity/usb/host.html - обзор классов, необходимых для работы с USB в Android
14. ссылка на исходники приложения

Шина USB (Universal Serial Bus – универсальная последовательная шина) появилась 15 января 1996 года при утверждении первого варианта стандарта фирмами – Intel, DEC, IBM, NEC, Northen Telecom и Compaq.

Основная цель стандарта, поставленная перед его разработчиками – создать возможность пользователям работать в режиме Plug&Play с периферийными устройствами. Это означает, что должно быть предусмотрено подключение устройства к работающему компьютеру, автоматическое распознавание его немедленно после подключения и последующей установки соответствующих драйверов. Кроме этого, желательно питание маломощных устройств подавать с самой шины. Скорость шины должна быть достаточной для подавляющего большинства периферийных устройств. Контроллер USB должен занимать только одно прерывание независимо от количества подключенных к шине устройств, то есть решить проблему нехватки ресурсов на внутренних шинах IBM PC совместимого компьютера.

Практически все поставленные задачи были решены в стандарте на USB и весной 1997 года стали появляться компьютеры, оборудованные разъемами для подключения USB устройств. Сейчас USB стала настолько активно внедряться производителями компьютерной периферии, что, например, в компьютере iMAC фирмы Apple Computers присутствует только USB в качестве внешней шины.

Возможности USB 1.0 следующие:

1. высокая скорость обмена данными (full-speed) – 12 Мбит /с;

2. максимальная длина кабеля для высокой скорости обмена – 5 метров;

3. низкая скорость обмена данными (low-speed) – 1,5 Мбит /с;

4. максимальная длина кабеля для низкой скорости обмена – 3 метра;

5. максимальное количество подключенных устройств – 127;

6. возможное одновременное подключение устройств с различными скоростями обмена;

8. максимальный ток потребления на одно устройство – 500 мА.

Поэтому целесообразно подключать к USB 1.0 практически любые периферийные устройства, кроме цифровых видеокамер и высокоскоростных жестких дисков. Особенно удобен этот интерфейс для подключения часто подключаемых/отключаемых приборов, таких как цифровые фотокамеры.
Возможность использования только двух скоростей обмена данными ограничивает применяемость шины, но существенно уменьшает количество линий интерфейса и упрощает аппаратную реализацию.
Питание непосредственно от USB возможно только для устройств с малым потреблением, таких как клавиатуры, мыши, джойстики и т.п.

Сигналы USB передаются по 4-х проводному кабелю, схематично показанному на рисунке ниже:

Рисунок 2.6.1 – Сигнальные провода USB

Здесь GND – цепь общего провода для питания периферийных устройств, Vbus - +5 В также для цепей питания. Шина D+ предназначена для передачи данных по шине, а шина D- для приема данных.
Кабель для поддержки полной скорости шины (full-speed) выполняется как витая пара, защищается экраном и может также использоваться для работы в режиме минимальной скорости (low-speed). Кабель для работы только на минимальной скорости (например, для подключения мыши) может быть любым и неэкранированным.
Разъемы, используемые для подключения периферийных устройств, делятся на серии: разъемы серии «A» (вилка и розетка) предназначены только для подключения к источнику, например, компьютеру, разъемы серии «B» (вилка и розетка) только для подключения к периферийному устройству.

USB разъемы имеют следующую нумерацию контактов, показанную в таблице 2.6.1.

Таблица 2.6.1 – Назначение и маркировка контактов USB

В 1999 году тот же консорциум компьютерных компаний, который инициировал разработку первой версии стандарта на шину USB, начал активно разрабатывать версию 2.0 USB, которая отличается введением дополнительного высокоскоростного (Hi-speed) режима. Полоса пропускания шины увеличена в 40 раз, до 480 Мбит/с, что сделало возможным передачу видеоданных по USB.
Совместимость всей ранее выпущенной периферии и высокоскоростных кабелей полностью сохраняется. Контроллер стандарта 2.0 уже интегрирован в набор системной логики программируемых устройств (например, материнская плата персонального компьютера).

В 2008 году компаниями Intel, Microsoft, Hewlett-Packard, Texas Instruments, NEC и NXP Semiconductors создана спецификация стандарта USB 3.0. В спецификации USB 3.0 разъёмы и кабели обновлённого стандарта физически и функционально совместимы с USB 2.0, однако в дополнение к четырем линиям связи, добавлены ещё четыре. Тем не менее, новые контакты в разъёмах USB 3.0 расположены отдельно от старых на другом контактном ряду. Спецификация USB 3.0 повышает максимальную скорость передачи информации до 5 Гбит/с - что на порядок больше 480 Мбит/с, которые может обеспечить USB 2.0. Кроме того, увеличена максимальная сила тока с 500 мА до 900 мА на одно устройство, что позволяет питать некоторые устройства, требующие ранее отдельного блока питания.

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

1. разработка полнофункционального драйвера операционной системы;

2. использования интерфейса специального класса USB – устройств, называемых HID (Human Interface Device) устройствами.

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

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

1. клавиатуры, мыши, джойстики;

2. различные датчики и считыватели;

3. игровые рулевое управление и педали;

4. кнопки, переключатели, регуляторы.

Любое такое устройство, если оно выполняет требования к HID-устройствам, будет автоматически распознано системой и не потребует написания специальных драйверов. Кроме того, их программирование, как правило, намного проще написания специализированного драйвера устройства. К сожалению, способ имеет существенный недостаток: скорость обмена информации с HID-устройством сильно ограничена и составляет максимум 64 кБ/с.

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

Стоит отметить что, многие USB-приборы, с первого взгляда не попадающие под определение устройств взаимодействия с человеком, логичнее все же реализовать как HID-устройства. Такое явление часто встречается в области производственного оборудования, которая последнее время переживает массовое внедрение USB-технологий. К примеру, рассмотрим лабораторный источник питания с возможностью задания параметров его выходных сигналов с компьютера с помощью USB-интерфейса. Непосредственно источник питания без сомнений не является средством взаимодействия с человеком. Однако, в данном случае функции, реализуемые посредством USB-подключения, дублируют клавиатуру, регуляторы и индикаторы, установленные на самом приборе. А эти органы управления как раз попадают под определение HID. Соответственно блок питания с этими USB-функциями логичнее всего организовать как HID-устройство.

В рассмотренном примере для нормальной работы достаточно будет небольшой скорости передачи данных, в других же случаях приборы могут быть весьма требовательны к скорости обмена. Низкая скорость передачи является главным ограничением HID-варианта построения устройства, что в сравнении с 12 Мбит/сек полной скорости USB 1.0-шины выглядит большим минусом HID-технологии в вопросе выбора конкретной USB-реализации. Однако для многих задач коммуникации указанной скорости вполне хватает и HID-архитектура как специализированный инструмент занимает достойное место среди способов организации обмена данными.

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

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

1. полноскоростное HID-устройство может передавать 64000 байт каждую секунду или по 64 байта каждые 1 мс; низкоскоростное HID-устройство имеет возможность передать вплоть до 800 байт в секунду или по 8 байт каждые 10 мс.

2. HID-устройство может назначить частоту своего опроса для определения того, есть ли у него свежие данные для передачи.

3. Обмен данными с HID-устройством осуществляется посредством специальной структуры, называемой репортом (Report). Каждый определенный репорт может содержать до 65535 байт данных. Структура репорта имеет весьма гибкую организацию, позволяющую описать любой формат передачи данных. Для того чтобы конкретный формат репорта стал известен хосту микроконтроллер должен содержать специальное описание – дескриптор репорта.

Реализуется USB взаимодействие непосредственно на микроконтроллере несколькими способами:

1. использованием контроллера с аппаратной поддержкой, например AT90USB*, фирмы atmega;

2. использованием программной эмуляции usb-интерфейса на любом микроконтроллере.

Для программной реализации в настоящее время существует ряд готовых решения под различные семейства микроконтроллеров. Для AVR микроконтроллеров, например, Atmega8 возможно использовать следующие свободные библиотеки на языке Си:

Обе достаточно простые в использовании, обеспечивают полную эмуляция USB 1.1 low-speed устройств за исключением обработки ошибок связи и электрических характеристик и запускаются практически на всех AVR контроллерах с минимум 2 килобайтами flash-памяти, 128 байтами RAM и частотой от 12 до 20 МГц.

Для написания приложений с поддержкой Windows USB HID устройств требуются заголовочные файлы hid*, входящие в состав WDK (Windows Driver Kit), или можно использовать свободно-распространяемую библиотеку hidlibrary или другую аналогичную.

Таким образом, в общем случае программирование USB достаточно сложная задача, требующая специального микроконтроллера с аппаратной поддержкой и написания драйвера операционной системы. Однако в практике при разработке устройств можно использовать значительно более просто интерфейс HID – устройств, поддержка которого реализована на уровне стандартного драйвера системы, а программирование упрощается использованием существующих библиотек функций.

Контрольные вопросы

  1. В чем отличие провода D- и GND в USB? Почему нельзя использовать один общий провод для питания и сигнала?
  2. Сколько режимов скорости работы USB существует на сегодняшний день (включая версию 3.0)?
  3. Что такое HID-устройство? Почему для их работы в современных ОС не требуется написание драйверов?
  4. Можно ли реалировать USB устройства с помощью микропроцессора, неимеющего встроенной поддержки интерфейса?
  5. Какие основные отличия USB 3.0 от предыдущих версий?

Но ведь мало только физически подсоединить устройство к компьютеру, нужно еще и наладить обмен данными между ними. Как же выбрать порт и организовать подключение? Несколько лет назад стандартным решением было использование COM-порта. Кстати, до сих пор различные специалисты доустанавливают на промышленные компьютеры по 8, по 16, а то и по 32 COM-порта (есть целая категория различных PCI-плат расширения последовательных портов, контроллеров и т. д.). Таким образом, если нужно подключить несколько внешних устройств с интерфейсом RS-232, могут потребоваться дорогие адаптеры и экзотические платы расширения, которые по старой традиции неделями плывут в Россию на пароходах. Кстати, название обычного переходника «адаптер DB9m/DB25f» у менеджера компьютерного магазина может вызвать разве что раздражение.

Что такое HID-устройство

Сейчас практически все устройства подключаются к компьютеру через USB-интерфейс. Поэтому во многих новых ПК COM-порт отсутствует вообще.

USB-интерфейс - типовое решение по сопряжению нового внешнего устройства с компьютером, точнее, это HID-интерфейс, базирующийся на протоколе USB 1.1.

Хотя многие и считают, что HID-интерфейс (Human Interface Device) предназначен исключительно для клавиатуры, мыши и джойстика, он годится для множества решений, связанных с сопряжением внешних устройств и компьютера.

Если пользователю необходимо производить низкоскоростной обмен данными (до 64 кбит/c) и при этом желательно сократить время на утомительной разработке собственных драйверов, то ему вполне подойдет HID. На выходе же получится простое и вполне современное решение на базе стандартного программного USB-интерфейса с гарантированной поддержкой на всех распространенных программных платформах.

Свойства HID-устройства

С точки зрения организации программной поддержки HID-устройства, все выглядит достаточно привлекательно: для работы под управлением Windows можно быстро создавать понятный компактный код на базе готовых проверенных алгоритмов. При этом у разработчика останется масса времени на реализацию собственного протокола обмена данными верхнего уровня, поскольку необходимый уровень абстрагирования уже организован за счет HID-протокола (см. таблицу). Кроме того, программисту легко проводить отладку написанного протокола обмена (разумеется, при наличии работающего HID-устройства) - благодаря относительной жесткости самого протокола достаточно просто разработать программу поддержки устройства компьютером. Еще бы! Массу работы уже взял на себя создатель HID-устройства.

Организация обмена данными между HID-устройством и компьютером

Чтобы описать взаимодействие HID-устройства с компьютером, употребим термин «хост». В данном случае под ним понимается управляющее устройство в общей физической архитектуре взаимодействия по USB-протоколу. Так, все порты в компьютере - хосты. К ним можно подключать различные USB-устройства (флэшки, мыши, веб-камеры, фотоаппараты и проч.), которые хоста не имеют. Хост обеспечивает обнаружение, подключение, отключение, конфигурирование устройств, а также сбор статистики и управление энергопотреблением.

HID-устройство может само установить частоту опроса, во время которого выясняется наличие в нем каких-либо новых данных. Значит, даже на таком низком уровне программист может довериться системе, поскольку частота опроса и другие параметры обмена данными должны быть заранее заданы в программе контроллера HID-устройства. Этим протокол HID отличается от общего описания USB 1.1 или USB 2.0, в котором нет жестких требований к организации протокола. Однако при специфических задачах, требующих повышенного уровня безопасности, может оказаться довольно сложно избавиться от циклических опросов, когда постоянно передаются почти одни и те же блоки данных.

Особенности программирования HID-устройств

HID-устройства имеют специальные дескрипторы. Когда хост определит, что устройство принадлежит к классу HID, он передает управление им соответствующему драйверу. Предполагается, что дальнейший обмен данными ведется под его руководством.

В Windows за доступ к HID-устройствам отвечает системная служба HidServ. Подробнее о функциях запросов к HID-устройствам и других особенностях работы с HID-драйвером рассказывается в работе П. В. Агурова «Интерфейс USB. Практика использования и программирования» (СПб.: БХВ-Петербург, 2005).

Программирование HID-устройств на «верхнем уровне»

Нелегкую жизнь «прикладных» программистов, работающих на Паскале, облегчает проверенный модуль HID. PAS, программная оболочка для hid. dll (Hid User Library - как указано в свойствах файла). В комментариях к файлу сообщается, что в основе его лежат модули hidsdi.h и hidpi.h корпорации Microsoft. А сам файл HID. PAS - часть пакета JEDI ().

Для работы с HID-устройством в среде Delphi for win32 применяется компонент TJvHidDeviceController, представляющий собой удобный глобальный менеджер для доступа к HID-устройствам. А уже на его базе можно получить объектный экземпляр для работы с конкретным устройством.

Основные свойства и события компонента TJvHidDeviceController

Рассмотрим компонент TJvHidDeviceController более подробно. Событие OnArrival срабатывает на поступление (подключение) в систему HID-устройства, доступ к устройству предоставляется в обработчике этого события через экземпляр класса TJvHidDevice. Простое событие OnDeviceChange реагирует на изменение состояния устройства, оно только сигнализирует об изменениях в системе. Событие OnDeviceData срабатывает при поступлении данных от одного из HID-устройств и передает обработчику следующее: HidDev: TJvHidDevice; - устрой-ство, от которого были получены данные;

Событие OnDeviceDataError уведомляет об ошибке передачи данных, передавая в процедуру обработки параметры HidDev: TJvHidDevice; - HID-устройство и Error: DWORD; - код ошибки. Событие OnDeviceUnplug уведомляет об извлечении устройства из списка установленных в системе. Типы обработчиков событий на Plug и Unplug одинаковы (в исходном тексте: TJvHidUnplugEvent = TJvHidPlugEvent). В обработчик передается объект класса TJvHidDevice, соответствующий HID-устройству.

Для последовательного перечисления имеющихся в системе HID-устройств по вызову метода Enumerate предназначено событие OnEnumerate, т. е. в обработчике события найденные устройства последовательно передаются в виде объектов. Это событие принудительно инициируется методом Enumerate, использующимся для «проведения» имеющихся HID-устройств через обработчик, например при ревизии состояния HID-устройств по инициативе хоста (компьютера).

Событие OnRemoval срабатывает на физическое извлечение устройства из системы и имеет тот же тип обработчика TJvHidUnplugEvent, что и для OnDeviceUnplug. Функция CountByProductName выдает количество устройств, удовлетворяющих указанному в аргументе имени продукта, а CountByVendorName - указанному в аргументе имени производителя.

Основные свойства и события класса TJvHidDevice

Класс TJvHidDevice - виртуальное представление отдельно взятого HID-устройства. Новый объект этого класса можно получить, как было уже сказано, из события OnArrival или OnEnumerate. Функционал классов TJvHidDeviceController и TJvHidDevice частично дублируется, поскольку в первом из них интегрированы общий инструментарий для работы с набором имеющихся в системе HID-устройств и механизм доступа к одному из них. Устройство можно однозначно идентифицировать по свойствам SerialNumber, ProductName и VendorName. Чтобы получить сведения о поступлении данных с применением такого объекта, можно воспользоваться событием OnData. Отсылка данных ведется через метод WriteFile (в строгом смысле - через функцию). WriteFile - это оболочка системной функции WriteFile (kernel32).

Чтобы проконтролировать факт извлечения устройства, следует присвоить свой обработчик событию OnUnplug. Перед началом обмена данными с HID-устройством нужно удостовериться в самой возможности такого обмена с помощью HasReadWriteAccess. В этом классе на возникновение ошибки обмена данными даже есть отдельное событие OnDataError.

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

Метод ScanDevices (листинг 1) предназначен для инициирования процесса поиска в системе необходимого HID-устройства. Большая часть кода, за исключением вызова метода Enumerate, необязательна и обеспечивает гибкость приложения, например, для того, чтобы в эту же тестовую программу можно было добавить возможность работы по интерфейсу, отличному от HID. Метод AddError выводит в окно отладочную информацию в процессе работы программы.

В листинге 2 приведен обработчик события OnEnumerate для поиска необходимого внешнего устройства. Для простоты будем считать, что программа может работать только с одним устройством нужного ей типа.

Прежде чем рассматривать дальнейшую реализацию проекта, следует немного рассказать о принятом формате обмена данными верхнего уровня, т. е. о структуре, призванной быть посредником между методами приема-передачи данных и конкретной решаемой прикладной задачей. Дело в том, что здесь разработчику предоставляется возможность реализовать свои творческие способности. Вернее, разработчикам, потому что процесс создания нового протокола очень часто бывает двусторонним, и при этом первую скрипку играет тот, кому труднее реализовывать алгоритм обмена. В общем, каким бы ни был протокол обмена, всегда приятно делать каждую программную сущность максимально наглядной и самодостаточной, пусть даже в ущерб некоторым общепринятым традициям. Ибо лучшее решение - то, которое будет реализовано в сжатые сроки с минимальной привязкой к программной среде и с большими возможностями дальнейшего развития. На основе этих принципов был создан протокол обмена верхнего уровня, где главное понятие - «команда». Из листинга 3 видно, насколько автор любит строковые данные, не раз спасавшие его при отладке программных модулей. Как же замечательно, что у нас вообще есть тип String! Все команды протокола делятся на категории (классы), внутри которых существует код команды, однозначно характеризующий ее назначение. Параметр edParam служит для отсылки данных в устройство, а параметр edAnswerData содержит в себе полученные от устройства данные. Строковый тип описанных членов записи позволяет свободно и наглядно манипулировать данными в формате HEX-строки. И что самое приятное, формат описанной записи идеологически стоит где-то посередине между ее непосредственным назначением и различными формами ее представления (INI, HEX, XML и т. д.)

Выполнение команды, т. е. отсылка данных в устройство, реализовано с применением отсылки пакетов данных длиной 8 байт (листинг 4). Эта длина - не единственное решение, такой выбор продиктован требованиями протокола верхнего уровня и в каждом конкретном случае может быть другим. Это, что называется, дело вкуса. Странный флаг IsUSBMode в методе ExecuteCommand (листинг 5 на «Мир ПК-диске») оставлен как напоминание о том, что вместо работы с USB нам может потребоваться использовать COM-порт или какой-то другой интерфейс. В начале отсылаемой группы данных в устройство передается синхросерия произвольно выбранного формата (например, 3E3E3E2B), сообщающая устройству, что у него на входе вполне легальные данные. Напомню, что в данном случае речь идет не столько о HID, сколько о специфическом протоколе верхнего уровня, идеологически оторванном от «железа» и предназначенном для решения особых прикладных задач.

В обработчике GetDataExecutor полученных от устройства данных (пакет по 8 байт) использовано специально созданное событие OnNewInputData для передачи первично обработанных данных на дальнейшую обработку, причем с указанием их старого и нового значений (листинг 6 на «Мир ПК-диске»). Таким образом, события поступления необработанных данных и указание на дальнейшую обработку развязываются, позволяя добавлять какой-то специфический алгоритм предупреждения на раннем этапе ошибочной, повторной или ненужной входной информации.

Представленные здесь примеры работы с HID-устройством иллюстрируют общую идею статьи - относительную простоту программирования нестандартных HID-устройств средствами Delphi.

Введение
Для кого эта книга
Что вы найдете в книге
Программные требования
Аппаратные требования
О программном коде
Краткое описание глав
Обозначения
Благодарности
Обратная связь
Часть I. Общие сведения о USB
Глава 1. Спецификация USB
1.1. Что такое USB и зачем это надо
1.1.1. Общая архитектура USB
1.1.2. Физическая и логическая архитектура USB
1.1.3. Составляющие USB
1.1.4. Свойства USB-устройств
1.1.5. Принципы передачи данных
1.1.6. Механизм прерываний
1.1.7. Режимы передачи данных
1.1.8. Логические уровни обмена данными
1.1.8.1. Уровень клиентского ПО
1.1.8.2. Уровень системного драйвера USB
1.1.8.3. Уровень хост-контроллера интерфейса
1.1.8.4. Уровень шины периферийного USB-устройства
1.1.8.5. Уровень логического USB-устройства
1.1.8.6. Функциональный уровень USB-устройства
1.1.9. Передача данных по уровням
1.1.10. Типы передач данных
1.1.11. Кадры
1.1.12. Конечные точки
1.1.13. Каналы
1.1.14. Пакеты
1.1.14.1. Формат маркер-пакетов IN, OUT, SETUP и PING
1.1.14.2. Формат пакета SOF
1.1.14.3. Формат пакета данных
1.1.14.4. Формат пакета подтверждения
1.1.14.5. Формат пакета SPLIT
1.1.15. Контрольная сумма
1.1.15.1. Алгоритм вычисления CRC
1.1.15.2. Программное вычисление CRC
1.1.16. Транзакции
1.1.16.1. Типы транзакций
1.1.16.2. Подтверждение транзакций и управление потоком
1.1.16.3. Протоколы транзакций
1.2. Запросы к USB-устройствам
1.2.1. Конфигурационный пакет
1.2.2. Стандартные запросы к USB-устройствам
1.2.2.1. Получение состояния GET_STATUS
1.2.2.2. Сброс свойства CLEAR_FEATURE
1.2.2.3. Разрешение свойства SET_FEATURE
1.2.2.4. Задание адреса на шине SET_ADDRESS
1.2.2.5. Получение дескриптора GET_DESCRIPTOR
1.2.2.6. Передача дескриптора SET_DESCRIPTOR
1.2.2.7. Получение кода конфигурации GET_CONFIGURATION
1.2.2.8. Задание кода конфигурации SET_CONFIGURATION
1.2.2.9. Получение кода настройки интерфейса GET_INTERFACE
1.2.2.10. Задание кода настройки интерфейса SET_INTERFACE
1.2.2.11. Задание номера кадра синхронизации SYNC_FRAME
1.2.2.12. Обработка стандартных запросов
1.2.3. Дескриптор устройства
1.2.3.1. Дескриптор устройства
1.2.3.2. Уточняющий дескриптор устройства
1.2.3.3. Дескриптор конфигурации
1.2.3.4. Дескриптор интерфейса
1.2.3.5. Дескриптор конечной точки
1.2.3.6. Дескриптор строки
1.2.3.7. Специфические дескрипторы
1.2.3.8. Порядок получения дескрипторов
1.3. Система Plug and Play (PnP)
1.3.1. Конфигурирование USB-устройств
1.3.2. Нумерация USB-устройств
1.3.3. PnP-идентификаторы USB-устройств
1.3.4. Символьные имена устройств
1.4. Модель WDM
Глава 2. Программирование на языке C для микроконтроллера
2.1. Общие сведения о языке С для микроконтроллеров
2.2. Использование стандартных библиотек
2.3. Программирование для АТ89С5131
2.3.1. Файл инициализации
2.3.2. Структуры дескрипторов
2.3.3. Структура проекта
Глава 3. Инструменты
3.1. Программаторы
3.1.1. Программатор Flip
3.1.2. Программатор ER-Tronik
3.2. Инструменты создания драйверов
3.2.1. NuMega Driver Studio
3.2.2. Jungo WinDriver
3.2.3. Jungo KernelDriver
3.3. Средства Microsoft Visual Studio
3.3.1. Depends (Dependency Walker)
3.3.2. Error Lookup
3.3.3. GuidGen
3.4. Средства Microsoft DDK
3.4.1. DeviceTree
3.4.2. DevCon
3.4.2.1. Ключ classes
3.4.2.2. Ключ driverfiles
3.4.2.3. Ключ hwids
3.4.2.4. Ключ rescan
3.4.2.5. Ключ stack
3.4.2.6. Ключ status
3.4.3. Chklnf и Genlnf
3.5. Средства CompuWare Corporation
3.5.1. Monitor
3.5.2. SymLink
3.5.3. EzDriverlnstaller
3.5.4. WdmSniff
3.6. Средства Syslnternals
3.6.1. WinObj
3.7. Средства USB Forum
3.7.1. HID Descriptor Tool
3.8. USB Command Verifier
3.9. Средства HDD Software
3.10. Средства Sourceforge
3.11. Программа мониторинга Bus Hound
Глава 4. Принципы использования функций Win32 в.NET
4.1. Общие сведения
4.2. Импорт функций Win32
4.3. Структуры
4.3.1. Атрибут StructLayout
4.3.2. Атрибут MarshalAs
4.4. Прямой доступ к данным
4.5. Обработка сообщений Windows
4.6. Общие сведения о WMI
4.7. Интернет-ресурсы к этой главе
Часть II. Классы USB
Глава 5. Класс CDC
5.1. Методы преобразования интерфейсов USB/RS-232
5.2. Общие сведения об интерфейсе RS-232
5.2.1. Линии обмена
5.2.1.1. Передаваемые данные (BA/TxD/TD)
5.2.1.2. Принимаемые данные (BB/RxD/RD)
5.2.1.3. Запрос передачи (CA/RTS)
5.2.1.4. Готовность к передаче (CB/CTS)
5.2.1.5. Готовность DCE (CC/DSR)
5.2.1.6. Готовность DTE (CD/DTR)
5.2.1.7. Индикатор вызова (CE/RI)
5.2.1.8. Обнаружение несущей (CF/DCD)
5.2.1.9. Готовность к приему (CJ)
5.3. Спецификация CDC
5.3.1. Стандартные дескрипторы
5.3.2. Функциональные дескрипторы
5.3.2.1. Заголовочный функциональный дескриптор
5.3.2.2. Дескриптор режима команд
5.3.2.3. Дескриптор абстрактного устройства
5.3.2.4. Дескриптор группирования
5.3.3. Специальные запросы
5.3.3.1. Запрос SET_LINE_CODING
5.3.3.2. Запрос GET_LINE_CODING
5.3.3.3. Запрос SET_CONTROL_LINE_STATE
5.3.3.4. Запрос SEND_BREAK
5.3.4. Нотификации
5.3.4.1. Нотификация RING^DETECT
5.3.4.2. Нотификация SERIAL_STATE
5.4. Поддержка CDC в Windows
5.4.1. Обзор функций Windows для работы с последовательными портами
5.4.1.1. Основные операции с портом
5.4.1.2. Функции настройки порта
5.4.1.3. Специальная настройка порта
5.4.1.4. Получение состояния линий модема
5.4.1.5. Работа с CDC на платформе. NET
5.4.2. Соответствие функций Windows и USB-запросов
Глава 6. Класс HID
6.1. Спецификация HID-устройств
6.2. Порядок обмена данными с HID-устройством
6.3. Установка драйвера HID-устройства
6.4. Идентификация HID-устройства
6.4.1. Идентификация загрузочных устройств
6.4.2. Дескриптор конфигурации HID-устройства
6.4.3. HID-дескриптор
6.4.4. Дескриптор репорта
6.5. Структура дескриптора репорта
6.5.1. Элементы репорта
6.5.1.1. Элементы короткого типа
6.5.1.2. Элементы длинного типа
6.5.2. Типы элементов репорта
6.5.2.1. Основные элементы
6.5.2.2. Глобальные элементы
6.5.2.3. Локальные элементы
6.5.3. Примеры дескрипторов
6.6. Запросы к НID-устройству
6.6.1. Запрос GET_REPORT
6.6.2. Запрос SET_REPORT
6.6.3. Запрос GET_IDLE
6.6.4. Запрос SET_IDLE
6.6.5. Запрос GET_PROTOCOL
6.6.6. Запрос SET_PROTOCOL
6.7. Инструменты
6.8. Драйверы для HID-устройств в Windows
Глава 7. Другие классы USB
Часть III. Практика программирования USB
Глава 8. Создание USB-устройства на основе АТ89С5131
8.1. Общая информация об АТ89С5131
8.2. Структурная схема АТ89С5131
8.3. USB-регистры AT89C5131
8.3.1. Регистр USBCON
8.3.2. Регистр USBADDR
8.3.3. Регистр USBINT
8.3.4. Регистр USBIEN
8.3.5. Регистр UEPNUM
8.3.6. Регистр UEPCONX
8.3.7. Регистр UEPSTAX
8.3.8. Регистр UEPRST
8.3.9. Регистр UEPINT
8.3.10. Регистр UEPIEN
8.3.11. Регистр UEPDATX
8.3.12. Регистр UBYCTLX
8.3.13. Регистр UFNUML
8.3.14. Регистр UFNUMH
8.4. Схемотехника АТ89С5131
8.5. Базовый проект для АТ89С5131
8.5.1. Первая версия программы для АТ89С5131
8.5.2. Добавляем строковые дескрипторы
8.5.3. Добавление конечных точек
8.6. Загрузка программы
Глава 9. Реализация класса CDC
9.1. Реализация CDC
9.2. Дескрипторы устройства
9.2.1. Инициализация конечных точек
9.2.2. Обработка CDC-запросов
9.2.3. Конфигурирование RS-порта и CDC-линии
9.2.4. Прием и передача данных
9.3. Установка драйвера
9.4. Программирование обмена данными с CDC-устройством на языке Delphi
9.5. Программирование обмена с CDC-устройством на языке С#
9.5.1. Использование компонента MSCOMM
9.5.2. Использование функций Win32
9.6. Проблемы CDC
Глава 10. Реализация класса HID
10.1. Реализация HID на АТ89С5131
10.2. Передача нескольких байтов
10.3. Feature-репорты
10.4. Передача данных от хоста (SET_REPORT)
10.5. Установка HID-устройства
10.6. Обмен данными с HID-устройством
10.6.1. Получение имени HID-устройства
10.6.2. Получение атрибутов устройства и чтение репортов
10.6.3. Передача данных от хоста к HID-устройству
10.7. Примеры HID-устройств
10.7.1. Реализация устройства "мышь"
10.7.2. Реализация устройства "клавиатура"
10.8. Использование HID-протокола
10.8.1. Интерпретация данных
10.8.2. Коллекции
10.8.3. Массивы и кнопки
10.9. HID-устройство с несколькими репортами
Глава 11. Специальные функции Windows
11.1. Функции Setup API
11.1.1. Перечисление USB-устройств
11.1.2. Получение состояния USB-устройства
11.2. Перечисление USB-устройств с помощью WMI
11.3. Специальные функции Windows XP
11.3.1. HidD_GetInputReport - чтение HID-репортов
11.3.2. Получение данных Raw Input
11.4. Функции DirectX
11.5. Диалог добавления нового оборудования
11.6. Работа с символьными именами устройств
11.7. Безопасное извлечение флэш-дисков
11.8. Обнаружение добавления и удаления устройств
11.9. Интернет-ресурсы
Глава 12. Разработка драйвера
12.1. Основные процедуры драйвера WDM
12.1.1. Процедура DriverEntry
12.1.2. Процедура AddDevice
12.1.3. Процедура Unload
12.1.4. Рабочие процедуры драйвера
12.1.4.1. Заголовок пакета
12.1.4.2. Ячейки стека ввода/вывода
12.4.1.3. Рабочие процедуры драйвера
12.1.5. Обслуживание запросов IOCTL
12.2. Загрузка драйвера и обращение к процедурам драйвера
12.2.1. Процедура работы с драйвером
12.2.2. Регистрация драйвера
12.2.2.1. Регистрация с помощью SCM-менеджера
12.2.2.2. Параметры драйвера в реестре
12.2.3. Обращение к рабочим процедурам
12.2.4. Хранение драйвера внутри исполняемого файла
12.3. Создание драйвера с помощью Driver Studio
12.3.1. Несколько слов о библиотеке Driver Studio
12.3.1.1. Класс KDriver
12.3.1.2. Класс KDevice
12.3.1.3. Класс Klrp
12.3.1.4. Класс KRegistryKey
12.3.1.5. Класс KLowerDevice
12.3.1.6. Классы USB
12.3.2. Другие классы Driver Studio
12.3.3. Создание шаблона драйвера с помощью Driver Studio
12.3.3.1. Шаг 1. Задание имени и пути проекта
12.3.3.2. Шаг 2. Выбор архитектуры драйвера
12.3.3.3. Шаг 3. Выбор шины
12.3.3.4. Шаг 4. Задание набора конечных точек
12.3.3.5. Шаг 5. Задание имени класса и файла
12.3.3.6. Шаг 6. Выбор функций драйвера
12.3.3.7. Шаг 7. Выбор способа обработки запросов
12.3.3.8. Шаг 8. Создание сохраняемых параметров драйвера
12.3.3.9. Шаг 9. Свойства драйвера
12.3.3.10. Шаг 10. Задание кодов IOCTL
12.3.3.11. Шаг 11. Дополнительные настройки
12.3.4. Доработка шаблона драйвера
12.3.5. Базовые методы класса устройства
12.3.6. Реализация чтения данных
12.3.7. Установка драйвера
12.3.8. Программа чтения данных
12.3.9. Чтение данных с конечных точек других типов
12.3.10. "Чистый" драйвер USB-устройства
Часть IV. Справочник
Глава 13. Формат INF-файла
13.1. Структура INF-файла
13.1.1. Секция Version
13.1.2. Секция Manufacturer
13.1.3. Секция DestinationDirs
13.1.3.1. Ключ DefaultDescDir
13.1.3.2. Ключи file-list-section
13.1.3.3. Ключ dirid
13.1.3.4. Ключ subdir
13.1.4. Секция описания модели
13.1.5. Секция xxx. AddRegw xxx. DelReg
13.1.6. Секция xxx. LogConfig
13.1.7. Секция xxx. CopyFiles
13.1.8. Секция Strings
13.1.9. Связи секций
13.2. Создание и тестирование INF-файлов
13.3. Установка устройств с помощью INF-файла
13.4. Ветки реестра для USB
Глава 14. Базовые функции Windows
14.1. Функции CreateFile и CloseHandle: открытие и закрытие объекта
14.1.1. Дополнительные сведения
14.1.2. Возвращаемое значение
14.1.3. Пример вызова
14.2. Функция ReadFile: чтение данных
14.2.1. Дополнительные сведения
14.2.2. Возвращаемое значение
14.2.3. Пример вызова
14.3. Функция Write File: передача данных
14.3.1. Дополнительные сведения
14.3.2. Возвращаемое значение
14.3.3. Пример вызова
14.4. Функция ReadFileEx. АРС-чтение данных
14.4.1. Возвращаемое значение
14.4.2. Дополнительные сведения
14.4.3. Пример вызова
14.5. Функция WriteFiieEx: АРС-передача данных
14.5.1. Возвращаемое значение
14.5.2. Пример вызова
14.6. Функция WaitForSingieObject ожидание сигнального состояния объекта
14.6.1. Возвращаемое значение
14.7. Функция WaitForMultipleObjects: ожидание сигнального состояния объектов
14.7.1. Возвращаемое значение
14.8. Функция GetOverlapped Result: результат асинхронной операции
14.8.1. Возвращаемое значение
14.9. Функция DeviceloControl: прямое управление драйвером
14.9.1. Возвращаемое значение
14.10. Функция Cancel/o: прерывание операции
14.10.1. Возвращаемое значение
14.11. Функция Query Dos Device, получение имени устройства по его DOS-имени
14.11.1. Возвращаемое значение
14.11.2. Пример вызова
14.12. Функция Define Dos Device: операции с DOS-именем устройства
14.12.1. Возвращаемое значение
14.12.2. Пример вызова
Глава 15. Структуры и функции Windows для последовательных портов
15.1. Структура настроек порта COMMCONFIG
15.2. Структура свойств порта COMMPROP
15.3. Структура тайм-аутов COMMTIMEOUTS
15.4. Структура статуса порта COMSTAT
15.5. Структура DCB
15.6. Функция BuildCommDCB: создание структуры DCB из строки
15.6.1. Дополнительные сведения
15.6.2. Возвращаемое значение
15.6.3. Пример вызова
15.7. Функция BuildCommDCBAndTimeouts: создание структуры DCB и тайм-аутов из строки
15.8. Функции SetCommBreak и ClearCommBreak: управление выводом данных
15.8.1. Возвращаемое значение
15.9. Функция ClearCommError: получение и сброс ошибок порта
15.9.1. Возвращаемое значение
15.10. Функция EscapeCommFunction: управление портом
15.10.1. Возвращаемое значение
15.11. Функции GetCommMask и SetCommMask: маска вызова событий
15.11.1. Возвращаемое значение
15.12. Функция WaitCommEvent ожидание события СОМ-порта
15.12.1. Возвращаемое значение
15.12.2. Дополнительные сведения
15.12.3. Пример вызова
15.13. Функции GetCommConfig и SetCommConfig: конфигурирование параметров порта
15.13.1. Возвращаемое значение
15.13.2. Пример вызова
15.14. Функция CommConfigDialog: диалог конфигурирования порта
15.14.1. Возвращаемое значение
15.14.2. Дополнительные сведения
15.14.3. Пример вызова
15.15. Функция GetCommProperties: прочитать свойства порта
15.15.1. Возвращаемое значение
15.15.2. Пример вызова
15.16. Функции GetCommState и SetCommState: состояние порта
15.16.1. Возвращаемое значение
15.16.2. Пример вызова
15.17. Функции GetCommTimeouts и SetComniTimeouts: тайм-ауты порта
15.17.1. Возвращаемое значение
15.17.2. Пример вызова
15.18. Функция PurgeComm: сброс буферов порта
15.18.1. Возвращаемое значение
15.18.2. Пример вызова
15.19. Функция SetupComm: конфигурирование размеров буферов
15.19.1. Возвращаемое значение
15.20. Функции GetDefaultCommConfig и SetDefaitltCommConfig: настройки порта по умолчанию
15.20.1. Возвращаемое значение
15.21. Функция TransmitCommChar. передача специальных символов
15.21.1. Возвращаемое значение
15.22. Функция GetCommModemStatus: статус модема
15.22.1. Возвращаемое значение
15.22.2. Пример вызова
15.23. Функция EnumPorts: перечисление портов
15.23.1. Дополнительные сведения
15.23.2. Возвращаемое значение
15.23.3. Пример вызова
Глава 16. Структуры и функции Windows Setup API
16.1. Функция Setup DiGetCiassDevs: перечисление устройств
16.1.1. Возвращаемое значение
16.2. Функция SetupDiDestroyDevicelnfoList освобождение блока описания устройства
16.2.1. Возвращаемое значение
16.3. Функция SetupDiEnumDevicelnterfaces: информация об устройстве
16.3.1. Возвращаемое значение
16.4. Функция SetupDiGetDevicelnterfaceDetaii: детальная информация об устройстве
16.5. Функция SetupDiEnumDevicelnfo: информация об устройстве
16.6. Функция SetupDiGetDeviceRegistryProperty: получение Plug and Play свойств устройства
16.7. Функция CM_Get_DevNode_Status: статус устройства
16.8. Функция CM_Request_Device_Eject безопасное извлечение устройства
Глава 17. Структуры и функции Windows HID API
17.1. Функция HidD_Hello: проверка библиотеки
17.2. Функция HidD_JetHidGuid: получение GUID
17.3. Функция HidD_GetPreparsedData: создание описателя устройства
17.4. Функция HidD_EreePreparsedData: освобождение описателя устройства
17.5. Функция HidD_Get Feature: получение Feature-репорта
17.6. Функция HidD_SetFeature: передача Feature-репорта
17.7. Функция HidD_GetNumlnputBuffers: получение числа буферов
17.8. Функция HidD_SetNumlnputBuffers: установка числа буферов
17.9. Функция HidD_GetAttributes: получение атрибутов устройства
17.10. Функция HidD_GetManufacturerString. получение строки производителя
17.11. Функция HidD_GetProductString получение строки продукта
17.12. Функция HidD_GetSerialNumberString. получение строки серийного номера
17.13. Функция HidD_GetIndexedString. получение строки по индексу
17.14. Функция HidD_Jetlnput Report получение Input-репорта
17.15. Функция HidD_SetOutputReport. передача Output-репорта
17.16. Функция HidP_GetCaps: получение свойств устройства
17.17. Функция HidP_MaxDataListLength: получение размеров репортов
17.18. Функция HidD_FIushQueue: сброс буферов
17.19. Функция HidP_GetLinkColiectionNodes: дерево коллекций
17.20. Функции HidP_GetScaledUsageValue u HidP_SetScaledUsage Value: получение и задание преобразованных значений
17.21. Функция HidF_MaxUsageListLength: размер буфера для кодов клавиш
17.22. Функция HidP_UsageListDifference: различие между массивами
Приложения
Приложение 1. Дополнительные функции
Приложение 2. Компиляция примеров в других версиях Delphi
Приложение 3. Таблица идентификаторов языков (LangID)
Приложение 4. Таблица кодов производителей (Vendor ID, Device ID)
Приложение 5. Как создать ярлык Device Manager
Приложение 6. Часто задаваемые вопросы
Приложение 7. Описание компакт-диска
Литература
Предметный указатель