Android USB Host的使用详解

转自:http://blog.csdn.net/leo_wonty/article/details/6721214

HID是一种USB通信协议,无需安装驱动就能进行交互,在学习HID之前,先来复习一下USB协议的相关内容。

 

USB设备描述符-概述

当插入USB设备后,主机会向设备请求各种描述符来识别设备。那什么是设备描述符呢?

Descriptor即描述符,是一个完整的数据结构,可以通过C语言等编程实现,并存储在USB设备中,用于描述一个USB设备的所有属性,USB主机是通过一系列命令来要求设备发送这些信息的。

描述符的作用就是通过命令操作作来给主机传递信息,从而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作。

USB有那些标准描述符? 

USB有5种标准描述符:设备描述符 、配置描述符、字符描述符、接口描述符、端点描述符  。



以下转自:http://blog.csdn.net/duckmoving/article/details/9713269



我必须声明一下,本文章是我自己的X度空间最近迁移过来的,不是抄袭。

【废话一段】
      前段时间,我的小组开发一个Android主机的系统,这个系统需要外接USB的指纹机、读卡器、U盘,硬件已经有了,主机是一个开发板接口丰富,并且支持Android USB Host模式,外设自然不用说。
     但是碰到了一个问题,驱动!本来这个项目是源于Windows的,外设全部是针对Windows而开发的,如果要用专门的驱动,那么开发Android本身就需要复杂的过程。后来经过硬件工程师的改造,我们将USB换成了HID模式,减轻开发难度。
     经过一段时间搜索网上资料,关于Android USB Host的资料可以说非常少,不是少数,而是几乎雷同。我是百度+google,更换无数中英文关键字,最后我如愿完成自己的项目,和HID设备正常通讯了,并且识别了U盘。对于网络上中文资料的少而单一的现状,我决定自己写一篇文章,让同行们少走弯路。
    我的代码参考了来自“开源中国”部分资料,如果有解决不了的,可以在那里查询。
【基础功能】
    注意:本文的步骤,可能需要你具备Root的权限,否则有些操作可能会无法完成。强烈建议你先root设备。
    步骤一:你必须确定你的Android设备支持USB Host,具体支持与否请参考自己的说明书。确定了才有必要看本文章。
   步骤二:确认Android是否已经开放了USB访问权限,这一步非常重要。操作是:进入系统,找到目录“/system/etc/permissions”,可以用ES或者RE文件管理器进行操作。查看该目录下,是否有一个文件"android.hardware.usb.host.xml",如果没有,则自己创建一个同名文件,内容如下:

[html]  view plain copy
  1. <permissions>   
  2.     <feature name="android.hardware.usb.host"/>   
  3. </permissions>  

然后,拷贝到“/system/etc/permissions”目录下。(可以用Eclipse的DDMS帮忙,push进去)
   步骤三:其实呢,有了步骤三基本也就可以了,但是我自己也不是很确定,于是有了步骤四。继续检查目录“/system/etc/permissions”下,将其中的“handheld_core_hardware.xml (手机)或者 tablet_core_hardware.xml(平板)”拖出来,打开文件,看看<permissions>结点下面有没有下面这个结点:

[html]  view plain copy
  1. <feature name="android.hardware.usb.host" />  

如果没有,就自己补上一行,保存,并push进去替换原来的文件。比如我的文件内容是:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <permissions>  
  3.     <feature name="android.hardware.camera" />  
  4.     <feature name="android.hardware.location" />  
  5.     <feature name="android.hardware.location.network" />  
  6.     <feature name="android.hardware.sensor.compass" />  
  7.     <feature name="android.hardware.sensor.accelerometer" />  
  8.     <feature name="android.hardware.bluetooth" />  
  9.     <feature name="android.hardware.touchscreen" />  
  10.     <feature name="android.hardware.microphone" />  
  11.     <feature name="android.hardware.screen.portrait" />  
  12.     <feature name="android.hardware.screen.landscape" />  
  13.     <feature name="android.hardware.usb.host" />  
  14. </permissions>  

步骤四:非常重要,就是重启你的Android设备
【详细代码】
       事实上,做完上面的步骤,剩下的代码就非常地简单了,我之前搜索到的基本就是这些内容了。


      强烈建议大家参考SDK文档下面的google示例:ADBTest,他写的类非常有用,我下面的内容可以忽略过去。我后来就是参考了ADBTest之后,改装成一个通用类,可以让我的主机接多个USB-HID外设,实现同时异步收发。当然,你通过我下面的代码应该也能实现举一反三。


   先看看AndroidManifest.xml文件,为了写这篇文章,我特意做了大量注释:

