2012-11-08 238 views
14

我是新来的USB和Android所以请原谅我,如果我没有解释清楚自己。使用Android与USB HID设备通信

我有一个USB HID设备,可以在Windows中进行通信。我正在尝试使用运行Android 3.1的Acer Iconia A500平板电脑建立通信。

我能够找到设备,枚举它,获取其唯一可用的接口,获取唯一可用的端点(0),并确定它是什么类型的端点(从设备到主机传输中断)。

我对USB规范的理解是,所有HID设备都需要具有控制端点(端点0)和中断IN端点。但似乎这里的端点0是中断端点,而不是控制端点。

然而,为了让设备枚举它,它必须成功地在控制端点上传输它的描述符数据。我推断,因此控制端点必须被找到(并使用),因为主机实际上枚举了设备。

就我所能进行的操作而言,如上所述,在应用程序级别呈现给我的唯一接口/端点是从设备到主机的中断类型。没有终端可用于我的应用从主机到设备,中断或控制。所以设备等待被告知该怎么做,主机等待设备中发生的事情。不是很刺激。请注意,此设备在连接到Windows时正确响应,例如,我能够发送包含13字节数据的报告,使设备点亮一个LED。所以它似乎符合USB HID规范。作为一种绝望行为,我曾尝试将这一个端点用作控制端点和中断OUT端点,并使用controltransfer()和UsbRequest()将数据提交给设备,在这两种情况下均无响应。

所以我的问题是:“控制传输终点是(?)被用来设置设备,为什么我无法找到&使用它?”

感谢任何见解,下面是相关的代码,如果需要的话我可以在其全部的休息:

private UsbManager mUsbManager; 
private UsbDevice mDevice; 
private UsbDeviceConnection mConnectionRead; 
private UsbDeviceConnection mConnectionWrite; 
private UsbEndpoint mEndpointRead; 
private UsbEndpoint mEndpointWrite; 

    // check for existing devices 
    for (UsbDevice device : mUsbManager.getDeviceList().values()) 
    { 
     //Need to filter for my device when other HIDs are also connected, but for now...   
     String devName = device.getDeviceName(); 
     if (DEBUG == 1){ 
     Toast.makeText(UsbHidDeviceTesterActivity.this, "My device got connected: " + devName, Toast.LENGTH_LONG).show(); 
     } 
     //mDevice = device; 
     setHIDDevice(device); 
    } 

