1i7 (1i7) wrote,
1i7
1i7

Categories:

ChipKIT и Ётафон: близкие контакты

Продолжаем дружить ChipKIT и Yotaphone: в прошлый раз выбрали оборудование, сейчас всё остальное сделаем сами.

ChipKIT Max32 и Yotaphone from 1i7 on Vimeo.



Исходники:
прошивка для платы ChipKIT: chipkit_usbhost_android
приложение Андроид: AndroidUSBClient


Перевести плату в режим USB-хоста

Плата ChipKIT WF32 имеет 2 порта USB общего назначения (не считая mini USB для питания, прошивки и отладки по UART): большой вход USB как на ноутбуке или стационарном компьютере (он называется тип A) и вход micro USB.

Большой порт USB A используется для использования платы в режиме USB-хоста (USB Host) - это когда к ней подключаются внешние USB-устройства типа мышки или флешки, а сама плата рулит всем процессом - предоставляет устройству питание, общается с устройством при помощи зашитых в контроллер драйверов.

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

Стартфоны и планшеты Android при использовании подсистемы Accessory работают в режиме USB-устройства, а плата-аксессуар выступает в роли USB-хоста, поэтому на плате мы будем работать с большим входом USB A и драйверами USB-хоста.

WF32-USB.jpg

Чтобы включить его на плате ChipKIT WF32, нужно установить джампер JP10 в положение A, также нужно замкнуть перемычки JP9 и JP11, которые задают кое-какие настройки питания для USB.

WF32-USB-jumpers.jpg

На плате ChipKIT Max32 с расширением NetworkShield также присутствует 2 USB-порта общего назначения.

Max32-USB.jpg

Чтобы перевести плату в режим USB-хоста и включить порт USB A, нужно установить джампер JP4 в положение A.

Max32-USB-jumpers.jpg

Переходим к программной части.

Определим протокол

Протокол общения между смартфоном и платой: смартфон отправляет команду, плата выполняет команду и присылает ответ; команды и ответы строковые.

Весь полезный функционал определим двумя основными командами:
ledon - включить лампочку;
ledoff - выключить лампочку.

Ответы от платы:
ok - в случае успеха выполнения команды;
dontunderstand - команда не распознана.

Также введём одну вспомогательную команду letmego для разрыва соединения между платой и смартфоном, положительный ответ на неё getout (её необходимость будет объяснена далее).


Начнём с контроллера

Проект для MPIDE содержит 4 файла:
chipkit_usbhost_android/HardwareProfile.h
chipkit_usbhost_android/usb_config.h
chipkit_usbhost_android/usb_config.c
chipkit_usbhost_android/chipkit_usbhost_android.pde

Замечание: Первые 3 файла содержат разнообразные системные настройки, необходимые для того, чтобы в конечном итоге код работал корректно на нашем конкретном оборудовании. Они генерируются специальной утилитой, которую можно скачать с сайта Microchip где-то недалеко от MLA (Microchip library for applications); для наших плат серии ChipKIT я взял их из примеров ChipKIT и примеров MLA, они работают, поэтому их без особой надобности можно не трогать. Единственно место, куда может быть любопытно заглянуть - это список поддерживаемых внешних USB-устройств в файле usb_config.c - список USB_TPL (USB Embedded Host Targeted Peripheral List), в котором перечислены идентефикаторы производителей и идентификаторы устройств, с которыми сможет работать наша плата. Так как устройства с Android выпускаются большим количеством разных производителей, в этой настройке указана возможность принимать любые устройства без ограничений (vendorId=0xFFFFul, productId=0xFFFFul).


Весь основной рабочий код находится в файле chipkit_usbhost_android.pde

Подключаем необходимые библиотеки стека USB - нижний слой USB Host и верхний слой Android Host для работы конкретно в режиме аксессуара для Android'а.


#include <chipKITUSBHost.h>
#include <chipKITUSBAndroidHost.h>



Определим информацию о нашем аксессуаре: производитель, модель, описание, версия, ссылка и серийный номер - произвольные строки, заполнить по желанию; они будут переданы устройству Android в момент подключения к аксессуару.


// Информация о текущем устройстве
static char manufacturer[] = "NNTU";
static char model[] = "Android accessory basic demo";
static char description[] = "Android accessory basic demo: accepts 'ledon' and 'ledoff' commands, sends back 'ok' as reply";
static char version[] = "1.0";
static char uri[] = "https://github.com/1i7/snippets";
static char serial[] = "N/A";

ANDROID_ACCESSORY_INFORMATION myDeviceInfo = {
    manufacturer, sizeof(manufacturer),
    model, sizeof(model),
    description, sizeof(description),
    version, sizeof(version),
    uri, sizeof(uri),
    serial, sizeof(serial)
};