[html]  view plain copy
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     package="com.example.usbmanager"  
  3.     android:versionCode="1"  
  4.     android:versionName="1.0" >  
  5.       <!-- 这个权限是必须有的,否则操作不了硬件,google的文档没有说出来,据说是因为有过滤器后自动获得,但是我的项目没这句话不成功。 -->  
  6.     <uses-permission android:name="android.permission.HARDWARE_TEST" />  
  7.     <!-- 这句话也是必须的 -->  
  8.     <uses-feature android:name="android.hardware.usb.host" android:required="true"/>  
  9.     <!-- SDK必须是12以上的,因为从 Android3.1开始,才正式支持USB Host -->  
  10.     <uses-sdk  
  11.         android:minSdkVersion="12"  
  12.         android:targetSdkVersion="17" />  
  13.               
  14.     <application  
  15.         android:icon="@drawable/ic_launcher"  
  16.         android:label="@string/app_name"  
  17.         android:theme="@style/AppTheme" >  
  18.         <activity  
  19.             android:name=".MainActivity"  
  20.             android:label="@string/app_name"  
  21.             >  
  22.             <intent-filter>  
  23.                 <action android:name="android.intent.action.MAIN" />  
  24.                               
  25.                 <category android:name="android.intent.category.LAUNCHER" />  
  26.             </intent-filter>  
  27.             <!-- 以下这个过滤器是要手工增加上,如果不增加也可以在代码中动态注册,不过我的代码是在这里注册 -->  
  28.             <intent-filter>  
  29.                 <action  
  30.                     android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />  
  31.             </intent-filter>  
  32.              <!-- 以下这个meta-data是要手工增加上,他是用来过滤你的具体USB设备的,其中的device_filter是个xml文件 -->  
  33.             <meta-data  
  34.                 android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"     
  35.                 android:resource="@xml/device_filter"/>  
  36.             <!--    
  37.             <intent-filter>   
  38.                 <action android:name="android.intent.action.VIEW"></action>  
  39.                 <category android:name="android.intent.category.DEFAULT"></category>  
  40.                 <data android:mimeType=""></data>   
  41.             </intent-filter>  
  42.             -->  
  43.                           
  44.         </activity>  
  45.     </application>  
  46.               
  47. </manifest>  

注意看:上面的文件提到了一个文件“ device_filter ”,他也是你能否成功的一个重要文件” device_filter.xml “,这个文件必须自己创建:
         在项目工程中的res结点创建一个新的文件夹叫”xml“(不会操作????右键啊!),然后再在xml文件夹下创建一个xml,文件名就叫做“device_filter",内容如下:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <!-- 以下内容的 vendor-id、product-id就是USB的vid和pid了,请大家自己在Windows中找到这个设备的属性,检查设备的VID_PID值-->  
  4.     <!-- 还要说明一点的是:Windows设备属性中看到的ID值是16进制表示的,必须转换成10进制,并配置 。-->  
  5.     <!-- 测试用的亿捷U盘,Windows中显示的VID是090C,转换为十进制是2316,其他的道理是一样的 -->  
  6.     <usb-device vendor-id="2316" product-id="4096"/>  
  7.     <!-- 测试用的M1读卡器 -->  
  8.     <usb-device vendor-id="1155" product-id="22352"/>  
  9.     <!-- USB-HUB -->  
  10.     <usb-device vendor-id="1507" product-id="1544" />  
  11. </resources>  

     我必须讲清楚,上面的设备是我测试用的,和大家手上的设备根本不一样,请自行查看USB设备VID\PID然后转换

         我们再来看看布局文件activity_main.xml,
这个布局是测试用的,非常随意,请大家根据自己的项目布局测试,不一定要用我这个,也不要问我为什么要这么设计布局,哥我只是玩玩而已,不要那么认真嘛~~~~~


