AVR-USB-MEGA16: управление устройством USB HID из Android |
![]() |
Добавил(а) microsin | |||
В этом примере будет происходить управление светодиодом на макетной плате AVR-USB-MEGA16 из ПО хоста, работающего на Android. В память микроконтроллера макетной платы ATMega32A прошит код устройства USB HID, которое принимает простейшие команды от хоста и в соответствии с командами зажигает или гасит светодиод, и передает его состояние. [Firmware устройства USB HID] Firmware для микроконтроллера ATmega32A взято готовое, из примеров библиотеки V-USB (исходный код проекта и бинарники для него можно скачать в пакете [3], см. папку avrusb-20080513\examples\hid-custom-rq). Это firmware можно скомпилировать и для любого другого микроконтроллера AVR, например для макетной платы metaboard, AVR-USB-TINY45.
USB HID, который работает в firmware микроконтроллера, поддерживает только одну конечную точку - control endpoint, её также называют конечной точкой по умолчанию. Эта конечная точка имеет номер 0, и она обязательно должна быть во всех USB-устройствах, потому что через неё происходит в основном обмен служебными данными. В нашем примере она используется также для передачи полезных данных - состояния светодиода, и команд для его переключения. Для передачи этих данных в наше устройство USB HID используются короткие управляющие передачи, что поддерживает иерархия Java классов. Кроме консольных версий ПО хоста, работающих про Windows, Linux, FreeBSD, для этого firmware также есть и GUI-версия под Windows [4]. И вот теперь настала очередь платформы Android. [Реализация USB host в Android] Поддержка устройств USB (реализация хоста USB) появилась в Android начиная с версии Android 3.1.x (API Level 12 или выше). Более старые системы не поддерживают режим хоста USB, т. е. они принципиально неспособны опознавать USB-устройства и работать с ними. Кроме того, аппаратура в Android должна поддерживать стандарт OTG (в настоящее время такая поддержка есть почти во всех современных телефонах и планшетах). Для подключения к Android, у которых нет на корпусе коннектора Type A Female (это обычное дело, если у Вас не планшет, а телефон или смартфон Android), потребуется переходник Android OTG micro usb cable [2]. Такие переходники почти задаром раздают на dealextreme, aliexpress, ebay, и его также можно купить на рынке или в любом магазине, торгующим мобильными телефонами. На фото показана макетная плата AVR-USB-MEGA16, подключенная к смартфону Samsung Galaxy Note GT-N7000 через такой переходник. Для управления устройством USB в Android всегда используются классы UsbManager, UsbDevice, UsbInterface, UsbEndpoint, UsbDeviceConnection, которые хорошо документированы и просты в применении. Получается следующая иерархия использования классов: UsbManager -> UsbDevice -> UsbInterface -> UsbEndpoint -> UsbDeviceConnection Т. е. сначала запускается UsbManager, с помощью которого получают доступ к определенному нужному устройству на шине USB (устройство обычно выбирается по его описанию, например по параметрам VID и PID). Найденное нужное устройство будет представлено классом UsbDevice. Далее происходит опрос UsbDevice на наличие в нем нужного интерфейса, и запускается экземпляр класса UsbInterface. Далее UsbInterface опрашивается на наличие в нем нужных конечных точек, и в результате появляется один или несколько экземпляров класса UsbEndpoint (в нашем примере конечная точка одна-единственная, control endpoint 0). Далее на нужной конечной точке запускается соединение, и получается экземпляр класса UsbDeviceConnection. Класс UsbDeviceConnection имеет методы для реализации обмена данными с устройством USB - методы bulkTransfer и controlTransfer (в нашем примере для передачи данных в обе стороны используется только метод controlTransfer). Примечание: использование процедуры controlTransfer удивительным образом совпадает с синтаксисом вызовов кроссплатформенной библиотеки LibUSB. Именно благодаря этому факту удалось быстро разобраться с программированием на Android устройств USB - был взят готовый код ПО хоста примера из библиотеки V-USB (см. [3], файл avrusb-20080513\examples\hid-custom-rq\commandline\set-led.c), и константы из LibUSB, и вызовы функций LibUSB были портированы в вызовы API Android. Сравните: //Так запрашивается статус устройства USB с помощью библиотеки LibUSB // (код работает в ПО хоста под управлением Windows, Linux, FreeBSD или MAC OS). usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_GET_STATUS, 0, 0, buffer, sizeof(buffer), 5000); Согласитесь, разница минимальная, что очень радует, поскольку это позволяет довольно просто портировать на Android программное обеспечение USB с других платформ. Помимо API для обмена данными с устройствами USB в системе Android для получения доступа к устройству USB применяется специальная система разрешений, т. е. без получения явного разрешения от пользователя подключиться к устройству USB для передачи данных нельзя. Все эти вопросы будут кратко рассмотрены далее в реализации примера программы Android. [Пошаговая реализация примера программы Android, управляющая устройством USB HID] Рассмотрим на конкретном простом примере создание приложения Android, которое будет обмениваться данными с простейшим устройством USB HID на макетной плате AVR-USB-MEGA16. Программы были написаны в среде Eclipse, и испытывались на смартфоне Samsung Galaxy Note GT-N7000, версия Android 4.1.2 (API Level 16). Готовый проект Eclipse можете скачать в архиве по ссылке [3] (см. также файл readme.txt архива). 1. Создайте в Eclipse проект приложения Android при помощи стандартного мастера. На этом шаге выберите минимальную версию целевого SDK API 12. Target SDK и Compile With можете выставить по максимуму, на самую последнюю поддерживаемую версию Android. Так же задайте, как обычно, имя приложения, имя проекта и имя пакета. 2. Бросьте на форму приложения чекбокс, который будет отображать состояние светодиода на макетной плате, и одновременно будет управлять им (погашен светодиод или горит). Сделайте для чекбокса обработчик клика, в обработчике мы будем переключать светодиод на макетной плате. 3. Создайте файл usb_device_filter.xml, и положите его в папку res/xml проекта. Этот файл предоставляет параметры описания устройства USB HID (фильтр по VID и PID), и он используется для получения разрешения на подключение к устройству. Благодаря использованию этого фильтра наша программа будет реагировать только на подключение нужного устройства USB, все остальные устройства будут игнорироваться. Вот содержимое файла usb_device_filter.xml: < ?xml version="1.0" encoding="utf-8"? > < resources > < usb-device vendor-id="5824" product-id="1503" /> < /resources > Атрибуты vendor-id и product-id задают параметры VID и PID, которые используются в устройстве USB HID макетной платы AVR-USB-MEGA16. Примечание: обратите внимание, что значения VID и PID указаны здесь не в шестнадцатеричном, а в десятичном виде. Указанные здесь значения VID и PID должны совпадать со значениями, используемыми в проекте firmware нашего устройства USB HID на макетной плате AVR-USB-MEGA16 (см. файл avrusb-20080513\examples\hid-custom-rq\firmware-mega\usbconfig.h, параметры USB_CFG_VENDOR_ID и USB_CFG_DEVICE_ID. Значения VID 0x16C0 и PID 05DF переведены в десятичные значения 5824 и 1503 соответственно). 3. Добавьте в AndroidManifest.xml секцию < uses-feature android:name="android.hardware.usb.host" />, эта секция задает использование в программе возможностей хоста USB. Добавьте также секции intent-filter и meta-data, отвечающие за определение события подключения устройства USB и получение разрешения на его использование от пользователя. В результате должен получиться примерно такой файл AndroidManifest.xml: < ?xml version="1.0" encoding="utf-8"? > <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.setled" android:versionCode="1" android:versionName="1.0" > 4. Добавьте в класс MainActivity глобальные определения констант и глобальные переменные классов UsbManager, UsbDevice, UsbInterface, UsbEndpoint. Константы и экземпляры этих классов будут использоваться для доступа к устройству USB HID. public class MainActivity extends Activity { private static final int USB_TYPE_VENDOR = (0x02 << 5); private static final int USB_RECIP_DEVICE = 0x00; private static final int USB_ENDPOINT_IN = 0x80; private static final int USB_ENDPOINT_OUT = 0x00; /* Этот запрос устанавливает состояние светодиода LED макетной платы * AVR-USB-MEGA16. Используется управляющая передача вывода (Control-OUT). * Запрашиваемый статус передается в поле "wValue" управляющей передачи * (control transfer). Данные OUT не передются. Бит 0 в младшем байте * wValue управляет состоянием светодиода. */ private static final int CUSTOM_RQ_SET_STATUS = 1; /* Получает текущее состояние светодиода макетной платы AVR-USB-MEGA16. * Используется управляющая передача ввода (Control-IN). * Управляющая передача (control transfer) в 1 байте фазы данных бит 0 * передает хосту текущее состояние светодиода. */ private static final int CUSTOM_RQ_GET_STATUS = 2; CheckBox cb; //чекбокс для состояния светодиода UsbManager usbmanager; UsbDevice usbdev; UsbInterface usbif; UsbEndpoint usbep; ... Примечание: значения констант были скопированы из файла usb.h библиотеки LibUSB и из файла requests.h avrusb-20080513\examples\hid-custom-rq\firmware-mega\requests.h, см. [3]. Константы используются в вызовах controlTransfer при обмене данными с устройством USB. private boolean usbOpenDevice () { boolean opened = false; Примечания к коду usbOpenDevice: функция работает с глобальными переменными usbmanager, usbdev, usbif, usbep. Если удалось открыть устройство USB, то возвращает true, и переменные находятся в рабочем состоянии, готовые к работе. Иначе будет возвращено false. Для опроса устройств, интерфейсов и конечных точек используются циклы, которые строго говоря применять необязательно, так как у нас всегда только одно устройство, и мы заранее точно знаем его параметры. Процедура clog выводит информационные сообщения в текстовую консоль, и её использование необязательно. Метод hasPermission класса UsbManager определяет, получено ли от пользователя разрешение на подключение к устройству или нет. Окно на запрос разрешения выводится примерно в таком виде: 6. Добавьте функцию getledstate, которая будет запрашивать от макетной платы AVR-USB-MEGA16 состояние светодиода. private boolean getledstate() { boolean ledstate = false; byte buffer[] = new byte[4]; if (usbOpenDevice()) { UsbDeviceConnection connection = usbmanager.openDevice(usbdev); connection.claimInterface(usbif, true); connection.controlTransfer(USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_GET_STATUS, 0, 0, buffer, buffer.length, 5000); ledstate = (buffer[0]!=0)?true:false; } return ledstate; } Перед непосредственным вызовом обмена данными запускается функция usbOpenDevice, которая устанавливает соединение с нужным устройством USB. Состояние светодиода возвращается в младшем байте буфера - если бит 0 в этом байте равен 1, то это означает горение светодиода, а если 0, то светодиод погашен. 7. Добавьте функцию setled, которая будет включать и выключать светодиод на макетной плате AVR-USB-MEGA16, и добавьте вызов этой функции в обработчик клика на чекбоксе: private void setled(int value) { if (usbOpenDevice()) { UsbDeviceConnection connection = usbmanager.openDevice(usbdev); connection.claimInterface(usbif, true); connection.controlTransfer(USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, value, 0, null, 0, 5000); } } Ниже показан интерфейс программы Android и соответствующее состояние светодиода на макетной плате. Подобное совместное использование Android и самодельного простейшего USB устройства открывает перед радиолюбителем широкие перспективы. Можно самому создать малогабаритное устройство с автономным питанием, которое снабжено удобным дисплеем и интерфейсом управления. К примеру, можно сделать переносной программатор, пульт управления освещением, устройство измерения или генерации сигналов, и многое другое. [Ссылки] 1. Android как хост USB. |