将 PC 用作带有 libusb 的 USB 主机的 Android ADK,批量传输错误

Posted

技术标签:

【中文标题】将 PC 用作带有 libusb 的 USB 主机的 Android ADK,批量传输错误【英文标题】:Android ADK with PC as USB Host with libusb, bulk transfer error 【发布时间】:2011-09-25 09:02:32 【问题描述】:

我正在尝试使我的 PC 成为 android 2.3.4 设备的 USB 主机,以便能够在不需要实际“附件”的情况下开发 API。为此,我需要将 PC 设置为 USB 主机和“设备”(在我的例子中是运行 2.3.4 的 Nexus One)。

我从 http://android.serverbox.ch/ 的 libusb 代码开始作为 PC 端的基础,并在 Android 端使用 DemoKit 代码和 Android 文档。

两者似乎可以很好地协商连接,并且接口被“声明”但在批量传输的实际尝试中死亡。在 OSX 上,错误为 -5 (LIBUSB_ERROR_NOT_FOUND),而在 Ubuntu Linux(以 root 身份)上,错误为 -1 (LIBUSB_ERROR_IO)。 (每个版本的最新版本都带有最新的 libusb 1.0.8)。

这是代码。欢迎其他有关问题的 cmets,尽管这主要是一个概念验证,所以我真的只是在寻找批量传输不起作用的原因:

#include <stdio.h>
#include <libusb.h>
#include <string.h>

#define ENDPOINT_BULK_IN 0x83
#define ENDPOINT_BULK_OUT 0x03 // Have tried 0x00, 0x01 and 0x02

#define VID 0x18D1
#define PID 0x4E11

#define ACCESSORY_PID 0x2D00
#define ACCESSORY_ADB_PID 0x2D01 // Can't get this to work, if ADB is active, can't get handle on device

/*
ON OSX
gcc adktest.c -I/usr/local/include -o adktest -lusb-1.0.0 -I/usr/local/include -I/usr/local/include/libusb-1.0
ON UBUNTU
gcc adktest.c -I/usr/include -o adktest -lusb-1.0 -I/usr/include -I/usr/include/libusb-1.0

Testing on Nexus One with Gingerbread 2.3.4
*/

static int transferTest();
static int init(void);
static int shutdown(void);
static void error(int code);
static void status(int code);
static int setupAccessory(
const char* manufacturer,
const char* modelName,
const char* description,
const char* version,
const char* uri,
const char* serialNumber);

//static
static struct libusb_device_handle* handle;

int main (int argc, char *argv[])
if(init() < 0)
    return;

if(setupAccessory(
    "PCHost",
    "PCHost1",
    "Description",
    "1.0",
    "http://www.mycompany.com",
    "SerialNumber") < 0)
    fprintf(stdout, "Error setting up accessory\n");
    shutdown();
    return -1;
;
if(transferTest() < 0)
    fprintf(stdout, "Error in transferTest\n");
    shutdown();
    return -1;
   
shutdown();
fprintf(stdout, "Finished\n");
return 0;


static int transferTest()
  // TEST BULK IN/OUT
  const static int PACKET_BULK_LEN=64;
  const static int TIMEOUT=5000;
  int r,i;
  int transferred;
  char answer[PACKET_BULK_LEN];
  char question[PACKET_BULK_LEN];
  for (i=0;i<PACKET_BULK_LEN; i++) question[i]=i;

    // ***FAILS HERE***
    r = libusb_bulk_transfer(handle, ENDPOINT_BULK_OUT, question, PACKET_BULK_LEN,
                             &transferred,TIMEOUT);
    if (r < 0) 
        fprintf(stderr, "Bulk write error %d\n", r);
        error(r);
        return r;
    
    fprintf(stdout, "Wrote %d bytes", r);

    r = libusb_bulk_transfer(handle, ENDPOINT_BULK_IN, answer,PACKET_BULK_LEN,
                             &transferred, TIMEOUT);
    if (r < 0) 
        fprintf(stderr, "Bulk read error %d\n", r);
        error(r);
        return r;
    
    fprintf(stdout, "Read %d bytes", r);

    if (transferred < PACKET_BULK_LEN) 
        fprintf(stderr, "Bulk transfer short read (%d)\n", r);
        error(r);
        return -1;
    
    printf("Bulk Transfer Loop Test Result:\n");
    //     for (i=0;i< PACKET_BULK_LEN;i++) printf("%i, %i,\n ",question[i],answer[i]);
    for(i = 0;i < PACKET_BULK_LEN; i++) 
        if(i%8 == 0)
            printf("\n");
        printf("%02x, %02x; ",question[i],answer[i]);
    
    printf("\n\n");

    return 0;