private boolean setHIDDevice(UsbDevice device) 
{  
    UsbInterface usbInterfaceRead = null; 
    UsbInterface usbInterfaceWrite = null; 
    UsbEndpoint ep1 = null; 
    UsbEndpoint ep2 = null; 
    boolean UsingSingleInterface = true; 

    mDevice = device; 

    //This HID device is using a single interface 
    if (UsingSingleInterface) 
    { 
     //usbInterfaceRead = device.getInterface(0x00);//only 1 EP on this interface 
     usbInterfaceRead = findInterface(device); 

     //Try getting an interface at next index 
     //usbInterfaceWrite = device.getInterface(0x01);//throws exception 

     // Try using the same interface for reading and writing 
     usbInterfaceWrite = usbInterfaceRead; 

     int endPointCount = usbInterfaceWrite.getEndpointCount(); 
     if (DEBUG == 2) 
     { 
      Toast.makeText(UsbHidDeviceTesterActivity.this, "Endpoints: " + endPointCount, Toast.LENGTH_LONG).show(); 
      //Toast.makeText(UsbHidDeviceTesterActivity.this, "Interface: " + usbInterfaceRead, Toast.LENGTH_LONG).show(); 
     } 

     if (endPointCount == 1)//only getting 1 endpoint 
     { 
      ep1 = usbInterfaceRead.getEndpoint(0); 
      //As an act of desperation try equating ep2 to this read EP, so that we can later attempt to write to it anyway 
      ep2 = usbInterfaceRead.getEndpoint(0); 
     } 
     else if (endPointCount == 2) 
     { 
      ep1 = usbInterfaceRead.getEndpoint(0); 
      ep2 = usbInterfaceRead.getEndpoint(1); 
     } 
    } 

    else  // ! UsingSingleInterface 
    { 
     usbInterfaceRead = device.getInterface(0x00); 
     usbInterfaceWrite = device.getInterface(0x01); 
     if ((usbInterfaceRead.getEndpointCount() == 1) && (usbInterfaceWrite.getEndpointCount() == 1)) 
     { 
      ep1 = usbInterfaceRead.getEndpoint(0); 
      ep2 = usbInterfaceWrite.getEndpoint(0); 
     } 
     if (DEBUG == 3) 
     { 
      Toast.makeText(UsbHidDeviceTesterActivity.this, "Using Dual Interface", Toast.LENGTH_LONG).show(); 
     } 
    } 

    //because ep1 = ep2 this will now not cause a return unless no ep is found at all 
    if ((ep1 == null) || (ep2 == null)) 
    { 
     if (DEBUG == 4) 
     { 
      Toast.makeText(UsbHidDeviceTesterActivity.this, "One EP is null", Toast.LENGTH_LONG).show(); 
     } 
     return false; 
    } 

    // Determine which endpoint is the read, and which is the write 
    if (ep1.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)//I am getting a return of 3, which is an interrupt transfer 
    { 
     if (ep1.getDirection() == UsbConstants.USB_DIR_IN)//I am getting a return of 128, which is a device-to-host endpoint 
     { 
      mEndpointRead = ep1; 
      if (DEBUG == 5) 
      { 
       Toast.makeText(UsbHidDeviceTesterActivity.this, "EP1 type: " + ep1.getType(), Toast.LENGTH_LONG).show(); 
      } 
     } 
     if (ep1.getDirection() == UsbConstants.USB_DIR_OUT)//nope 
     { 
      mEndpointWrite = ep1; 
      if (DEBUG == 6) 
      { 
       Toast.makeText(UsbHidDeviceTesterActivity.this, "EP1 is a write", Toast.LENGTH_LONG).show(); 
      } 
     } 
    } 

    if (ep2.getType() == UsbConstants.USB_ENDPOINT_XFER_INT) 
    { 
     if (ep2.getDirection() == UsbConstants.USB_DIR_IN) 
     { 
      //Try treating it as a write anyway    
      //mEndpointRead = ep2; 
      mEndpointWrite = ep2; 
     } 
     else if (ep2.getDirection() == UsbConstants.USB_DIR_OUT) 
     { 
      //usbEndpointWrite = ep2; 
      mEndpointWrite = ep2; 
     } 
    } 

    //check that we should be able to read and write 
    if ((mEndpointRead == null) || (mEndpointWrite == null)) 
    { 
     return false; 
    } 
    if (device != null) 
    { 
     UsbDeviceConnection connection = mUsbManager.openDevice(device); 
     if (connection != null && connection.claimInterface(usbInterfaceRead, true)) 
     { 
      Log.d(TAG, "open SUCCESS"); 
      mConnectionRead = connection; 
      // Start the read thread 
      //Comment out while desperately attempting to write on this connection/interface 
      //Thread thread = new Thread(this); 
      //thread.start(); 

     } 
     else 
     { 
      Log.d(TAG, "open FAIL"); 
      mConnectionRead = null; 
     } 
    } 
    if (UsingSingleInterface) 
    { 
     mConnectionWrite = mConnectionRead; 
    } 
    else //! UsingSingleInterface 
    { 
     mConnectionWrite = mUsbManager.openDevice(device); 
     mConnectionWrite.claimInterface(usbInterfaceWrite, true); 
    } 
    return true; 
} 

// searches for an interface on the given USB device 
private UsbInterface findInterface(UsbDevice device) { 
    Log.d(TAG, "findInterface " + device); 
    int count = device.getInterfaceCount(); 
    if (DEBUG == 7) 
    { 
     Toast.makeText(UsbHidDeviceTesterActivity.this, "Interface count: " + count, Toast.LENGTH_LONG).show(); 
    } 

    for (int i = 0; i < count; i++) { 
     UsbInterface intf = device.getInterface(i); 
     String InterfaceInfo = intf.toString(); 
     Log.d(TAG, "Interface: " + InterfaceInfo); 
     //Class below is 3 for USB_HID 
     if (intf.getInterfaceClass() == 3 && intf.getInterfaceSubclass() == 0 && 
       intf.getInterfaceProtocol() == 0) { 
      return intf; 
     } 
     //....try just returning the interface regardless of class/subclass 
     //return intf; 
    } 

    return null; 
} 
private boolean sendControlTransfer(byte[] dataToSend) 
{ 
    synchronized (this) 
    { 
    if (mConnectionRead != null) 
    { 
     //byte[] message = new byte[13]; // or 14? 
     byte[] message = dataToSend; 
     if (DEBUG == 9) 
     { 
      Toast.makeText(UsbHidDeviceTesterActivity.this, "Sending Control Transfer", Toast.LENGTH_LONG).show(); 
     } 

     //first field ox21 is bin 00100001 which splits into 0 01 00001 for direction(1bit)/type(2b)/recipient(5b) 
     //To set direction as 'host to Device' we need 0, To set type to HID we need 11 (3), and for recipient we want 00001 
     //second field 0x09 is class specific request code, 0x09 is listed as 'reserved for future use' 
     //third field 0x200 is value 
     //int transfer = mConnectionRead.controlTransfer(0x21, 0x9, 0x200, 0, message, message.length, 0); 
     //try with type set to HID 
     int transfer = mConnectionRead.controlTransfer(0xC1, 0x9, 0x200, 0, message, message.length, 0); 
     if (DEBUG == 10) 
     { 
      Toast.makeText(UsbHidDeviceTesterActivity.this, "Transfer returned " + transfer, Toast.LENGTH_LONG).show(); 
     } 
    } 
    } 
    return true; 
} 


