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.

avr-usb-mega16-IMG_8082.JPG hntd-tiny45-8173.jpg metaboard-IMG 1402

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 через такой переходник.

Android-Findusbdev-connect-AVR-USB-MEGA16

Для управления устройством 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); 

//А так запрашивается статус устройства USB с помощью API Android
// (код работает в ПО хоста под управлением Android). connection.controlTransfer(USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, CUSTOM_RQ_GET_STATUS, 0, 0, buffer, buffer.length, 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" >
< uses-feature android:name="android.hardware.usb.host" /> < uses-sdk android:minSdkVersion="12" android:targetSdkVersion="19" />
< application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > < activity android:name="com.setled.MainActivity" android:label="@string/app_name" > < intent-filter > < action android:name="android.intent.action.MAIN" /> < category android:name="android.intent.category.LAUNCHER" /> < intent-filter > < action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> < /intent-filter > < meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter"
/> < /activity > < /application >
< /manifest >

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.

5. Добавьте функцию usbOpenDevice, которая будет выполнять основную работу по подключению к нужному устройству.

private boolean usbOpenDevice ()
{
   boolean opened = false;
try { usbmanager = (UsbManager) getSystemService(Context.USB_SERVICE); HashMap < String, UsbDevice > deviceList = usbmanager.getDeviceList(); Iterator< UsbDevice > deviceIterator = deviceList.values().iterator(); //Цикл по обнаруженным устройствам. while(deviceIterator.hasNext()) { usbdev = deviceIterator.next(); if (usbmanager.hasPermission(usbdev)) { //Вывод информации об устройстве. clog(usbdev.toString()); //Цикл по интерфейсам устройства. for(int ifidx=0; ifidx<usbdev.getInterfaceCount(); ifidx++) { usbif = usbdev.getInterface(ifidx); clog(usbif.toString()); //Цикл по конечным точкам интерфейса. for(int epidx=0; epidx<usbif.getEndpointCount(); epidx++) { usbep = usbif.getEndpoint(epidx); clog(usbep.toString()); clog(""); opened = true; } } } else { clog("Доступ к устройству USB запрещен."); } } } catch (Exception e) { clog("Failed to parse devices.xml: " + e.getMessage()); clog("Устройство USB HID не найдено."); } return opened; }

Примечания к коду usbOpenDevice: функция работает с глобальными переменными usbmanager, usbdev, usbif, usbep. Если удалось открыть устройство USB, то возвращает true, и переменные находятся в рабочем состоянии, готовые к работе. Иначе будет возвращено false. Для опроса устройств, интерфейсов и конечных точек используются циклы, которые строго говоря применять необязательно, так как у нас всегда только одно устройство, и мы заранее точно знаем его параметры. Процедура clog выводит информационные сообщения в текстовую консоль, и её использование необязательно. Метод hasPermission класса UsbManager определяет, получено ли от пользователя разрешение на подключение к устройству или нет. Окно на запрос разрешения выводится примерно в таком виде:

Android-USB-HID-ask-permission

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);
   }
}
public void cbClick(View v) { int ledstate = cb.isChecked()?1:0; setled(ledstate); cb.setChecked(getledstate()); }

Ниже показан интерфейс программы Android и соответствующее состояние светодиода на макетной плате.

Android-AVR-USB-MEGA16-LED-control

Подобное совместное использование Android и самодельного простейшего USB устройства открывает перед радиолюбителем широкие перспективы. Можно самому создать малогабаритное устройство с автономным питанием, которое снабжено удобным дисплеем и интерфейсом управления. К примеру, можно сделать переносной программатор, пульт управления освещением, устройство измерения или генерации сигналов, и многое другое.

[Ссылки]

1. Android как хост USB.
2. Как сделать для смартфона Android кабель microUSB OTG.
3. 140419android-USB-devices-access.zip - проекты Eclipse с примерами управления макетной платой AVR-USB-MEGA16 из Android, проект устройства USB HID с исходными кодами и готовыми прошивками.
4. Как использовать библиотеку libusb в Visual Studio (управление макетной платой AVR-USB-MEGA16).