USB通信两端分别称为:HOST(USB主机) 与 Device(USB从机/USB配件),常见的主机就是我们的计算机。而Android 可以支持USB主机模式与USB配件模式,意思就是Android既可以是主机也可以是配件。



Android作为配件与其通信的主机必须设计为与Android设备兼容,即遵循Android配件通信协议(AOA协议)。也就是说需要按照此协议进行USB主机设备的开发。

配件模式

有兴趣的可以在《4、AOA协议》中了解,此处不再过多描述。

主机模式

主机模式表示Android设备作为主机为USB总线供电与外部设备进行通信。进行USB通信的大致流程如下:

1、连接检测

这一步由电气工程师负责,软件开发人员无需关心(如果感兴趣可以看圈圈教你玩USB1:链接:https://pan.baidu.com/s/1dOen5533QWN5IMtGAO_zXw 提取码:wvs9 )。

以低速设备连接检测为例:

  1. USB主机端口在D+(正极)和D-(负极)上都有15K的下拉电阻,在没有设备连接时,在下拉电阻的作用下,使得两条数据线的电压都是近地的,也就是0V;
  2. 低速USB设备在D-上有1.5K的上拉电阻;
  3. 在设备连接后,此时主机上D-的下拉电阻和设备上D-的上拉电阻构成分压。此时在D-上会出现3V左右的电压,D+仍然是0V;
  4. 主机检测电压维持2ms,则认为当前有USB设备连接,并且是一个低速USB设备。

2、枚举设备

USB只是一个总线,只提供一个数据通路而已。USB总线驱动程序并不知道一个设备具体如何操作,有哪些行为。具体设备实现什么功能,由设备自己决定,USB主机需要通过描述符了解。这些描述符主要包括:

  • 设备描述符:一个设备只有一个,固定18字节长度,指出设备设备使用的USB协议号、设备信息、厂商和产品ID(vid与pid)等;
  • 配置描述符:描述设备配置的集合,包含的接口数、配置编号、供电方式、电流需求量等,每个配置描述符中又定义了此配置里有多少接口;
  • 接口描述符:每个接口有一个接口描述符,接口描述符定义了该接口的编号、接口端点数、接口的类、子类、协议等,每个端点对应一个端点描述符;
  • 端点描述符:定义了端点大小,类型(读、写)等。

枚举设备就是从设备获取各种描述符,选择不同的接口(功能),根据端点完成通信。

描述符间的关系

USB设备会分出一些端点,如端点0、端点1等,因此USB主机要和设备通信,光有设备地址还不够,还需要一个端点地址。有了设备地址和端点地址,就可以准确的对端点发送和读取数据。

就好像我们在一台主机上运行多个Java程序,不同的Java程序就是不同的配置,而接口就是一个Java程序中实现个多个功能,包括文件操作、用户操作等功能。文件操作功能中开启多个Socket,其中Socket1接收文件数据,提供完成文件上传;Socket2发送文件数据,提供文件下载。我们需要通过主机IP地址与不同的Socket绑定的端口号去完成通信。

枚举阶段就是从设备获取各种描述符,选择不同的接口(功能)

Android 枚举设备

2.1、枚举设备

在Android SDK中已经完成了枚举的封装。Android提供两种方式进行枚举:

2.1.1、通过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>

除了指定 Intent 过滤器 之外,还需要指定一个资源文件来指定支持处理的USB设备的属性,在工程res/xml目录下创建 device_filter.xml 文件,文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1234" product-id="5678" />
</resources>

表示只对vid:1234,pid:5678 感兴趣,其他设备接入并不会调起我们的Activity。

VID:供应商ID,由供应商向USB-IF(Implementers Forum,应用者论坛)申请;

PID:产品ID,由供应商自行决定。不同的产品、相同产品的不同型号、相同型号的不同设计的产品采用不同的PID,以便区别相同厂家的不同设备。

如果需要处理所有USB设备,则需要将内容修改为:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device />
</resources>

配置完成后,在USB设备加入时,就能通过以下方式从 Intent 获取代表所连接设备的 UsbDevice

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
} @Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
}
2.1.2、主动枚举设备

除了使用Intent过滤器在设备插入时接收连接设备的 UsbDevice 之外,还可以使用 getDeviceList() 方法获取连接的所有 USB 设备的哈希映射。

UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
Map<String, UsbDevice> deviceMap = usbManager.getDeviceList();
Collection<UsbDevice> values = deviceMap.values();
Iterator<UsbDevice> iterator = values.iterator();
while (iterator.hasNext()) {
devices.add(iterator.next());
}
2.2、获得通信权限

如果应用使用 Intent 过滤器来发现已连接的 USB 设备,则它会在用户允许应用处理 Intent 时自动获得权限。否则,必须在应用中明确请求权限,然后才能连接到设备。因此在尝试与设备通信之前,必须先检查是否具有访问设备的权限。

如果还未具备设备访问权限则需要通过 requestPermission() 完成请求:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); UsbReceiver usbReceiver = new UsbReceiver();
IntentFilter filter = new IntentFilter(UsbReceiver.ACTION_PERMISSION);
registerReceiver(usbReceiver, filter);
if (!usbManager.hasPermission(usbDevice)) {
//申请权限,系统弹出对话框,用户选择后发出广播,触发注册的广播接收者
PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0,
new Intent(UsbReceiver.ACTION_PERMISSION), 0);
usbManager.requestPermission(usbDevice, permissionIntent);
}
} public class UsbReceiver extends BroadcastReceiver {
public static final String ACTION_PERMISSION = "com.zxj.usb.USB_PERMISSION"; @Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_PERMISSION.equals(action)) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Log.d("Lance", "获取权限 ");
} else {
Log.d("Lance", "没有权限 ");
}
}
}
}

根据描述符筛选设备

在获得通信权限进行通信之前,一般会根据USB设备的描述信息来判断程序是否支持当前设备的通信。

如下示例为判断是否为大容量存储设备(U盘):

https://www.usb.org/defined-class-codes

for (int i = 0; i < usbDevice.getInterfaceCount(); i++) {
UsbInterface usbInterface = usbDevice.getInterface(i); // 1、类为:USB_CLASS_MASS_STORAGE 0x08
// 2、子类为:0x06 (大部分U盘使用)
// 3、协议为:0x50 (Bulk-Only协议 批量传输)
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_MASS_STORAGE//大容量设备
&& usbInterface.getInterfaceSubclass() == 0x06//U盘
&& usbInterface.getInterfaceProtocol() == 0x50) {//通信协议采用Bulk-Only协议
//每个存储设备一定有两个端点:in 和 out
UsbEndpoint outEndpoint = null, inEndpoint = null;
for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
UsbEndpoint endpoint = usbInterface.getEndpoint(j);//获取到端点
if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
outEndpoint = endpoint;//写端点
} else {
inEndpoint = endpoint;//读端点
}
}
}
}
}

3、数据传输

在完成设备枚举后,选定 UsbDevice 对象并获取权限后,接下来就可以与之进行数据交互。在USB通信过程中主要有四种数据传输方式:Control Transaction(控制传输)、Interrupt Transaction(中断传输)、Bulk Transaction(批量传输)和Isochronous Transaction(等时传输)。

  • 控制传输

    所有USB设备与主机必须支持的方式,特点是数据量小、正确性高,一般用于信息获取、命令控制与参数配置等。在获取设备描述符阶段采用此方式。
  • 中断传输

    中断传输是一种保证查询频率的传输,主机会保证在小于这个时间间隔的范围内安排一次传输,常用于数据量不大,但是对时间要求严格的设备,比如键盘按一个键值,鼠标移动的位移量。
  • 批量传输:

    主要用于传输大量的,但是对时间无要求的数据,比如读取U盘的数据。
  • 等时传输

    适用于数据量大且恒定的数据,比如USB摄像头。



Android提供使用 批量传输bulkTransfer()与控制传输controlTransfer()在端点上传输的数据

如果要完成中断与等时传输可以借助后面介绍的第三方C库:libusb

在进行传输之前,首先需要获取设备对应的UsbDeviceConnection对象

UsbDeviceConnection deviceConnection = usbManager.openDevice(usbDevice);
//锁定此UsbInterface (其实就是锁定端口,同时只能一处使用)
deviceConnection.claimInterface(usbInterface, true); // 控制传输
byte[] maxLun = new byte[1];
deviceConnection.controlTransfer(requestType,request, value, index, bytes, bytes.length, TIMEOUT);
// 批量传输
deviceConnection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT);

4、关闭通信

当完成与设备的通信后,需要调用 releaseInterface()close() 来关

UsbInterfaceUsbDeviceConnection

//关闭
deviceConnection.releaseInterface(usbInterface);
deviceConnection.close();

而如果是设备断开连接,可以通过广播监听:

BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//某个USB设备断开连接
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
UsbDevice device =
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null && //当前通信设备) {
deviceConnection.releaseInterface(usbInterface);
deviceConnection.close();
}
}
}

Android USB开发—USB通信的更多相关文章

  1. usb开发

    usb开发 USB HID报告及报告描述符简介 LibUSB通过SetReport()请求与USBHID设备通信 libusb开发者指南 USB枚举和HID枚举实例 USB命令 BusHound数据分 ...

  2. Android USB 开发详解

    Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...

  3. 【转】Android Service创建USB HOST通信

    之前做了一个关于Android USB通信的Case,通过Android的USB总线给Zigbee供电,和板载的Zigbee(基于Zigbee的自组网)进行通信.要使用Android的USB Host ...

  4. Android Service创建USB HOST通信

    之前做了一个关于Android USB通信的Case,通过Android的USB总线给Zigbee供电,和板载的Zigbee(基于Zigbee的自组网)进行通信.要使用Android的USB Host ...

  5. Android Studio关于USB device not found的解决的方法

    Android Studio关于USB device not found的解决的方法 我们使用Android Studio进行Android开发时.当我们使用真机进行调试时.非常可能会出现USB de ...

  6. 树莓派和STM32通过USB和串口通信记录

    不管怎样,为了简便开发,通信选择串口通信. 推荐文章:https://blog.csdn.net/magnetoooo/article/details/53564797 推荐测试工具:https:// ...

  7. Android OTG支持USB读卡器

    我们知道,三星Android手机将USB读卡器通过OTG线插入Micro USB插口后,插拔读卡器里的SD卡,文件管理器也能够识别卡的插拔:而很多手机的OTG连上USB读卡器也来插拔SD卡,会发现文件 ...

  8. android 串口开发第二篇:利用jni实现android和串口通信

    一:串口通信简介 由于串口开发涉及到jni,所以开发环境需要支持ndk开发,如果未配置ndk配置的朋友,或者对jni不熟悉的朋友,请查看上一篇文章,android 串口开发第一篇:搭建ndk开发环境以 ...

  9. 用DriverStudio开发USB驱动程序

    很多写Windows Device Driver的开发人员基本上都是使用Windows DDK进行开发的.但是,现在也有不少人都开始借助一些辅助工具.笔者去年开始接触到DriverStudio,发现它 ...

  10. USB开发库STSW-STM32121文件分析(转)

    源: USB开发库STSW-STM32121文件分析

随机推荐

  1. Java并发(八)----使用线程避免cpu占用100%

    1.sleep 实现 在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序 while(true ...

  2. 好书推荐之《码出高效》、《阿里巴巴JAVA开发手册》

    好评如潮 <阿里巴巴Java开发手册> 简介 <阿里巴巴Java开发手册>的愿景是码出高效,码出质量.它结合作者的开发经验和架构历程,提炼阿里巴巴集团技术团队的集体编程经验和软 ...

  3. js 实现call和apply方法,超详细思路分析

    壹 ❀ 引 我在 五种绑定策略彻底弄懂this 一文中,我们提到call,apply,bind属于显示绑定,这三个方法都能直接修改this指向.其中call与apply比较特殊,它们在修改this的同 ...

  4. NVME(学习笔记四)—概念解读

    1. 综述 NVMe over PCIe协议,定义了NVMe协议的使用范围.指令集.寄存器配置规范等. 名词解释 1.1.1 Namespace Namespace是一定数量逻辑块(LB)的集合,属性 ...

  5. ARP(Address Resolution Protocol) Packet

    Address Resolution Protocol The Address Resolution Protocol (ARP) is a communication protocol used f ...

  6. STC89C52控制74HC595,74HC138双色16x16点阵屏循环显示汉字

    简介 常见的LED点阵除了使用MAX7219, 还有一部分是使用74HC595, 前者能主动刷新, 后者需要上位机主动扫描刷新. 手里这块是德飞莱的16x16LED点阵模块, 板上印的型号LY-LED ...

  7. Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列

    写在开头 队列是Java中的一个集合接口,之前的文章已经讲解了List和Set,那么今天就来唠一唠它吧.队列的特点:存储的元素是有序的.可重复的. 队列的两大接口Queue vs Deque Queu ...

  8. Swoole从入门到入土(6)——TCP服务器[粘包]

    在了解Swoole下如何处理粘包问题之前,我们需要先了解什么是"粘包".我们以下面这张图进行普及: 假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是 ...

  9. Laravel入坑指南(12)——最终章:Session、缓存与Redis

    因为web服务除了业务准确之外,我们最关注的就是服务的性能.鉴于web服务几乎都是IO密集型,我们为了提高IO的速度,自然不能把所有的数据都放在关系型数据库中.而redis的并发与性能可以很好地帮我们 ...

  10. springboot中前端ajax如何给controller提交数组参数?

    说明 我有个需求,前端批量添加一堆商品明细.也就是说会有一个商品ID,然后一堆商品明细,多行. 如此一来,针对后端接口肯定是要以数组或列表方式接收这个商品明细数组了. 前端代码 关键地方在于以form ...