private boolean sendInterruptTransfer(byte[] dataToSend) 
{ 
    int bufferDataLength = mEndpointWrite.getMaxPacketSize();//The write endpoint is null unless we just copy the read endpoint 
    if (DEBUG == 12) 
    { 
     Toast.makeText(UsbHidDeviceTesterActivity.this, "Max Packet Size: " + bufferDataLength, Toast.LENGTH_LONG).show(); 
    } 

    ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1); 
    UsbRequest request = new UsbRequest(); 
    buffer.put(dataToSend); 

    request.initialize(mConnectionWrite, mEndpointWrite); 
    request.queue(buffer, bufferDataLength); 

    try 
    { 
     /* only use requestwait on a read 
     if (request.equals(mConnectionWrite.requestWait())) 
     { 
      return true; 
     } 
     */ 
    } 
    catch (Exception ex) 
    { 
     // An exception has occurred 
     if (DEBUG == 13) 
     { 
      Toast.makeText(UsbHidDeviceTesterActivity.this, "Caught Write Exception", Toast.LENGTH_LONG).show(); 
     } 
    } 

    return true; 
} 

回答

7

所以,我一直在研究类似的东西。我无法确认,但我相信正在发生的事情是:

  1. Android在枚举它的端点时未列出控制端点。它只列出其他端点。
  2. 到任何端点的连接都可以通过controlTransfer方法将控制传输发送到端点0,该方法(从api引用)“在此设备的端点零上执行控制事务”。
  3. 因此,在你的上面的代码中,我将使用第0个端点作为中断输入端点,但它仍然允许控制传输。
  4. 某人使用HID设备的例子是Missle Launcher演示,它使用的设备是带有中断端点的HID设备。
+1

@ zabuni-即使单个端点(EP0)被标识为中断端点,但如果我只是将其作为写入操作并对其执行控制传输,那么它就是正确的。 – DasBoos

9

您可以通过使用获得的接口和端点详细信息的完整列表如下:

+1

@ user1815293-感谢您的回复。您的代码是我分散在代码中的DEBUG的更清晰版本。然而,当我运行你的代码时,它证实了我已经发现的东西:与单个端点只有一个接口,并且该端点是一个中断类型,它在从设备到主机的方向上运行。总之:在Android中,我没有给它发送消息到设备的端点。但我知道端点必须存在,因为它在Windows中可用并且可以工作。因此Android一定不能正确支持USB HID – DasBoos

+0

无论是控制端点还是未提供的中断端点都不清楚。任何其他洞察这个问题将不胜感激,即使它只是为了证实或反驳我的结果。 – DasBoos

2

默认情况下,控制传输不显示任何接口描述符,并且其端点号默认为0,用于传入和传出。

如果您有其他接口,那些接口的索引应该从0开始,即默认控制传输接口不计数。

因此,您的接口0包含端点1描述符。使用UsbEndpoint方法查找端点的属性,无论它是否是中断类型。如果是则端点类型由UsbEndpoint.getType()应该由UsbEndpoint.getEndpointNumber返回0x03和端点号()应该返回0×81这是下面的代码端点1

平常值是错误的:

//first field ox21 is bin 00100001 which splits into 0 01 00001 for direction(1bit)/type(2b)/recipient(5b) 
    //To set direction as 'host to Device' we need 0, **To set type to HID we need 11 (3)**, and for recipient we want 00001 
    //second field 0x09 is class specific request code, **0x09 is listed as 'reserved for future use'** 
    //**third field 0x200 is value** 
    //int transfer = mConnectionRead.controlTransfer(0x21, 0x9, 0x200, 0, message, message.length, 0); 
    //try with type set to HID 
    int transfer = mConnectionRead.controlTransfer(0xC1, 0x9, 0x200, 0, message, message.length, 0); 

类型2位用于指示类特定请求,即它的值为01, 0x09是Hid类特定请求SET_REPORT,不保留。 value是用作Hid类的报告ID的wValue,对于您的情况,如果您在您的HID描述符中只有一个报告,则值可能为0。 和第四个参数是wIndex,它应该用来指示收件人,对于您的情况,它应该是接口0x01作为收件人。

所以你的用于读控制传输或接收数据形式的器件代码应该是:

int transfer = mConnectionRead.controlTransfer(0xA1, 0x01, 0x00, 0x01, message, message.length, 0); 

其中0×01在第二个参数是GET_REPORT是已隐藏调用特定请求。

和你的写控制传输码或发送数据到设备应该是:

int transfer = mConnectionWrite.controlTransfer(0x21, 0x09, 0x00, 0x01, message, message.length, 0); 

既然你只有中断IN端点1,批量或中断传输应该是这样的:

int transfer = bulkTransfer (ep1, message, message.length, 0); 

有中断Out端点应该有一个端点描述符为您的设备的固件接口描述符。

+0

很棒的破败。这次真是万分感谢! – tricknology