Android usb通信
一、前言
最近在学习android的usb开发,写了一个android手机和arduino开发板通信的Demo。和其他开发板或设备进行usb通信,原理都是一样的。
写篇博客记录一下。
按照惯例,先看效果图:
最后一张图是我用到的arduino设备和用于连接手机的数据线,需要一个usb转tc的转接头才能插到手机上
二、开始
1、AndroidManifest.xml清单文件
首先,要在清单文件中声明使用 usb host 功能:
<uses-feature android:name="android.hardware.usb.host" android:required="true"/>
然后,在主Activity中添加以下内容,用于接收有关连接的 USB 设备的通知。
在<intent-filter>
标签中添加:
`<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />`
添加<meta-data>
标签:
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" />
在res/xml 文件夹下,创建一个名为device_filter的xml文件,
在device_filter中声明一些usb设备信息,当对应的usb设备插入时就会弹出对话框,询问是否打开应用。其作用就是当文件中指定的设备插入时,立即发出通知,打开应用。也可以什么都不写。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="6790" product-id="29987" />
</resources>
完整清单文件代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.usbdemo">
<uses-feature android:name="android.hardware.usb.host" android:required="true"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.USBDemo">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<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>
2、创建权限广播接收者
创建一个监听连接usb权限的广播接收者,并在onCreate方法中进行广播注册。
private static final String ACTION_USB_PERMISSION = "ACTION.USB_PERMISSION";
private final BroadcastReceiver usbPerMissionReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive");
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
//获取连接权限成功
}
} else {
Toast.makeText(MainActivity.this,"访问权限被拒绝",Toast.LENGTH_SHORT);
//Log.d(TAG, "访问权限被拒绝 " + mDevice);
}
}
}
}
};
注册广播接收者:
permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbPerMissionReceiver, filter);
3、枚举usb设备
通过 usbManager.getDeviceList() 获取已插入的usb设备列表,对感兴趣的设备发出连接权限请求,在上面的广播接收者中处理请求结果
mUsbManager = (UsbManager) this.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext()) {
mDevice = deviceIterator.next();
//因为只有一个设备插入,所以直接获取该设备的接口及端点
getEndpoint();
//请求访问/连接usb设备的权限
mUsbManager.requestPermission(mDevice, permissionIntent);
}
4、获取usb接口以及输入/输出端点
一个usb设备可以有多个接口(UsbInterface),一个接口也有多个端点(UsbEndpoint),我的这个arduino nano板只有一个接口。
你可以通过 usbDevice.getInterfaceCount()
来查看你的usb设备有多少个接口,一般使用第一个接口就行。接口中有多种端点,有控制端点和数据传输端点,我们需要的是数据传输端点。通过endpoint.getType()
来分辨是哪种端点,类型为UsbConstants.USB_ENDPOINT_XFER_BULK
的就是数据传输端点。我们还得分别获取数据端点中的输入和输出端点,用来传输数据。可以通过端点的getDirection()方法获取端点的传输方向: UsbConstants.USB_DIR_IN
或 UsbConstants.USB_DIR_OUT
。
下面我写的这个getEndpoint()方法就包含了上面的内容,在获取设备后,调用该方法:
private void getEndpoint() {
inft = mDevice.getInterface(0);
Log.d(TAG,"Interface Count:" + mDevice.getInterfaceCount());
endpointCount = inft.getEndpointCount();
for (int i = 0; i < endpointCount; i++) {
if (inft.getEndpoint(i).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (inft.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) {
mEndpointIN = inft.getEndpoint(i);
Log.d(TAG, "获取到mEndpointIN");
} else if (inft.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_OUT) {
mEndpointOUT = inft.getEndpoint(i);
Log.d(TAG, "获取到mEndpointOUT");
}
}
}
}
5、打开设备
在成功获取权限后,在广播接收者中顺便打开设备,打开设备很容易,调用usb设备实例的openDevice(UsbDevice device)
方法就可以了。
mUsbConnection = mUsbManager.openDevice(mDevice);
广播接收者的完整代码继续往下看
6、设置波特率
关于如何设置波特率,网上很多文章都没有提到,但在我学习的过程中,发现如果没有设置和arduino端同样的波特率就会收发数据出错。别人有没有遇到这个问题我不知道,但我觉得还是有必要设置的。我在csdn找到了一篇博客关于如何设置波特率的,但没有过多的解释,只有代码。我也看不懂,希望大伙有看得懂的教一下我。代码如下:
private boolean configUsb(int paramInt) {
byte[] arrayOfByte = new byte[8];
mUsbConnection.controlTransfer(192, 95, 0, 0, arrayOfByte, 8, 1000);
mUsbConnection.controlTransfer(64, 161, 0, 0, null, 0, 1000);
long l1 = 1532620800 / paramInt;
for (int i = 3; ; i--) {
if ((l1 <= 65520L) || (i <= 0)) {
long l2 = 65536L - l1;
int j = (short) (int) (0xFF00 & l2 | i);
int k = (short) (int) (0xFF & l2);
mUsbConnection.controlTransfer(64, 154, 4882, j, null, 0, 1000);
mUsbConnection.controlTransfer(64, 154, 3884, k, null, 0, 1000);
mUsbConnection.controlTransfer(192, 149, 9496, 0, arrayOfByte, 8, 1000);
mUsbConnection.controlTransfer(64, 154, 1304, 80, null, 0, 1000);
mUsbConnection.controlTransfer(64, 161, 20511, 55562, null, 0, 1000);
mUsbConnection.controlTransfer(64, 154, 4882, j, null, 0, 1000);
mUsbConnection.controlTransfer(64, 154, 3884, k, null, 0, 1000);
mUsbConnection.controlTransfer(64, 164, 0, 0, null, 0, 1000);
return true;
}
l1 >>= 3;
}
}
7、创建接收数据的线程
创建一个线程来持续接收数据,在打开设备之后开启线程就可以。
发送/接收 数据都是调用UsbConnection实例的同一个方法:bulkTransfer
bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)
该方法的参数分别是:
UsbEndpoint endpoint
数据输入/输出端点(接收数据用输入端点)
byte[] buffer
接收或发送的数据,类型是字节数组
int length
字节数组的长度
int timeout
超时的时间,单位毫秒
该方法的返回值是一个整数,是实际传输的字节数。
public class MyThread extends Thread {
private boolean isReceive = true;
String message = null;
byte[] bytes = new byte[5];
@Override
public void run() {
super.run();
while (isReceive) {
int i = mUsbConnection.bulkTransfer(mEndpointIN, bytes, 0, bytes.length, 3000);
if(i < 0){
Log.d(TAG,"没有收到数据。。。");
}else{
try {
message = new String(bytes,"UTF-8");
Log.d(TAG, "接收到数据:" + message);
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(message);
}
});
isReceive = false;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
}
结合上面说到的打开usb设备,设置波特率,开启线程,把广播接收者的代码补充完整:
private final BroadcastReceiver usbPerMissionReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive");
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
//获取连接权限成功
mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (mDevice != null) {
//打开设备
mUsbConnection = mUsbManager.openDevice(mDevice);
mUsbConnection.claimInterface(inft,true);
if (mUsbConnection != null) {
Log.d(TAG, "连接设备成功");
Toast.makeText(MainActivity.this,"连接设备成功",Toast.LENGTH_SHORT);
}
//设置波特率
configUsb(9600);
//开启接收数据线程
myThread = new MyThread();
myThread.start();
}
} else {
Toast.makeText(MainActivity.this,"访问权限被拒绝",Toast.LENGTH_SHORT);
//Log.d(TAG, "访问权限被拒绝 " + mDevice);
}
}
}
}
};
8、发送数据
创建一个方法用来发送数据,发送数据也是调用bulkTransfer()方法,第一个参数要传入usb数据输出端点。可以用该方法的返回值来判断是否发送成功。
private void sendMessage(String msg) {
byte[] bytes = msg.getBytes();
if (mUsbConnection != null) {
int result = mUsbConnection.bulkTransfer(mEndpointOUT, bytes, bytes.length, 3000);
if (result < 0) {
Log.d(TAG, "发送失败");
} else {
Log.d(TAG, "发送成功");
}
} else {
Log.d(TAG, "mUsbConnection-->null");
}
}
9、断开连接
当完成通信,断开设备连接后,调用UsbConnection实例的releaseInterface()
和close()
方法来关闭UsbInterface 和 UsbConnection
创建一个广播接收者来监听设备连接断开的广播:
BroadcastReceiver usbDetachedReceiver = 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) {
mUsbConnection.releaseInterface(intf);
mUsbConnection.close();
mDeviceDetail.setText("设备已断开连接");
}
}
}
};
结束
Android usb通信的简单过程大概就是这样。
你也可以查看官方文档来学习:
https://developer.android.google.cn/guide/topics/connectivity/usb/host?hl=zh_cn
如果这篇文章对你有所帮助,点个赞吧,嘻嘻。