USB主从模式开发知识

Posted 無昂博奥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了USB主从模式开发知识相关的知识,希望对你有一定的参考价值。

官网解释:https://developer.android.com/guide/topics/connectivity/usb?hl=zh-cn

前言

Android中的两种USB模式,分别为主机模式、配件模式(也就是我们常说的从模式)。

  • **主机模式:**在此模式下Android设备充当主机,并为总线供电。此时键盘、鼠标、U盘都属于外挂设备。
  • **配件模式:**在此模式下所连接的Android USB设备会充当主机,为USB总线供电,并枚举所连接的设备。

主机模式

当您的 Android 设备处于 USB 主机模式时,它会充当 USB 主机,为总线供电并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。

API概览

在开发中用到的类如下:

  • UsbManager:枚举连接的USB设备并与之通信
  • UsbDevice:表示连接的USB设备,并包含用于访问其标识信息、接口和端点的方法。
  • UsbInterface:表示USB设备的接口,它定义设备的一组功能,设备可以具有一个或者多个用于通信的接口。
  • UsbEndpoint:接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。
  • UsbDeviceConnection:表示与设备的连接,可在端点上上传数据。
  • UsbRequest:通过UsbDeviceConnection与设备通信的异步请求。
  • UsbConstants:定于与Linux内核的linux/usb/ch9.h中的定义相对于的USB常量。

通信的步骤如下:

  1. 通过USBManager获取所需的UsbDevice。
  2. 找到设备相应的UsbInterface和该接口的UsbEndpoint
  3. 找到正确的端点后,打开UsbDeviceConnection与USB设备通信。

清单文件配置

Manifest文件需要添加以下内容,才能使用USB主机API:

  • 由于并不是所有的Android设备都保证支持USB主机API,因此需要添加<uses-feature>元素来声明应用使用android.hardware.usb.host功能
  • 在主Activity中的指定<intent-filter><meta-data>元素对。其中 <meta-data>元素指向外部的XML资源文件,用于声明有关要检测的设备的标识信息
  • 在XML文件中,声明<usb-device>元素,其中包括供应商ID和产品ID。如果想要过滤一组USB设备(例如大容量存储设备或者数码相机),请使用类、子类、和协议。这些属性可以指定,也可以不指定,如果不指定任何属性则会与每个USB设备进行匹配。

清单文件示例:

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

res/xml/device_filter.xml文件内容如下:

    <?xml version="1.0" encoding="utf-8"?>

    <resources>
        <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
    </resources>
    

发现设备

您的应用可以通过使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备来发现设备。如果您希望应用自动检测所需的设备,那么使用 Intent 过滤器就非常有用。如果您想获取所有连接的设备的列表,或者您的应用没有 Intent 过滤器,那么枚举连接的 USB 设备就非常有用。

使用Intent过滤器

在Manifest文件中声明Intent过滤器

    <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" />
    </activity>
    

device_filter.xml内容如下:

    <?xml version="1.0" encoding="utf-8"?>

    <resources>
        <usb-device vendor-id="1234" product-id="5678" />
    </resources>
    

在Activity中,通过以下方式从Intent获取所连接设备的UsbDevice

    UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

枚举设备

如果应用有兴趣在运行时检查当前连接的所有USB设备,则可以枚举总线上的设备。使用getDeviceList()方法获取连接的所有USB设备的哈希映射。

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    ...
    HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
    UsbDevice device = deviceList.get("deviceName");
    

如果需要,还可以从HashMap中获取迭代器,并逐个处理设备:

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    ...
    HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
    Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
    while(deviceIterator.hasNext())
        UsbDevice device = deviceIterator.next();
        //your code
    

获取与设备通信的权限

应用必须获得用户的许可,才能与USB设备通信。创建一个广播接收器,此接收器会在应用调用requestPermission()时接收广播的Intent,调用requestPermission()会想用户弹出一个请求连接设备权限的对话框,广播接收器的代码如下:

    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() 

        public void onReceive(Context context, Intent intent) 
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) 
                synchronized (this) 
                    UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) 
                        if(device != null)
                          //call method to set up device communication
                       
                    
                    else 
                        Log.d(TAG, "permission denied for device " + device);
                    
                
            
        
    ;
    

在Activity中注册广播接收器,在onCreate()函数中添加以下代码:

    UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    ...
    permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver(usbReceiver, filter);

调用requestPermission()方法来显示请求连接到设备权限的对话框

    UsbDevice device;
    ...
    usbManager.requestPermission(device, permissionIntent);
    

与设备通信

与USB设备的通信可以是同步的,也可以是异步的。无论使用哪种方式,在开发过程中都应该创建一个新线程来执行所有数据传输。

  • 检查UsbDevice对象的属性,判断是否要与设备通信
  • 找到要用于通信的UsbInterface以及该接口的适当UsbEndpoint。
  • 在该端点上打开UsbDeviceConnection
  • 使用bulkTransfer()或controlTransfer()方法提供要在端点上传输的数据。

示例代码如下:

    private Byte[] bytes;
    private static int TIMEOUT = 0;
    private boolean forceClaim = true;

    ...

    UsbInterface intf = device.getInterface(0);
    UsbEndpoint endpoint = intf.getEndpoint(0);
    UsbDeviceConnection connection = usbManager.openDevice(device);
    connection.claimInterface(intf, forceClaim);
    connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
    

终止与设备的通信

当完成设备的通信或者设备断开之后,需要调用releaseInterface()close()来关闭UsbInterfaceUsbDeviceConnection

创建以下广播接收器来监听断开连接事件:

    BroadcastReceiver usbReceiver = new BroadcastReceiver() 
        public void onReceive(Context context, Intent intent) 
            String action = intent.getAction();

          if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) 
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (device != null) 
                    // call your method that cleans up and closes communication with the device
                
            
        
    ;
    

说明:

在应用内创建广播接收器后,应用可以仅在运行时处理断开连接的事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。

配件模式

在此模式下所连接的Android USB设备会充当主机,为USB总线供电,并枚举所连接的设备。

API概览

用到的两个类:

  • UsbManager:允许您枚举所连接的 USB 配件并与之通信。
  • UsbAccessory:表示一个 USB 配件,并包含访问其标识信息的方法。

清单文件配置

  • 添加<use-feature>元素来声明应用使用android.hardware.usb.accessory 功能。

  • 如果您希望应用收到 USB 配件连接通知,请为主 Activity 中的 android.hardware.usb.action.USB_ACCESSORY_ATTACHED Intent 指定 <intent-filter><meta-data> 元素对。<meta-data> 元素指向一个外部 XML 资源文件,该文件声明关于要检测的配件的识别信息。

    在 XML 资源文件中,为要过滤的配件声明 <usb-accessory> 元素。每个 <usb-accessory> 可以包含以下属性:

    • manufacturer
    • model
    • version

    将资源文件保存在 res/xml/ 目录中。资源文件的名称(不含 .xml 扩展名)必须与您在 <meta-data> 元素中指定的名称相同。下面的示例还展示了 XML 资源文件的格式。

    <manifest ...>
        <uses-feature android:name="android.hardware.usb.accessory" />
        
        <uses-sdk android:minSdkVersion="<version>" />
        ...
        <application>
          <uses-library android:name="com.android.future.usb.accessory" />
            <activity ...>
                ...
                <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>
    

在本例中,以下资源文件应保存在 res/xml/accessory_filter.xml 中,并指定应过滤具有相应型号、制造商和版本的任何配件。配件会将这些属性发送给 Android 设备:

    <?xml version="1.0" encoding="utf-8"?>

    <resources>
        <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
    </resources>
    

发现配件

您的应用可以通过两种方式发现配件:使用在用户连接配件时会收到通知的 Intent 过滤器,或者枚举已连接的配件。如果您希望应用能够自动检测所需的配件,那么使用 Intent 过滤器会非常有用。如果您希望获得所有已连接配件的列表,或者您的应用没有 Intent 过滤器,那么枚举已连接的配件就非常有用。

使用 Intent 过滤器

如果要让应用发现特定的 USB 配件,您可以指定一个 Intent 过滤器来过滤 android.hardware.usb.action.USB_ACCESSORY_ATTACHED Intent。除了该 Intent 过滤器之外,您还需要指定一个资源文件来指定 USB 配件的属性,例如制造商、型号和版本。当用户连接与您的配件过滤器匹配的配件时,

以下示例展示了如何声明 Intent 过滤器:

    <activity ...>
        ...
        <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>
    

accessory_filter.xml文件的内容如下:

    <?xml version="1.0" encoding="utf-8"?>

    <resources>
        <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
    </resources>
    

在Activity中通过intent来获取所连接配件的UsbAccessory

    UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

枚举列表

通过UsbManager来枚举已连接的所有USB配件列表

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    UsbAccessory[] accessoryList = manager.getAccessoryList();

获取与配件通信的权限

应用必须获取用户的许可之后,才能与USB配件进行通信。调用的方式是定义一个广播接收器,用来监听调用requestPermission()时接收广播的Intent,当应用调用requestPermission()时,会显示一个对话框,请求授予连接配件的权限。定义广播接收器的代码如下:

    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() 

        public void onReceive(Context context, Intent intent) 
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) 
                synchronized (this) 
                    UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) 
                        if(accessory != null)
                            //call method to set up accessory communication
                        
                    
                    else 
                        Log.d(TAG, "permission denied for accessory " + accessory);
                    
                
            
        
    ;
    

定义广播接收器之后,需要在activity的onCreate()函数中进行注册,代码如下:

    UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
    private static final String ACTION_USB_PERMISSION =
        "com.android.example.USB_PERMISSION";
    ...
    permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver(usbReceiver, filter);
    

要显示对话框向用户请求连接到配件的权限,需要调用requestPermission()函数:

    UsbAccessory accessory;
    ...
    usbManager.requestPermission(accessory, permissionIntent);
    

当用户对弹出的对话框做出操作时,广播接收器会收到包含EXTRA_PERMISSION_GRANTEDextra的intent,该extra是代表用户操作的布尔值。在连接到配件之前,需要检查此extra的值是否为true。

与配件通信

开发者可以使用UsbManager来获取文件描述符,然后设置输入和输出流来向描述符读写数据,以此与配件通信。这些流表示配件的输入和输出批量端点。在通信的时候,需要注意:应该另启动一个线程来设置设备和配件的通信,防止主线程阻塞。示例代码如下:

    UsbAccessory accessory;
    ParcelFileDescriptor fileDescriptor;
    FileInputStream inputStream;
    FileOutputStream outputStream;
    ...

    private void openAccessory() 
        Log.d(TAG, "openAccessory: " + accessory);
        fileDescriptor = usbManager.openAccessory(accessory);
        if (fileDescriptor != null) 
            FileDescriptor fd = fileDescriptor.getFileDescriptor();
            inputStream = new FileInputStream(fd);
            outputStream = new FileOutputStream(fd);
            Thread thread = new Thread(null, this, "AccessoryThread");
            thread.start();
        
    

注意:在使用FileInputStream对象从配件读取数据时,请确保您使用的缓冲区足以存储USB数据包数据、Android配件协议支持最大16384字节的数据包缓冲区,因此为了简单期间,可以选择始终将缓冲区声明为此大小。

终止与配件通信

当完成与配件的通信后或者配件断开连接后,请调用close()来关闭文件描述符。需要在广播接收器中监听断开的连接事件,代码如下:

    BroadcastReceiver usbReceiver = new BroadcastReceiver() 
        public void onReceive(Context context, Intent intent) 
            String action = intent.getAction();

            if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) 
                UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                if (accessory != null) 
                    // call your method that cleans up and closes communication with the accessory
                
            
        
    ;

建议点赞、收藏,方便以后查阅。

以上是关于USB主从模式开发知识的主要内容,如果未能解决你的问题,请参考以下文章

为啥 USB CDC 在接收数据时会挂起?

驱动问题USB主从切换验证

Linux下 USB设备驱动分析(原创)

带你整理面试过程中关于Redis 主从模式哨兵模式和集群模式详解的相关知识点

mysql主从的几个小知识点

S3C2440-裸机篇-06 | UART数据发送接收实验(扫描模式)