背景介绍

    Android系统支持多种USB外围设备。根据Android设备在USB通信中充当的角色,可以将Android USB通信分为主机模式(Host Mode)和配件模式(Accessory Mode)两种模式。

    主机模式是指Android设备充当USB主机并为总线供电。此模式下,Android设备需支持USB主机功能或OTG(On-The-Go)功能,此时Android设备的USB主机称为USB嵌入式主机EH(Embedded Host)。与PC上的USB主机相比,EH设备可能无法为连接到其总线上的未识别外围设备加载驱动程序,因此它们对其目标外围设备列表TPL(Target Peripheral List)进行了定义。这些外围USB设备大部分为HID设备(Human Interface Device)、BOMS设备(Bulk Only Mass Storage,如U盘)和CDC设备(Comm-unication Device Class,USB通信设备类,如打印机),其驱动程序已存在于Android平台的系统中(Linux Kernel),因此Android设备可以与其直接通信。[Android APP实现参考]https://developer.android.com/guide/topics/connectivity/usb/host.html

    配件模式是指Android设备充当USB从机,外部设备充当主机并为总线供电。此模式下,外部USB设备称为Android配件。该模式为不具备主机功能的Android设备提供与USB设备交互的能力。Android设备和Android配件都必须支持AOA协议。[Android APP实现参考]https://developer.android.com/guide/topics/connectivity/usb/accessory.html

usb_model

协议介绍

    Google 在 Android 3.1 (API Level 12) 推出了 Android Open Accessory 协议。这个协议是用来支持外部 USB 设备做为主机连接 Android 设备的底层基础。当 Android 设备进入 accessory 模式后,外部设备将做为视为主机为 bus 供电,并且和 Android 设备交互。

注:accessory 模式,最终依赖于设备的硬件,而不是所有设备都支持配件模式。
Android Open Accessory (AOA) 会在省电模式下失效。经过咨询 USB 的相关同事,了解到省电模式下连接 USB 线仅为充电功能。

AOA有两个版本,支持不同类型的通信:

AOAv2是兼容AOAv1的。AOA目前不支持同时进行AOA和MTP的连接。从AOA切换到MTP的话,必须先断开USB设备,然后再连接USB设备的时候选择MTP。

实现步骤

    一个Android USB配件必须遵循AOA协议,它定义了配件如何检测并与Android设备建立通信。

    在一般情况下,配件应进行以下步骤:

  1. 等待并检测连接的设备
  2. 确定设备支持配件模式
  3. 如果需要,尝试启动设备的配件模式
  4. 如果它支持Android配件模式,建立与设备的通信
确定设备支持配件模式

当Android手机连接,它可以在三种状态之一:

在初始连接时,该配件应检查连接设备的vendor ID和product ID。 vendor ID应符合谷歌的ID(0x18D1),并且如果该设备已在配件模式(状态A),product ID应该存在下表中。 如果是这样的话,该配件现在可以在自己的通信协议下批量传输端点来与设备建立通信。 没必要在配件模式启动设备。

Version Product ID Communication
AOAv1 0x2D00 accessory
0x2D01 accessory + adb
AOAv2 0x2D02 audio
0x2D03 audio + adb
0x2D04 accessory + audio
0x2D05 accessory + audio + adb

表1

尝试开启配件模式

第一步,发送51控制请求(“Get Protocol”),判断该设备是否支持AOA协议。 支持该协议,则返回一个非0的数字,它代表了该设备支持的版本协议。返回1表示支持AOAv1,返回2表示支持AOAv2。该请求的endpoint为0。

requestType:    USB_DIR_IN | USB_TYPE_VENDOR
request:        51
value:          0
index:          0
data:           protocol version number (16 bits little endian sent from the
                device to the accessory)

第二步,如果该设备返回一个正确的协议版本,发送标识字符串(string ID)信息到该设备。 这一信息使该设备找出一个支持该协议的应用程序,如果不存在相应的应用程序,还将会为用户提供一个URL下载。 该请求的endpoint为0。

requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
request:        52
value:          0
index:          string ID
data            zero terminated UTF8 string sent from accessory to device

标识字符串(string ID)信息如下

manufacturer name:  0
model name:         1
description:        2
version:            3
URI:                4
serial number:      5

第三步,发送控制请求(control request),要求设备以配件模式启动。 该请求的endpoint为0。

requestType:    USB_DIR_OUT | USB_TYPE_VENDOR
request:        53
value:          0
index:          0
data:           none

可以参考Accessory Development Kit 2011 的源代码(/adk1/board/AndroidAccessory/AndroidAccessory.cpp)

#define USB_ACCESSORY_VENDOR_ID         0x18D1
#define USB_ACCESSORY_PRODUCT_ID        0x2D00
#define USB_ACCESSORY_ADB_PRODUCT_ID    0x2D01
#define ACCESSORY_STRING_MANUFACTURER   0
#define ACCESSORY_STRING_MODEL          1
#define ACCESSORY_STRING_DESCRIPTION    2
#define ACCESSORY_STRING_VERSION        3
#define ACCESSORY_STRING_URI            4
#define ACCESSORY_STRING_SERIAL         5
#define ACCESSORY_GET_PROTOCOL          51
#define ACCESSORY_SEND_STRING           52
#define ACCESSORY_START                 53