Вспомогательные глобальные переменные - информация о текущем подключении устройства: флаг подключено/не подключено и ссылка на устройство (если оно подключено).


BOOL deviceAttached = FALSE;
void* deviceHandle = NULL;



Обработчик событий стека USB - мы принимаем 2 главных события: EVENT_ANDROID_ATTACH - устройство подключено, и EVENT_ANDROID_DETACH - устройство отключено; здесь же и устанавливаем вспомогательные deviceAttached и deviceHandle. При желании мы также могли бы обработать событие EVENT_VBUS_REQUEST_POWER запроса питания только что подключенным устройством и например при привышении установленного максимального потребления отказать ему в питании и дальнейшей работе с платой, но это за нас уже сделано в библиотечном обработчике событий USBHost.DefaultEventHandler (он отказывает в подключении устройствам, требующим более 500мАч), главное не забыть его вызвать из нашего обработчика.



BOOL USBEventHandlerApplication( uint8_t address, USB_EVENT event, void *data, DWORD size ) {
    BOOL fRet = FALSE;

    // Вызываем обработчик событий для базового хост-контроллера
    // (это важно сделать, т.к. он также включает и выключает питание на ножках контроллера
    // по событиям EVENT_VBUS_REQUEST_POWER и EVENT_VBUS_RELEASE_POWER)
    fRet = USBHost.DefaultEventHandler(address, event, data, size);
 
    switch( event ) {
        // События от драйвера Android
        case EVENT_ANDROID_DETACH:
            Serial.println("Device NOT attached");
            deviceAttached = FALSE;
            return TRUE;
            break;

        case EVENT_ANDROID_ATTACH:
            Serial.println("Device attached");
            deviceAttached = TRUE;
            deviceHandle = data;
            return TRUE;

        default :
            break;
    }
    return fRet;
}



Старт контроллера - метод setup - инициализируем подсистему USB: регистрируем наш личный обрабочик событий USB в недрах стека, передаем туда же информацию о разработанном нами устройстве.


void setup() {
    ...
 
    // Инициализируем контроллер USB HOST:
    // Передаем ссылку на обработчик событий
    USBHost.Begin(USBEventHandlerApplication);
    // Передаем информацию об устройстве драйверу Android
    USBAndroidHost.AppStart(&myDeviceInfo);

    ...
}



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


void loop() {
    ...
   
    // Запускаем периодические задачи для поддержания стека USB в живом и корректном состоянии.
    // Следует выполнять их хотябы один раз внутри цикла или в момент, когда нужно
    // обновить внутреннее состояние контроллера USB хоста.
    USBTasks();

    ...
}


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

Продолжим Андроидом

Замечание: API для работы с аксессуарами отличаются в андроидах ветки 2.3 и 4.x в нескольких нюансах, хотя в целом очень похожи; поэтому, чтобы не засорять повествование, будем рассматривать только Андроид версии 4.x; портировать код на устаревшую ветку 2.3 при желании не составит большого труда.

Наше приложение будет состоять из одного экрана (USBClientActivity), который будет автоматически открываться системой при подключении аксессуара к смартфону. Для этого пропишем в манифесте приложения AndroidManifest.xml внутри блока activity блок intent-filter для действия USB_ACCESSORY_ATTACHED и блок meta-data для этого же действия со ссылкой на ресурс @xml/usb_accessory_filter.

AndroidUSBClient/AndroidManifest.xml

...
        <activity
            android:name="edu.nntu.usbclient.USBClientActivity"
            android:label="@string/app_name" >

...

            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
            </intent-filter>

            <meta-data
                android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/usb_accessory_filter" />
        </activity>
...


В ресурсах проекта res/xml создаем xml-файл usb_accessory_filter.xml и указываем в нем информацию об аксессуаре, с которым наше приложение умеет иметь дело: производитель, модель, версия - строковые поля, которые мы уже задавали выше в коде прошивки для контроллера.

AndroidUSBClient/res/xml/usb_accessory_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory
        manufacturer="NNTU"
        model="Android accessory basic demo"
        version="1.0" />
</resources>


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

Max32+Yotaphone-01-request permission.jpg

Android Accessory-01-request permission.png

Замечание 1: Если в диалоге установить галочку "Использовать по умолчанию для этого USB-аксессуара" и нажатии кнопки ОК, при последующих подключениях устройства экран нашего приложения будет открываться автоматически. Чтобы сбросить эту настройку, достаточно перейти в системные настройки > Приложения > Сторонние > Android USB accessory Client demo (имя приложения) и нажать кнопку "Удалить настройки по умолчанию" внизу экрана.