static int init()
libusb_init(NULL);
if((handle = libusb_open_device_with_vid_pid(NULL, VID, PID)) == NULL)
    fprintf(stdout, "Problem acquiring handle\n");
    return -1;

libusb_claim_interface(handle, 0);  

return 0;


static int shutdown()
if(handle != NULL)
    libusb_release_interface (handle, 0);
libusb_exit(NULL);
return 0;


static int setupAccessory(
const char* manufacturer,
const char* modelName,
const char* description,
const char* version,
const char* uri,
const char* serialNumber)

unsigned char ioBuffer[2];
int devVersion;
int response;

response = libusb_control_transfer(
    handle, //handle
    0xC0, //bmRequestType
    51, //bRequest
    0, //wValue
    0, //wIndex
    ioBuffer, //data
    2, //wLength
    0 //timeout
);

if(response < 0)error(response);return-1;

devVersion = ioBuffer[1] << 8 | ioBuffer[0];
fprintf(stdout,"Version Code Device: %d\n", devVersion);

usleep(1000);//sometimes hangs on the next transfer :(

response = libusb_control_transfer(handle,0x40,52,0,0,(char*)manufacturer,strlen(manufacturer),0);
if(response < 0)error(response);return -1;
response = libusb_control_transfer(handle,0x40,52,0,1,(char*)modelName,strlen(modelName)+1,0);
if(response < 0)error(response);return -1;
response = libusb_control_transfer(handle,0x40,52,0,2,(char*)description,strlen(description)+1,0);
if(response < 0)error(response);return -1;
response = libusb_control_transfer(handle,0x40,52,0,3,(char*)version,strlen(version)+1,0);
if(response < 0)error(response);return -1;
response = libusb_control_transfer(handle,0x40,52,0,4,(char*)uri,strlen(uri)+1,0);
if(response < 0)error(response);return -1;
response = libusb_control_transfer(handle,0x40,52,0,5,(char*)serialNumber,strlen(serialNumber)+1,0);
if(response < 0)error(response);return -1;

fprintf(stdout,"Accessory Identification sent\n", devVersion);

response = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,0);
if(response < 0)error(response);return -1;

fprintf(stdout,"Attempted to put device into accessory mode\n", devVersion);

if(handle != NULL)
    libusb_release_interface (handle, 0);

int tries = 4;
for(;;)
    tries--;
    if((handle = libusb_open_device_with_vid_pid(NULL, VID, ACCESSORY_PID)) == NULL)
        if(tries < 0)
            return -1;
        
    else
        break;
    
    sleep(1);


libusb_claim_interface(handle, 0);
fprintf(stdout, "Interface claimed, ready to transfer data\n");
return 0;


// error reporting function left out for brevity

这是 Android 应用程序:

package com.cengen.android.pchost;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import com.android.future.usb.UsbAccessory;
import com.android.future.usb.UsbManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends Activity implements Runnable

  private final Logger logger = LoggerFactory.getLogger("PCHost");

  private UsbManager usbManager;

  UsbAccessory accessory;
  ParcelFileDescriptor accessoryFileDescriptor;
  FileInputStream accessoryInput;
  FileOutputStream accessoryOutput;

  private final BroadcastReceiver usbBroadcastReceiver = new BroadcastReceiver()
  
    public void onReceive(Context context, Intent intent)
    
      String action = intent.getAction();
      if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action))
      
        synchronized (this)
        
          accessory = UsbManager.getAccessory(intent);
        
      
      else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action))
      
        UsbAccessory accessory = UsbManager.getAccessory(intent);
        if (accessory != null)
        
          // call your method that cleans up and closes communication with the accessory
        
      
    
  ;

  Handler messageHandler = new Handler()
  
    @Override
    public void handleMessage(Message msg)
    
      switch (msg.what)
      
        case 1:
          logger.info("Got message type ", msg.what);
//              SendFileMessage m = (SendFileMessage) msg.obj;
//              handleSendFile(m);
        break;
        case 2:
          logger.info("Got message type ", msg.what);
//              SendFileMessage m = (SendFileMessage) msg.obj;
//              handleSendFile(m);
        break;
        case 3:
          logger.info("Got message type ", msg.what);
