文章摘要:

本文主要描述了基于Andorid平台OTG模式下,对USB设备的访问,适用于能枚举成功的所有USB设备,以USB原始接口进行访问;

基于从所周知的原因,官方的参考文档我们应该是打不开的,所以就把资料整理一下并进行了测试,主要目的是以后自己找起来方便,如果能帮到你们,也是很开心的一件事。

官方参考文档:
如果你能打开以下链接的话,建议你看官方的参考文档:
http://developer.android.com/guide/topics/connectivity/usb/host.html


测试平台:Android 5.1 (魅蓝Note3)
开发环境:Android Studio 2.2.2
minSDK:APK12
硬件设备:圈圈教你玩USB--自定义HID设备


新建一个项目,设置最低运行版本为API12(Andorid3.1);


获取USB管理服务

// 获取USB管理服务
manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (manager == null)
{
    return;
}

获取设备

// 获取设备列表(正常情况下,数量应该大于0)
HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); 
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

show_log("DeviceList:" + String.valueOf(deviceList.size()));
   
// 查询设备列表,找出所有设备
while (deviceIterator.hasNext()) {
    UsbDevice device = deviceIterator.next(); 
    show_log("VID:" + String.format("%04X    ", device.getVendorId())
            + "PID:" + String.format("%04X", device.getProductId())
    );

    // 进行设备匹配
    if (device.getVendorId() == 0x8888 && device.getProductId() == 0x0006) {
        mUsbDevice = device;        // 记录设备,后续进行操作
        show_log("找到指定设备");
    }

    // 查找接口和端点,采用方法(函数)来实现;
    findIntfAndEpt();        
}

注意事项:
这里的show_log是我自己定义的一个调试输出函数,你也可以设备成你自己的调试输出,不重要,删除也不影响运行。


查找接口与端点

private UsbEndpoint epOut;
private UsbEndpoint epIn;

// 寻找接口和分配结点
private void findIntfAndEpt()
{
    if (mUsbDevice == null)
    {
        show_log("Error: 没有找到设备");
        return;
    }

    // 获取设备接口数(多个接口时,需在判断接口类型)
    // 本设备只有一个接口,在这个接口上有两个端点,OUT 和 IN
    for (int i = 0; i < mUsbDevice.getInterfaceCount(); i++)
    {
        UsbInterface intf = mUsbDevice.getInterface(i);
        mInterface = intf;
        break;
    }

    // 通过接口来查找端点,并检查权限
    if (mInterface == null) {
        show_log("Error: 没有找到接口");
        return;
    }

    UsbDeviceConnection connection = null;

    // 判断是否有权限
    if (!manager.hasPermission(mUsbDevice)) {
        show_log("Error: 没有访问权限");
        return;
    }

    // 打开设备,获取UsbDeviceConnection对象
    connection = manager.openDevice(mUsbDevice);
    if (connection == null)
    {
        return;
    }

    if (!connection.claimInterface(mInterface, true))
    {
        connection.close();
        return;
    }
    show_log("找到接口");
    mDeviceConnection = connection;

    show_log("端点数:" + mInterface.getEndpointCount());
    // 设置端点
    for(int i = 0; i < mInterface.getEndpointCount(); i++ ) {
        //if (mInterface.getEndpoint(1) != null)

        // 端点地址,最高位表示方向
        if((mInterface.getEndpoint(i).getAddress() & 0x80) == 0x00) {
            epOut = mInterface.getEndpoint(i);
            show_log("EpOut = " + String.format("%02X ", epOut.getAddress()));
        }
        else {
            epIn = mInterface.getEndpoint(i);
            show_log("EpIn = " + String.format("%02X ", epIn.getAddress()));
        }
    }

    
}

注意事项:

1.扫描设备不需要权限,但打开设备就需要权限了;
2.如果设备有多个接口,则需要通过接口类型(class)来识别不同接口;


权限设置

可以直接指定权限,貌似比较麻烦,还有一种就是采用意图过滤器方式来指明设备,意图过滤器会自动注册相关权限。

修改AndroidManifest.xml文件,加入以下代码:

<manifest .....

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk android:minSdkVersion="12" />

    <application ..../>
        <activity ..../>
            <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/device_filter"/>

新建xml目录并建立device_filter.xml,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resource>
    <usb-device vendor-id="0x8888" product-id="6" />
</resource>

注意事项:
vendor-id和product-id支持16进制格式;


数据发送与接收

如果之前设备查找没问题,以下就可以进入激动人心的读写操作了,USB传输一共分为4种,本次测试设备采用的中断传输(貌似一般的HID设备都是采用中断传输模式;

中断输出(发送)

byte[]init_data = {0,0x55,0,0,0,0,0,0};
        
//使用中断传输发送数据(不包括报告id)
ByteBuffer sendbuf = ByteBuffer.wrap(init_data);
        
//初始化请求,endpoint为IN中断端点
UsbRequest request = new UsbRequest();

// 通过端点类型自动识别数据方向
request.initialize(mDeviceConnection, epOut);    
request.queue(sendbuf, 8);
          
show_log("数据发送成功.");

中断输入(接收)

int maxPacketSize  = endpoint.getMaxPacketSize(); 
ByteBuffer bBuffer = ByteBuffer.allocate(maxPacketSize); 

//使用中断传输接收数据 
UsbRequest request = new UsbRequest(); 
request.initialize(connection, epIN);        // 初始化
request.queue(bBuffer, maxPacketSize);       // 请求队列   
while(connection.requestWait() == request);  // 等待完成

注意事项:
基于读接收操作的相关代码要放在线程中处理;
这种方式完全没有考虑HID的报告描述符,而是直接写中断端点;


USB串口工作说明


批量传输

函数原型

bulkTransfer(UsbEndpoint endpoint, // 端点号,通过端点号来决定输入或是输出
            byte[] buffer,         // 数据缓冲区
            int length,            // 数据长度
            int timeout) ;         // 超时时间

应用示例

ret = mDeviceConnection.bulkTransfer(epOut,     
                                     sendbuf,   
                                     8,    
                                     5000);  

控制传输

函数原型:

controlTransfer(int requestType,    
                int request, 
                int value, 
                int index, 
                byte[] buffer, 
                int length, 
                int timeout) ;

requestType:请求类型,bit[7]为数据方向,bit[6:5]请求类型,bit[4:0]

应用示例:

// 设置串口参数
int buadrate = 115200;
byte[] line_coding = new byte]7];
line_coding[0] = (byte)((buadrate) & 0xFF);
line_coding[1] = (byte)((buadrate >> 8)  & 0xFF);
line_coding[2] = (byte)((buadrate >> 16) & 0xFF);
line_coding[3] = (byte)((buadrate >> 24) & 0xFF);
line_coding[4] = 0;    // stop bits  
line_coding[5] = 0;    // parity
line_coding[6] = 8;    // data bits

mDeviceConnection.controlTransfer(
        0x21,             // bmRquestType: OUT(0),类请求(01) 专属接口(00001)
        0x20,             // bRequest: SET_LINE_CODE
        0,                // wValue,
        0,                // wIndex:接口索引号
        line_coding,7,    // Data,wLength
        5000);

注意事项:
此模式仅适用于CDC类的串口,不适用于自定义类的(如CP210x等)串口的波特率设置;


后记:

根据文档描述,基于Android 3.1以及之后的版本中,已经开始对OTG支持,但是实际测试过程中发现,有的设备会出现扫描不到设备的情况,大部分情况是由于系统缺少了权限文件所导致,需要在/etc/permissions目录下新建android.hardware.usb.host.xml文件;
内容如下:

<permissions>  
 <feature name="android.hardware.usb.host"/>  
</permissions> 

另外一篇比较详细的文章