[html]  view plain copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.      
  11.     <TextView  
  12.         android:id="@+id/tvtitle"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:text="@string/title" />  
  16.      
  17.     <EditText  
  18.         android:id="@+id/etxsend"  
  19.         android:layout_width="wrap_content"  
  20.         android:layout_height="wrap_content"  
  21.         android:layout_alignLeft="@+id/tvtitle"  
  22.         android:layout_below="@+id/tvtitle"  
  23.         android:ems="10"  
  24.         android:hint="@string/sendhint"  
  25.         android:visibility="invisible" />  
  26.      
  27.     <EditText  
  28.         android:id="@+id/etxreceive"  
  29.         android:layout_width="wrap_content"  
  30.         android:layout_height="wrap_content"  
  31.         android:layout_alignLeft="@+id/btsend"  
  32.         android:layout_below="@+id/btsend"  
  33.         android:layout_marginTop="37dp"  
  34.         android:ems="10"  
  35.         android:hint="@string/receivehint"  
  36.         android:visibility="invisible" />  
  37.      
  38.     <Button  
  39.         android:id="@+id/btreceive"  
  40.         android:layout_width="wrap_content"  
  41.         android:layout_height="wrap_content"  
  42.         android:layout_alignLeft="@+id/etxreceive"  
  43.         android:layout_below="@+id/etxreceive"  
  44.         android:text="@string/btreceive" />  
  45.      
  46.     <Button  
  47.         android:id="@+id/btsend"  
  48.         android:layout_width="wrap_content"  
  49.         android:layout_height="wrap_content"  
  50.         android:layout_alignLeft="@+id/etxsend"  
  51.         android:layout_below="@+id/etxsend"  
  52.         android:layout_marginTop="16dp"  
  53.         android:text="@string/btsend" />  
  54.      
  55.     <ListView  
  56.         android:id="@+id/lsv1"  
  57.         android:layout_width="match_parent"  
  58.         android:layout_height="wrap_content"  
  59.         android:layout_alignLeft="@+id/btreceive"  
  60.         android:layout_below="@+id/btreceive" >  
  61.     </ListView>  
  62.      
  63. </RelativeLayout>  

        特别是上面提到的一些按钮上的text,大家发挥自己想象力,我不想再贴上values下面的那些文件了。其中有些按钮根本没有用到。我是调试用的。
      
       这回,我们进入了直接的代码模块:MainActivity.java