//              SendFileMessage m = (SendFileMessage) msg.obj;
//              handleSendFile(m);
        break;
      
    
  ;

  /**
   * Main USB reading loop, processing incoming data from accessory and parsing
   * it into messages via the defined format.
   */
  public void run()
  
    int ret = 0;
    byte[] buffer = new byte[16384];
    int i;

    while (ret >= 0)
    
      try
      
        ret = accessoryInput.read(buffer);
        logger.debug("Read  bytes.", ret);
      
      catch (IOException e)
      
        logger.debug("Exception in USB accessory input reading", e);
        break;
      

      i = 0;
      while (i < ret)
      
        int len = ret - i;

        switch (buffer[i])
        
          case 0x1:
            if (len >= 3)
            
              Message m = Message.obtain(messageHandler, 1);
//                      m.obj = new MessageTypeOne(buffer[i + 1], buffer[i + 2]);
              messageHandler.sendMessage(m);
            
            i += 3;
            break;

          case 0x4:
            if (len >= 3)
            
              Message m = Message.obtain(messageHandler, 1);
//                      m.obj = new MessageTypeTwo(buffer[i + 1], buffer[i + 2]);
              messageHandler.sendMessage(m);
            
            i += 3;
            break;

          default:
            logger.debug("unknown msg: " + buffer[i]);
            i = len;
            break;
        
      

    
  

  @Override
  public void onCreate(Bundle savedInstanceState)
  
    super.onCreate(savedInstanceState);

    usbManager = UsbManager.getInstance(this);

    IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
    filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
    registerReceiver(usbBroadcastReceiver, filter);

    if (getLastNonConfigurationInstance() != null)
    
      accessory = (UsbAccessory) getLastNonConfigurationInstance();
      openAccessory(accessory);
    
    setContentView(R.layout.main);
  

  @Override
  public Object onRetainNonConfigurationInstance()
  
    return accessory != null ? accessory : super.onRetainNonConfigurationInstance();
  

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

   Intent intent = getIntent();
   if (accessoryInput != null && accessoryOutput != null)
     return;

   // TODO: verify, docs don't do this simple thing, not sure why?
   UsbAccessory accessory = UsbManager.getAccessory(intent);
   if (accessory != null)
     openAccessory(accessory);
   else
     logger.error("Failed to resume accessory.");
 

  @Override
  public void onPause()
  
    super.onPause();
    closeAccessory();
  

  @Override
  public void onDestroy()
  
    unregisterReceiver(usbBroadcastReceiver);
    super.onDestroy();
  

  private void openAccessory(UsbAccessory accessory)
  
    accessoryFileDescriptor = usbManager.openAccessory(accessory);
    if (accessoryFileDescriptor != null)
    
      this.accessory = accessory;
      FileDescriptor fd = accessoryFileDescriptor.getFileDescriptor();
      accessoryInput = new FileInputStream(fd);
      accessoryOutput = new FileOutputStream(fd);
      Thread thread = new Thread(null, this, "AndroidPCHost");
      thread.start();
      logger.debug("accessory opened");
      // TODO: enable USB operations in the app
    
    else
    
      logger.debug("accessory open fail");
    
  

  private void closeAccessory()
  
    // TODO: disable USB operations in the app
    try
    
      if (accessoryFileDescriptor != null)
        accessoryFileDescriptor.close();
    
    catch (IOException e)
    
    finally
    
      accessoryFileDescriptor = null;
      accessory = null;
    
  

还有清单(根据文档,包括意图过滤,因此它会自动关联和自动烫发设备):

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.mycompany.android.pchost"
      android:versionCode="1"
      android:versionName="1.0">
  <uses-feature android:name="android.hardware.usb.accessory" />
  <uses-sdk android:minSdkVersion="10" />
  <application android:label="@string/app_name" android:icon="@drawable/icon">
    <uses-library android:name="com.android.future.usb.accessory" />
        <activity android:name="MainActivity"
                  android:label="@string/app_name"
                  android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
          <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/accessory_filter" />

        </activity>
    </application>
</manifest> 

【问题讨论】:

注意:在 linux(Ubuntu) 上嗅探 USB 接口时,wireshark 显示的最后一件事是主机对 Get Descriptor 请求的响应,设备返回看起来正确的内容,描述符类型: 3、“安卓配件界面”。这似乎是发生 I/O 错误并停止通信的时候。 【参考方案1】:

最初的问题是为了告诉它进入附件模式而与设备的原始连接永远不会关闭。如果您在原始设备仍处于打开状态时重新打开并声明接口,则 USB 子系统和/或 libusb 不会引发错误。当您实际尝试写入/从端点写入时,您只会收到 IO 或 NOT FOUND 错误。

通过添加:

libusb_close(handle);

在从初始连接释放初始接口的 if 语句中,解决了 libusb 方面的问题。

阻止数据在这种特定软件组合中传递的下一个问题是,Android 端在接受读取之前正在等待更大的字节段,这导致超时(尚未解决,因为还没有),因此如果您将缓冲区设置为与 libusb 端(64)匹配,您将获得从 PC 写入 Android 设备的初始字节集。之后软件仍然会中断,因为 PC/libusb 端然后尝试读取数据但 Android 端没有写入任何数据,但这只是未完成的软件,不在问题范围内。

【讨论】:

Colin,你用 libusb-win32 试过了吗?我在设置附件阶段被击中,libusb-win32 返回 -5(实体未找到错误) 我猜想关闭初始“握手”句柄然后打开通讯句柄是类似的需要。我从来没有在win32上尝试过这个,但希望它是相同/相似的(但我已经有好几年没碰这个了!) @ColinM。您介意编辑答案以准确显示您的 libusb_close(handle) 的去向吗?【参考方案2】:

代码示例非常有用,它只需要一些修改就可以在 Windows 上使用 libusb 和 installing WinUSB drivers 通过Zadig 工作。

#include <stdio.h>
#include <libusb.h>
#include <string.h>
#ifdef _WIN32
#include <Windows.h>
#define sleep Sleep
#else
#include <unistd.h>
#endif

#define ENDPOINT_BULK_IN 0x83
#define ENDPOINT_BULK_OUT 0x03 // Have tried 0x00, 0x01 and 0x02

#define VID 0x18D1
#define PID 0x4E11

#define ACCESSORY_PID 0x2D00
#define ACCESSORY_ADB_PID 0x2D01 // Can't get this to work, if ADB is active, can't get handle on device

#define PACKET_BULK_LEN 64
#define TIMEOUT 5000

static int transferTest(void);
static int setupAccessory(
    const char* manufacturer,
    const char* modelName,
    const char* description,
    const char* version,
    const char* uri,
    const char* serialNumber);

//static
static struct libusb_device_handle* handle;

int main (int argc, char *argv[])

    int r, tries;

    libusb_init(NULL);
    if((handle = libusb_open_device_with_vid_pid(NULL, VID, PID)) != NULL)
    
        libusb_claim_interface(handle, 0);  

        r = setupAccessory("PCHost",
                           "PCHost1",
                           "Description",
                           "1.0",
                           "http://www.mycompany.com",
                           "SerialNumber");

        libusb_release_interface (handle, 0);
        libusb_close(handle);

        if (r < 0)
        
            libusb_exit(NULL);
            fprintf(stdout, "Error setting up accessory\n");
            return -1;
        
    

    tries = 4;
    for(;;)
    
        tries--;
        if((handle = libusb_open_device_with_vid_pid(NULL, VID, ACCESSORY_PID)) == NULL)
        
            if(tries < 0)
            
                libusb_exit(NULL);
                fprintf(stdout, "Problem acquiring handle\n");
                return -1;
            
        
        else
        
            break;
        
        sleep(1);
    

    libusb_claim_interface(handle, 0);
    fprintf(stdout, "Interface claimed, ready to transfer data\n");

    r = transferTest();

    libusb_release_interface (handle, 0);
    libusb_close(handle);

    libusb_exit(NULL);

    if (r < 0)
    
        fprintf(stdout, "Error in transferTest\n");
        return -1;
       
    fprintf(stdout, "Finished\n");
    return 0;


static int transferTest(void)

    // TEST BULK IN/OUT
    int r,i;
    int transferred;
    unsigned char answer[PACKET_BULK_LEN];
    unsigned char question[PACKET_BULK_LEN];
    struct libusb_device* device;
    struct libusb_config_descriptor* config;
    struct libusb_interface_descriptor const* interfaceDesc;
    unsigned char epInAddr = ENDPOINT_BULK_IN;
    unsigned char epOutAddr = ENDPOINT_BULK_OUT;
    unsigned char idx;

    for (i=0;i<PACKET_BULK_LEN; i++) 
        question[i]=(unsigned char)i;

    device = libusb_get_device(handle);
    r = libusb_get_active_config_descriptor(device, &config);
    if (r < 0) 
    
        fprintf(stderr, "No active descriptor error %d\n", r);
        return r;
    

    interfaceDesc = config->interface[0].altsetting;

    for(idx = 0; idx < interfaceDesc->bNumEndpoints; idx++)
    
        if ((interfaceDesc->endpoint[idx].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK)
        
            if ((interfaceDesc->endpoint[idx].bEndpointAddress & LIBUSB_ENDPOINT_IN) == LIBUSB_ENDPOINT_IN)
            
                epInAddr = interfaceDesc->endpoint[idx].bEndpointAddress;
            
            else
            
                epOutAddr = interfaceDesc->endpoint[idx].bEndpointAddress;
            
        
    

    r = libusb_bulk_transfer(handle, epOutAddr, question, PACKET_BULK_LEN,
                             &transferred,TIMEOUT);
    if (r < 0) 
    
        fprintf(stderr, "Bulk write error %d\n", r);
        return r;
    
    fprintf(stdout, "Wrote %d bytes", r);

    r = libusb_bulk_transfer(handle, epInAddr, answer,PACKET_BULK_LEN,
        &transferred, TIMEOUT);
    if (r < 0) 
    
        fprintf(stderr, "Bulk read error %d\n", r);
        return r;
    
    fprintf(stdout, "Read %d bytes", r);

    if (transferred < PACKET_BULK_LEN) 
    
        fprintf(stderr, "Bulk transfer short read (%d)\n", r);
        return -1;
    
    printf("Bulk Transfer Loop Test Result:\n");
    for(i = 0; i < PACKET_BULK_LEN; i++) 
    
        if(i%8 == 0)
            printf("\n");
        printf("%02x, %02x; ",question[i],answer[i]);
    
    printf("\n\n");

    return 0;



static int setupAccessory(
    const char* manufacturer,
    const char* modelName,
    const char* description,
    const char* version,
    const char* uri,
    const char* serialNumber)

    unsigned char ioBuffer[2];
    int devVersion;
    int r;

    r = libusb_control_transfer(
        handle, //handle
        0xC0, //bmRequestType
        51, //bRequest
        0, //wValue
        0, //wIndex
        ioBuffer, //data
        2, //wLength
        0 //timeout
        );

    if(r < 0)
    
        return-1;
    

    devVersion = ioBuffer[1] << 8 | ioBuffer[0];
    fprintf(stdout,"Version Code Device: %d\n", devVersion);

    sleep(1); //sometimes hangs on the next transfer :(

    if ((libusb_control_transfer(handle,0x40,52,0,0,(unsigned char*)manufacturer,strlen(manufacturer)+1,0) < 0) ||
        (libusb_control_transfer(handle,0x40,52,0,1,(unsigned char*)modelName,strlen(modelName)+1,0) < 0) ||
        (libusb_control_transfer(handle,0x40,52,0,2,(unsigned char*)description,strlen(description)+1,0) < 0) ||
        (libusb_control_transfer(handle,0x40,52,0,3,(unsigned char*)version,strlen(version)+1,0) < 0) ||
        (libusb_control_transfer(handle,0x40,52,0,4,(unsigned char*)uri,strlen(uri)+1,0) < 0) ||
        (libusb_control_transfer(handle,0x40,52,0,5,(unsigned char*)serialNumber,strlen(serialNumber)+1,0) < 0))
    
        return -1;
    

    fprintf(stdout,"Accessory Identification sent\n", devVersion);

    r = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,0);
    if(r < 0)
    
        return -1;
    

    fprintf(stdout,"Attempted to put device into accessory mode\n", devVersion);
    return 0;

除了在@ColinM 的回答中提到的一些重构和添加必要的libusb_close(handle) 调用之外,这主要是添加:

    枚举可用端点 如果未找到初始 PID/VID,则假定设备已处于附件模式

【讨论】:

以上是关于将 PC 用作带有 libusb 的 USB 主机的 Android ADK,批量传输错误的主要内容,如果未能解决你的问题,请参考以下文章

linux / libusb 获取usb设备路径

如何为 libusb android 应用程序授予 /dev/bus/usb 权限?

USB开发——内核USB驱动+libusb开发方法

对带有 USB 全速半双工通话优先级的 PySerial 感到困惑

如何最正确地使用 libusb 与连接的 USB 设备通信?

libusb 未列出的 USB/IP 仿真设备