int AndroidAccessory::getProtocol(byte addr)
{
    uint16_t protocol = -1;
    usb.ctrlReq(addr, 0,
                USB_SETUP_DEVICE_TO_HOST |
                USB_SETUP_TYPE_VENDOR |
                USB_SETUP_RECIPIENT_DEVICE,
                ACCESSORY_GET_PROTOCOL, 0, 0, 0, 2, (char *)&protocol);
    return protocol;
}

bool AndroidAccessory::switchDevice(byte addr)
{
    int protocol = getProtocol(addr);
    if (protocol >= 1) {
        Serial.print("device supports protocol 1 or higher\n");
    } else {
        Serial.print("could not read device protocol version\n");
        return false;
    }

    sendString(addr, ACCESSORY_STRING_MANUFACTURER, manufacturer);
    sendString(addr, ACCESSORY_STRING_MODEL, model);
    sendString(addr, ACCESSORY_STRING_DESCRIPTION, description);
    sendString(addr, ACCESSORY_STRING_VERSION, version);
    sendString(addr, ACCESSORY_STRING_URI, uri);
    sendString(addr, ACCESSORY_STRING_SERIAL, serial);

    usb.ctrlReq(addr, 0, USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_VENDOR |
                USB_SETUP_RECIPIENT_DEVICE, ACCESSORY_START, 0, 0, 0, 0, NULL);
    return true;
}

[参考]https://github.com/GoogleChrome/chrome-app-samples/blob/master/samples/serial/adkjs/firmware/arduino_libs/AndroidAccessory/AndroidAccessory.cpp

    发出最后的控制请求后,已连接的USB设备应在总线上以配件模式重新识别,并且重新检查连接的设备。 如果该设备已成功切换到配件模式,vendor ID将会变成Google's vendor,而不是设备制造商的ID,product ID变成了<表1>中的一类product ID。 该配件可以建立与设备的通信。

    任何这些步骤之一失败了,该设备都不能支持Android配件模式,只能等待后面连接的设备了。

==USB device APIs.==

/* Setup Data Constants */

#define USB_SETUP_HOST_TO_DEVICE                0x00    // Device Request bmRequestType transfer direction - host to device transfer
#define USB_SETUP_DEVICE_TO_HOST                0x80    // Device Request bmRequestType transfer direction - device to host transfer
#define USB_SETUP_TYPE_STANDARD                 0x00    // Device Request bmRequestType type - standard
#define USB_SETUP_TYPE_CLASS                    0x20    // Device Request bmRequestType type - class
#define USB_SETUP_TYPE_VENDOR                   0x40    // Device Request bmRequestType type - vendor
#define USB_SETUP_RECIPIENT_DEVICE              0x00    // Device Request bmRequestType recipient - device
#define USB_SETUP_RECIPIENT_INTERFACE           0x01    // Device Request bmRequestType recipient - interface
#define USB_SETUP_RECIPIENT_ENDPOINT            0x02    // Device Request bmRequestType recipient - endpoint
#define USB_SETUP_RECIPIENT_OTHER               0x03    // Device Request bmRequestType recipient - other

[USB Host Shield 2.0]http://felis.github.io/USB_Host_Shield_2.0/usb__ch9_8h_source.html

不通过Android app连接AOAv2

    设计一个配件(例如,音频基座(audio dock))使用新的音频和HID支持,但并不需要在Android设备上的应用程序进行通信。 在这种情况下,用户不希望看到提示发现和连接新的可以与应用程序通信的配件的相关对话框。 为了防止这些对话框在设备和配件连接后出现,配件可以不向Android设备发送制造商和型号的名称。 如果这些字符串没有提供给Android设备,在AOA 2.0下,配件可以使用新的音频和HID支持,而不需系统试图找到一个应用程序与配件进行通信。 另外,如果没有提供这些字符串,该设备进入配件模式后,配件的USB接口是不存在在Android设备的USB配置中的。

参考资料

[Android Open Accessory (AOA)]https://source.android.com/devices/accessories/protocol.html

[Android Open Accessory Protocol 1.0]https://source.android.com/devices/accessories/aoa.html

[Android Open Accessory Protocol 2.0]https://source.android.com/devices/accessories/aoa2.html

[Android Open Accessory Protocol(中文译文)]http://blog.csdn.net/jiezhi2013/article/details/42642297

[Android Open Accessory Protocol 2.0(中文译文)]http://blog.csdn.net/jiezhi2013/article/details/42642371

[基于AOA协议实现Android设备的USB通信]http://blog.csdn.net/tianruxishui/article/details/37903841

[Linux Cross Reference]http://lxr.free-electrons.com/source/include/uapi/linux/usb/ch9.h#L50