[java]  view plain copy
  1. package com.example.usbmanager;  
  2.      
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.Iterator;  
  6. import java.util.List;  
  7.      
  8. import android.os.Bundle;  
  9. import android.R.string;  
  10. import android.app.Activity;  
  11. import android.app.PendingIntent;  
  12. import android.content.BroadcastReceiver;  
  13. import android.content.Context;  
  14. import android.content.DialogInterface;  
  15. import android.content.Intent;  
  16. import android.content.IntentFilter;  
  17. import android.database.DataSetObserver;  
  18. import android.hardware.usb.UsbConstants;  
  19. import android.hardware.usb.UsbDevice;  
  20. import android.hardware.usb.UsbDeviceConnection;  
  21. import android.hardware.usb.UsbEndpoint;  
  22. import android.hardware.usb.UsbInterface;  
  23. import android.hardware.usb.UsbManager;  
  24. import android.util.Log;  
  25. import android.view.View.OnClickListener;  
  26. import android.view.Gravity;  
  27. import android.view.Menu;  
  28. import android.view.View;  
  29. import android.view.ViewGroup;  
  30. import android.widget.ArrayAdapter;  
  31. import android.widget.Button;  
  32. import android.widget.EditText;  
  33. import android.widget.ListAdapter;  
  34. import android.widget.ListView;  
  35. import android.widget.Toast;  
  36.      
  37. public class MainActivity extends Activity {  
  38.     private static final String TAG = "MainActivity";   //记录标识  
  39.     private Button btsend;      //发送按钮  
  40.     private UsbManager manager;   //USB管理器  
  41.     private UsbDevice mUsbDevice;  //找到的USB设备  
  42.     private ListView lsv1;         //显示USB信息的  
  43.     private UsbInterface mInterface;     
  44.     private UsbDeviceConnection mDeviceConnection;  
  45.     @Override  
  46.     protected void onCreate(Bundle savedInstanceState) {  
  47.         super.onCreate(savedInstanceState);  
  48.         setContentView(R.layout.activity_main);  
  49.         btsend = (Button) findViewById(R.id.btsend);  
  50.      
  51.         btsend.setOnClickListener(btsendListener);  
  52.      
  53.         lsv1 = (ListView) findViewById(R.id.lsv1);  
  54.         // 获取USB设备  
  55.         manager = (UsbManager) getSystemService(Context.USB_SERVICE);  
  56.         if (manager == null) {  
  57.             return;  
  58.         } else {  
  59.             Log.i(TAG, "usb设备:" + String.valueOf(manager.toString()));  
  60.         }  
  61.         HashMap<String, UsbDevice> deviceList = manager.getDeviceList();  
  62.         Log.i(TAG, "usb设备:" + String.valueOf(deviceList.size()));  
  63.         Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();  
  64.         ArrayList<String> USBDeviceList = new ArrayList<String>(); // 存放USB设备的数量  
  65.         while (deviceIterator.hasNext()) {  
  66.             UsbDevice device = deviceIterator.next();  
  67.      
  68.             USBDeviceList.add(String.valueOf(device.getVendorId()));  
  69.             USBDeviceList.add(String.valueOf(device.getProductId()));  
  70.      
  71.             // 在这里添加处理设备的代码  
  72.             if (device.getVendorId() == 1155 && device.getProductId() == 22352) {  
  73.                 mUsbDevice = device;  
  74.                 Log.i(TAG, "找到设备");  
  75.             }  
  76.         }  
  77.         // 创建一个ArrayAdapter  
  78.         lsv1.setAdapter(new ArrayAdapter<String>(this,  
  79.                 android.R.layout.simple_list_item_1, USBDeviceList));  
  80.         findIntfAndEpt();  
  81.              
  82.     }  
  83.      
  84.     private byte[] Sendbytes;    //发送信息字节  
  85.     private byte[] Receiveytes;  //接收信息字节  
  86.     private OnClickListener btsendListener = new OnClickListener() {  
  87.         int ret = -100;  
  88.         @Override  
  89.         public void onClick(View v) {  
  90.             /* 
  91.              * 请注意,本模块通信的内容使用的协议是HID读卡器协议,不会和大家手上的设备一样 
  92.              * 请大家在测试时参考自己手上的设备资料,严格按照HID标准执行发送和接收数据 
  93.              * 我的范例使用的设备是广州微云电子的WY-M1RW-01非接触式读卡器,outputreport是64,因此我发送的字节长度是64 
  94.              * 我发送的字节内容是要求读卡器蜂鸣器响两短一长 
  95.              */  
  96.             String testString = "90000CB20301F401F401F401F407D447FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";  
  97.             Sendbytes = clsPublic.HexString2Bytes(testString);  
  98.                  
  99.             // 1,发送准备命令  
  100.             ret = mDeviceConnection.bulkTransfer(epOut, Sendbytes, Sendbytes.length, 5000);   
  101.             Log.i(TAG,"已经发送!");  
  102.                  
  103.             // 2,接收发送成功信息  
  104.             Receiveytes=new byte[64];     //这里的64是设备定义的,不是我随便乱写,大家要根据设备而定  
  105.             ret = mDeviceConnection.bulkTransfer(epIn, Receiveytes, Receiveytes.length, 10000);  
  106.             Log.i(TAG,"接收返回值:" + String.valueOf(ret));  
  107.             if(ret != 64) {  
  108.                 DisplayToast("接收返回值"+String.valueOf(ret));  
  109.                 return;  
  110.             }  
  111.             else {  
  112.                 //查看返回值  
  113.                 DisplayToast(clsPublic.Bytes2HexString(Receiveytes));  
  114.                 Log.i(TAG,clsPublic.Bytes2HexString(Receiveytes));  
  115.             }  
  116.         }  
  117.     };  
  118.      
  119.     // 显示提示的函数,这样可以省事,哈哈  
  120.     public void DisplayToast(CharSequence str) {  
  121.         Toast toast = Toast.makeText(this, str, Toast.LENGTH_LONG);  
  122.         // 设置Toast显示的位置  
  123.         toast.setGravity(Gravity.TOP, 0200);  
  124.         // 显示Toast  
  125.         toast.show();  
  126.     }  
  127.          
  128.     // 寻找接口和分配结点  
  129.     private void findIntfAndEpt() {  
  130.         if (mUsbDevice == null) {  
  131.             Log.i(TAG,"没有找到设备");  
  132.             return;  
  133.         }  
  134.         for (int i = 0; i < mUsbDevice.getInterfaceCount();) {  
  135.             // 获取设备接口,一般都是一个接口,你可以打印getInterfaceCount()方法查看接  
  136.             // 口的个数,在这个接口上有两个端点,OUT 和 IN   
  137.             UsbInterface intf = mUsbDevice.getInterface(i);  
  138.             Log.d(TAG, i + " " + intf);  
  139.             mInterface = intf;  
  140.             break;  
  141.         }  
  142.      
  143.         if (mInterface != null) {  
  144.             UsbDeviceConnection connection = null;  
  145.             // 判断是否有权限  
  146.             if(manager.hasPermission(mUsbDevice)) {  
  147.                 // 打开设备,获取 UsbDeviceConnection 对象,连接设备,用于后面的通讯  
  148.                 connection = manager.openDevice(mUsbDevice);   
  149.                 if (connection == null) {  
  150.                     return;  
  151.                 }  
  152.                 if (connection.claimInterface(mInterface, true)) {  
  153.                     Log.i(TAG,"找到接口");  
  154.                     mDeviceConnection = connection;  
  155.                     //用UsbDeviceConnection 与 UsbInterface 进行端点设置和通讯  
  156.                     getEndpoint(mDeviceConnection,mInterface);  
  157.                 } else {  
  158.                     connection.close();  
  159.                 }  
  160.             } else {  
  161.                 Log.i(TAG,"没有权限");  
  162.             }  
  163.         }  
  164.         else {  
  165.             Log.i(TAG,"没有找到接口");  
  166.         }  
  167.     }  
  168.          
  169.          
  170.     private UsbEndpoint epOut;  
  171.     private UsbEndpoint epIn;  
  172.     //用UsbDeviceConnection 与 UsbInterface 进行端点设置和通讯  
  173.     private void getEndpoint(UsbDeviceConnection connection, UsbInterface intf) {  
  174.         if (intf.getEndpoint(1) != null) {  
  175.             epOut = intf.getEndpoint(1);  
  176.         }  
  177.         if (intf.getEndpoint(0) != null) {  
  178.             epIn = intf.getEndpoint(0);  
  179.         }  
  180.     }  
  181.          
  182.          
  183. }  