Замечание 2: Блоки <intent-filter> и <meta-data> для действия USB_ACCESSORY_ATTACHED в AndrodManifest.xml и файл usb_accessory_filter.xml можно и не добавлять - приложение и в этом случае сможет работать с аксессуаром, но тогда при подключении аксессуара система не будет запускать экран приложения автоматически, а вместо этого будет каждый раз показывать диалог, сообщающий, что в системе не установлены приложения, поддерживающие подключенное USB-устройство.


Весь остальной полезный рабочий код сконцентрирован всего в одном файле, описывающем поведение экрана USBClientActivity.java: AndroidUSBClient/src/edu/nntu/usbclient/USBClientActivity.java

Экран USBClientActivity состоит из 3х главных элементов:
1) Наверху текстовое поле с информацией о подключенном аксессуаре,
2) Две кнопки "Включить лампочку" и "Выключить лампочку" для отправки команд leoon и ledoff на устройство,
3) Внизу вспомогательное текстовое поле для вывода отладочной информации в процессе работы приложения (т.к. во время запуска приложения на смартфоне, его USB-шнур воткнут в плату аксессуара, а не в рабочий компьютер со средой разработки Эклипс и adb, видеть лог из System.out мы так просто на компьютере не сможем, поэтому такое текстовое поле на время разработки будет кстати).

Подключаем классы для работы с USB-аксессуарами (пакеты android.hardware.usb для Android 4.x, для младших андроидов полный путь будет другой, но мы их уже не рассматриваем).


import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;


И готовим ссылки на объекты для работы с USB-устройсвами внутри нашего класса - UsbManager  и UsbAccessory.


    private UsbManager usbManager;
    private UsbAccessory usbAccessory;


Теперь наша первоочередная задача - получить актуальную ссылку на объект UsbAccessory, через которую мы получим возможность общаться с подключенным аксессуаром.

При создании экрана в onCreate сразу получаем ссылку на системный объект UsbManager, через который мы сможем осуществлять доступ к подсистеме USB.


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        // Системные штуки
        usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

        ...
    }


В методе onResume (вызывается каждый раз, когда окно приложения получает фокус - при первом создании после onCreate, или если пользователь переключил задачу на другое приложение, а потом вернулся) запросим актуальную информацию о подключенных аксессуарах - получим список из UsbManager: если он пустой, аксессуаров нет, ничего не делаем; если не пустой, то первый элемент и будет наш аксессуар, т.к. больше одного аксессуара система Андроид в текущей реализации не поддерживает.


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

        // Попробуем подключиться к аксессуару, если он подсоединен.
        if (usbAccessory == null) {
            // Получить список доступных аксессуаров из истемы.
            final UsbAccessory[] accessories = usbManager.getAccessoryList();
            // Максимальное число подключенных аксессуаров в текущей реализации
            // Android - 1, поэтому можем просто брать 1й элемент, если он есть.
            final UsbAccessory accessory = (accessories == null ? null
                    : accessories[0]);
            connectToAccessory(accessory);
        }

        updateViews();
    }


В том случае, если аксессуар нашелся, пробуем к нему подключиться уже в нашем собственном методе connectToAccessory. В первую очередь проверяем, разрешен ли у нас к нему доступ (если пользователь нажал кнопку ОК в предыдущем системном диалоге, то разрешен). Если разрешен, то будем пробовать открыть двусторонний канал связи (далее в методе openAcessory).



    /**
     * Пробует подключиться к указанному аксессуару. Открывает канал
     * коммуникации, если все хорошо; при необходимости запрашивает разрешение
     * пользователя.
     *
     * @param accessory
     * аксессуар для подключения
     */
    private void connectToAccessory(UsbAccessory accessory) {
        if (accessory != null) {
            if (usbManager.hasPermission(accessory)) {
                debug("connectToAccessory: has permission => openAccessory");
                openAccessory(accessory);
            } else {
                ...
            }
        } else {
            debug("connectToAccessory: no accessories found");
        }
    }


Замечание 3: Если на экране подключения устройства нажать вместо ОК Отмена, а потом открыть приложение вручную иконкой с рабочего стола, вызов usbManager.hasPermission() в этом месте вернет false и в этом случае просто так открыть аксессуар не получится - нужно будет запросить у пользователя разрешение еще раз. О том, как это сделать, ниже в дополнениях.

А пока уже можем получить информацию об аксессуаре и вывести её на экран.


txtStatus.setText(getString(R.string.connected_to_accessory)
        + ":\n" + " manufacturer: "
        + accessory.getManufacturer() + "\n" + " model: "
        + accessory.getModel() + "\n" + " description: "
        + accessory.getDescription() + "\n" + " version: "
        + accessory.getVersion() + "\n" + " serial: "
        + accessory.getSerial() + "\n" + " uri: "
        + accessory.getUri());



Max32+Yotaphone-02-connected.jpg


Android Accessory-02-connected.png



продолжение следует.

подсветка синтаксиса.
Tags: android, chipkit, типовые задачи
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments