编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.IC实体卡介绍说明
  • 02.M1实体卡读写技术
  • 03.CPU实体卡读写技术
  • 04.PSAM卡读写技术
  • 05.键盘按键监听实践
  • 06.USB开发设计实践
  • 08.设备串口通信实践

06.USB开发设计实践

目录介绍

  • 01.基础概念介绍
    • 1.1 理解USB接口
    • 1.2 USB开发步骤
    • 1.3 USB主机和配件
    • 1.4 两种模式区别
    • 1.5 USB开发相关类
  • 02.常见思路和做法
    • 2.1 开发场景说明
    • 2.2 开发思路步骤
    • 2.3 发现设备实践
    • 2.4 打开设备实践
    • 2.5 设备建立连接
    • 2.6 数据传输实践
    • 2.7 关闭设备操作
  • 03.USB其他功能
    • 3.1 判断请求USB权限
    • 3.2 处理USB监听广播
    • 3.3 UsbEndpoint设备端点
    • 3.4 定期检查连接设备
    • 3.5 USB断开处理逻辑
  • 04.Api调用说明
  • 05.原理和遇到的坑
  • 06.其他问题说明

01.基础概念介绍

1.1 理解USB接口

  • 如何理解USB
    • USB是英文Universal Serial Bus(通用串行总线)的缩写,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。
    • AOA协议是Google公司推出的用于实现Android设备与外围设备之间USB通信的协议。
    • 该协议拓展了Android设备USB接口的功能,为基于Android系统的智能设备应用于数据采集和设备控制领域提供了条件。

1.2 USB开发步骤

  • Android USB 配件必须遵从 Android Open Accessory(AOA)协议,该协议定义了配件如何检测和建立与 Android 设备的通信。配件应执行以下步骤:
    • 等待并检测连接的设备
    • 确定设备的配件模式支持
    • 尝试以配件模式下启动设备(如果需要)
    • 如果设备支持 AOA,与设备建立通信
  • UsbManager官方文档
    • https://developer.android.google.cn/reference/android/hardware/usb/

1.3 USB主机和配件

  • 在 USB 配件模式下(USB accessory)
    • 外部 USB 硬件充当 USB 主机。配件示例可能包括机器人控制器、扩展坞、诊断和音乐设备、自助服务终端、读卡器等等。
    • 这样,不具备主机功能的 Android 设备就能够与 USB 硬件互动。Android USB 配件必须设计为与 Android 设备兼容,并且必须遵守 Android 配件通信协议。
  • 在 USB 主机模式下(USB host)
    • Android 设备充当主机。设备示例包括数码相机、键盘、鼠标和游戏控制器。即:安卓平板作为主机,usb外设作为从机进行数据通信。

1.4 两种模式区别

  • 两种模式之间的差异。
    • 当 Android 设备处于主机模式时,它会充当 USB 主机并为总线供电。
    • 当 Android 设备处于 USB 配件模式时,所连接的 USB 硬件(本例中为 Android USB 配件)充当主机并为总线供电。

1.5 USB开发相关类

  • android.hardware.usb包下提供了USB开发的相关类。
  • 我们需要了解UsbManager、UsbDevice、UsbInterface、UsbEndpoint、UsbDeviceConnection、UsbRequest、UsbConstants。
    • 1、UsbManager:获得Usb的状态,与连接的Usb设备通信。
    • 2、UsbDevice:Usb设备的抽象,它包含一个或多个UsbInterface,而每个UsbInterface包含多个UsbEndpoint。Host与其通信,先打开UsbDeviceConnection,使用UsbRequest在一个端点(endpoint)发送和接收数据。
    • 3、UsbInterface:定义了设备的功能集,一个UsbDevice包含多个UsbInterface,每个Interface都是独立的。
    • 4、UsbEndpoint:endpoint是interface的通信通道。
    • 5、UsbDeviceConnection:host与device建立的连接,并在endpoint传输数据。
    • 6、UsbRequest:usb 请求包。可以在UsbDeviceConnection上异步传输数据。注意是只在异步通信时才会用到它。
    • 7、UsbConstants:usb常量的定义,对应linux/usb/ch9.h

02.常见思路和做法

2.1 开发场景说明

  • 开发使用的是usb主机模式,即:安卓平板作为主机,usb外设作为从机进行数据通信。
    • 开发使用的是usb主机模式,即:安卓机器作为主机,usb外设作为从机进行数据通信。然后主机和联动设备需要交互。

2.2 开发思路步骤

  • 大概思路如下:
    • 第一步:发现设备。通过UsbManager调用getDeviceList可以获取当前连接的所有usb设备。
    • 第二步:打开设备。可以将机具与usb外设之间的连接想象成一个通道,只有把通道的门打开后,两边才能进行通信。
    • 第三步:数据传输。已经可以与usb外设进行数据传输

2.3 发现设备实践

  • 第一步:发现设备。通过UsbManager调用getDeviceList可以获取当前连接的所有usb设备。
    • 通过UsbManager这个系统提供的类,我们可以枚举出当前连接的所有usb设备,我们主要需要的是UsbDevice对象。
    • UsbDevice,这个类就代表了Android所连接的usb设备。通过getDeviceList()获取已连接的USB设备列表。
    UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
    Map<String, UsbDevice> usbList = usbManager.getDeviceList();
  • 请注意,为了使用UsbManager,你需要在AndroidManifest.xml文件中声明USB权限:
    <uses-permission android:name="android.permission.USB_PERMISSION" />

2.4 打开设备实践

  • 第二步:打开设备。可以将机具与usb外设之间的连接想象成一个通道,只有把通道的门打开后,两边才能进行通信。
    • 需要打开刚刚搜索到的usb设备,我们可以将平板与usb外设之间的连接想象成一个通道,只有把通道的门打开后,两边才能进行通信。
    • 注意:在没有定制的android设备上首次访问usb设备的时候,默认是没有访问权限的,因此首先要判断对当前要打开的usbDevice是否有访问权限。
    • 需要和usb外设建立一个UsbDeviceConnection,大部分情况下还需要对usb串口进行一些配置,比如波特率,停止位,数据控制等,不然两边配置不同,收到的数据会乱码。
    • 2.1 申请权限
    • 2.2 获得连接口UsbInterface
    • 2.3 获得连接端口UsbEndpoint
  • 选择要打开的设备,并请求权限
    • 使用requestPermission()方法请求USB设备的权限。你需要提供一个PendingIntent对象,用于接收权限请求的结果。
    PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
    usbManager.requestPermission(device, permissionIntent);
  • 如何打开设备
    mUsbManager.openDevice(device);
  • 在广播接收器中处理权限请求结果。
    private static final String ACTION_USB_PERMISSION = "com.example.myapp.USB_PERMISSION";
    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        if (device != null) {
                            // 权限已授予,可以打开设备
                            UsbDeviceConnection connection = usbManager.openDevice(device);
                            if (connection != null) {
                                // 打开设备成功,进行相关操作
                            }
                        }
                    } else {
                        // 权限被拒绝
                    }
                }
            }
        }
    };

2.5 设备建立连接

  • 使用usbManager.openDevice()方法打开USB设备,并获取一个UsbDeviceConnection对象。如果连接建立成功,使用该连接对象进行后续的USB通信操作。
    • 获取连接接口,interfaceIndex是你要获取的连接接口的索引。一个USB设备可能有多个接口,每个接口都有自己的功能和设置。
    • USB接口的索引从0开始,你可以使用device.getInterfaceCount()方法获取USB设备的接口数量。
    • 可以使用UsbInterface对象来获取接口的相关信息,如端点(endpoint)和接口设置等。
    UsbInterface usbInterface = device.getInterface(interfaceIndex);

2.6 数据传输实践

  • 第三步:数据传输。已经可以与usb外设进行数据传输
    • 向usb外设发送数据。使用usbDeviceConnection.bulkTransfer这个函数用于在给定的端口进行数据传输。返回值代表发送成功的字节数,如果返回-1,那就是发送失败了。
    • 接受usb外设发送来的数据。找到了数据输入端口usbEndpointIn,因为数据的输入是不定时的,因此我们可以另开一个线程,来专门接受数据。
    • 3.1 发送数据。调用bulkTransfer方法发送数据
    • 3.2 接收数据。找到了数据输入端口usbEndpointIn,因为数据的输入是不定时的,因此我们可以另开一个线程,来专门接受数据
  • 发送控制请求:
    • 使用controlTransfer()方法发送控制请求到USB设备。你需要提供请求类型、请求码、值、索引、数据缓冲区、超时时间等参数。该方法将返回操作结果。
    int requestType = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT;
    int request = 0x01;
    int value = 0x00;
    int index = 0x00;
    byte[] buffer = new byte[4];
    int timeout = 1000;
    int result = connection.controlTransfer(requestType, request, value, index, buffer, buffer.length, timeout);
  • 读取数据:
    • 使用bulkTransfer()方法从USB设备的输入端点(IN)读取数据。你需要提供输入端点、数据缓冲区、缓冲区大小和超时时间等参数。该方法将返回读取的字节数。
    byte[] buffer = new byte[64];
    int timeout = 1000;
    int bytesRead = connection.bulkTransfer(endpointIn, buffer, buffer.length, timeout);
  • 写入数据:
    • 使用bulkTransfer()方法向USB设备的输出端点(OUT)写入数据。你需要提供输出端点、数据缓冲区、缓冲区大小和超时时间等参数。该方法将返回写入的字节数。
    byte[] buffer = new byte[64];
    int timeout = 1000;
    int bytesWritten = connection.bulkTransfer(endpointOut, buffer, buffer.length, timeout);
  • 释放连接:
    • 在不再需要与USB设备通信时,你可以使用close()方法释放UsbDeviceConnection对象。
    connection.close();

2.7 关闭设备操作

  • 当我们要退出App的时候,需要去关闭串口
    mUsbManager.openDevice(device);

03.USB其他功能

3.1 判断请求USB权限

  • 判断设备是否有权限,核心代码如下所示:
    hasPermission = mUsbManager.hasPermission(device);
  • 如果没有权限,需要先申请权限,这一步很主要,要不然后面肯定是读取不到串口的数据的。
    • 你可以使用usbManager.requestPermission()方法来请求权限,并在回调中处理用户的响应。
    mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
    mUsbManager.requestPermission(device, mPermissionIntent);

3.2 处理USB监听广播

  • 注册USB广播监听,主要是监听USB逻辑
    • 创建一个广播接收器类,继承自BroadcastReceiver,并实现onReceive()方法。在onReceive()方法中处理USB相关的广播事件。
    • 声明了USB权限android.permission.USB_PERMISSION,以及我们创建的广播接收器USBReceiver和相关的广播动作。
    public class USBReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action != null && action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
                // 处理USB设备连接事件
                UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            } else if (action != null && action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
                // 处理USB设备断开事件
            }
        }
    }

3.3 UsbEndpoint设备端点

  • UsbEndpoint表示USB设备的端点,用于在USB设备和主机之间进行数据传输。每个UsbInterface都包含一个或多个UsbEndpoint。
    • 获取UsbEndpoint,遍历连接接口的所有端点,并选择类型为UsbConstants.USB_ENDPOINT_XFER_BULK的端点。
    • 你可以根据你的需求选择不同类型的端点,如批量传输(bulk transfer)、中断传输(interrupt transfer)或等时传输(isochronous transfer)。
    private UsbEndpoint epOut;
    private UsbEndpoint epIn;
    for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
        UsbEndpoint tempEndpoint = usbInterface.getEndpoint(i);
        if (tempEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
             if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                  epOut = ep;
              } else {
                  epIn = ep;
              }
        }
    }
  • 可以使用UsbEndpoint对象的方法,如getAddress()、getDirection()和getMaxPacketSize()等,来获取端点的相关信息。

3.4 定期检查连接设备

  • 开启一个心跳轮训,使用HandlerThread,具有独立looper的线程。在注册的时候发送检查健康消息,然后轮训。大概思路如下
    mAsyncHandler.postDelayed(mDeviceCheckRunnable, 1000);
    private final Runnable mDeviceCheckRunnable = new Runnable() {
        @Override
        public void run() {
            //获取设备列表,检查是否有权限
            //间隔2秒钟检查一下
            mAsyncHandler.postDelayed(this, 2000);
        }
    };

3.5 USB断开处理逻辑

  • 从哪里可以监听到USB断开操作,通过广播监听,如下所示:
    public class USBReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action != null && action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
                // 处理USB设备断开事件
                final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            }
        }
    }
    • 在广播中,拿到连接的设备device时,然后释放连接,关闭设备等操作。最后回调listener接口中的onDettach方法告知开发者。

04.Api调用说明

05.原理和遇到的坑

06.其他问题说明

5.4 其他推荐

  • https://github.com/felHR85/UsbSerial

5.5 博客

  • USB识别开发
    • https://blog.csdn.net/c19344881x/article/details/124838289
  • Android USB转串口通信开发实例详解【好案例】
    • https://blog.csdn.net/u011555996/article/details/86220900
  • Android Usb(OTG)串口通信
    • https://blog.csdn.net/MSONG93/article/details/130730467
  • 安卓与串口通信-实践篇
    • https://blog.csdn.net/sinat_17133389/article/details/130788942
  • Android的USB通信(AOA连接)
    • https://blog.csdn.net/CJohn1994/article/details/124669291
  • Android-USB通信
    • https://blog.csdn.net/dream_xang/article/details/124274920
  • Android 沾包处理,以串口接入为例 (usb-serial-for-android)
    • https://blog.csdn.net/EthanCo/article/details/129194519
贡献者: yangchong211
上一篇
05.键盘按键监听实践
下一篇
08.设备串口通信实践