还有个类clsPublic.java,用来做些类型转换的和其他用途。

[java]  view plain copy
  1. package com.example.usbmanager;  
  2.   
  3. public class clsPublic {  
  4.      // 整数到字节数组转换  
  5.      public static byte[] int2bytes(int n) {  
  6.      byte[] ab = new byte[4];  
  7.      ab[0] = (byte) (0xff & n);  
  8.      ab[1] = (byte) ((0xff00 & n) >> 8);  
  9.      ab[2] = (byte) ((0xff0000 & n) >> 16);  
  10.      ab[3] = (byte) ((0xff000000 & n) >> 24);  
  11.      return ab;  
  12.      }  
  13.   
  14.      // 字节数组到整数的转换  
  15.      public static int bytes2int(byte b[]) {  
  16.      int s = 0;  
  17.      s = ((((b[0] & 0xff) << 8 | (b[1] & 0xff)) << 8) | (b[2] & 0xff)) << 8  
  18.      | (b[3] & 0xff);  
  19.      return s;  
  20.      }  
  21.   
  22.      // 字节转换到字符  
  23.      public static char byte2char(byte b) {  
  24.      return (char) b;  
  25.      }  
  26.   
  27.      private final static byte[] hex = "0123456789ABCDEF".getBytes();  
  28.   
  29.      private static int parse(char c) {  
  30.      if (c >= 'a')  
  31.      return (c - 'a' + 10) & 0x0f;  
  32.      if (c >= 'A')  
  33.      return (c - 'A' + 10) & 0x0f;  
  34.      return (c - '0') & 0x0f;  
  35.      }  
  36.   
  37.      // 从字节数组到十六进制字符串转换  
  38.      public static String Bytes2HexString(byte[] b) {  
  39.      byte[] buff = new byte[2 * b.length];  
  40.      for (int i = 0; i < b.length; i++) {  
  41.      buff[2 * i] = hex[(b[i] >> 4) & 0x0f];  
  42.      buff[2 * i + 1] = hex[b[i] & 0x0f];  
  43.      }  
  44.      return new String(buff);  
  45.      }  
  46.   
  47.      // 从十六进制字符串到字节数组转换  
  48.      public static byte[] HexString2Bytes(String hexstr) {  
  49.      byte[] b = new byte[hexstr.length() / 2];  
  50.      int j = 0;  
  51.      for (int i = 0; i < b.length; i++) {  
  52.      char c0 = hexstr.charAt(j++);  
  53.      char c1 = hexstr.charAt(j++);  
  54.      b[i] = (byte) ((parse(c0) << 4) | parse(c1));  
  55.      }  
  56.      return b;  
  57.      }  
  58. }  


         
【调试】

      如果一切顺利的话,你接上你的USB设备后,android会弹出一个访问硬件的权限提示框,你要点击“确定“。

      调试的内容应该我不需要讲了,Eclipse会帮你搞定的。



      好吧!已经把该说的代码都说了,说句实话,CSDN的网络稳定性还是很差,目前我正在物色新的博客平台,X园好像挺可以的。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值