Android實戰技巧之四十九:Usb通訊之USB Host
零 USB背景知識
USB是一種資料通訊方式,也是一種資料匯流排,而且是最複雜的匯流排之一。
硬體上,它是用插頭連線。一邊是公頭(plug),一邊是母頭(receptacle)。例如,PC上的插座就是母頭,USB裝置使用公頭與PC連線。
目前USB硬體介面分三種,普通PC上使用的叫Type;原來諾基亞功能機時代的介面為Mini USB;目前Android手機使用的Micro USB。
Host
USB是由Host端控制整個匯流排的資料傳輸的。單個USB總線上,只能有一個Host。
OTG
On The Go,這是在USB2.0引入的一種mode,提出了一個新的概念叫主機協商協議(Host Negotiation Protocol),允許兩個裝置間商量誰去當Host。
一、Android中的USB
Android對Usb的支援是從3.1開始的,顯然是加強Android平板的對外擴充套件能力。而對Usb使用更多的,是Android在工業中的使用。Android工業板子一般都會提供多個U口和多個串列埠,它們是連線外設的手段與橋樑。下面就來介紹一下Android Usb使用模式之一的USB Host。
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
二、USB插入事件
Usb的插入和拔出是以系統廣播的形式傳送的,只要我們註冊這個廣播即可。
@Override
protected void onResume() {
super.onResume();
IntentFilter usbFilter = new IntentFilter();
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, usbFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mUsbReceiver);
}
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
tvInfo.append("BroadcastReceiver in\n");
if(UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
tvInfo.append("ACTION_USB_DEVICE_ATTACHED\n");
} else if(UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
tvInfo.append("ACTION_USB_DEVICE_DETACHED\n");
}
}
};
三、Usb插入時啟動程式
有些應用場景是,Usb插入後啟動特定程式處理特定問題。
我們的做法就是在Manifest中某個Activity加入Usb插入的action。
<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/usbfilter" />
在usbfilter中加入廠商id和產品id的過濾,如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1234" product-id="5678" />
</resources>
結果就是,當此型號裝置通過Usb連線到系統時,對應的Activity就會啟動。
四、UsbManager的初始化
mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
五、列出Usb裝置
HashMap<String,UsbDevice> deviceHashMap = mUsbManager.getDeviceList();
Iterator<UsbDevice> iterator = deviceHashMap.values().iterator();
while (iterator.hasNext()) {
UsbDevice device = iterator.next();
tvInfo.append("\ndevice name: "+device.getDeviceName()+"\ndevice product name:"
+device.getProductName()+"\nvendor id:"+device.getVendorId()+
"\ndevice serial: "+device.getSerialNumber());
}
六、USB使用許可權
安卓系統對USB口的使用需要得到相應的許可權,而這個許可權要使用者親自給才行。
首先我們會確認一下上一節中的device是否已經獲得許可權,如果沒有就要主動申請許可權:
//先判斷是否為自己的裝置
//注意:支援十進位制和十六進位制
//比如:device.getProductId() == 0x04D2
if(device.getProductId() == 1234 && device.getVendorId() == 5678) {
if(mUsbManager.hasPermission(device)) {
//do your work
} else {
mUsbManager.requestPermission(device,mPermissionIntent);
}
}
我們仍然使用廣播來獲得許可權賦予情況。
public static final String ACTION_DEVICE_PERMISSION = "com.linc.USB_PERMISSION";
註冊廣播
mPermissionIntent = PendingIntent.getBroadcast(this,0,new Intent(ACTION_DEVICE_PERMISSION),0);
IntentFilter permissionFilter = new IntentFilter(ACTION_DEVICE_PERMISSION);
registerReceiver(mUsbReceiver,permissionFilter);
接收器的程式碼:
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
tvInfo.append("BroadcastReceiver in\n");
if (ACTION_DEVICE_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
tvInfo.append("usb EXTRA_PERMISSION_GRANTED");
}
} else {
tvInfo.append("usb EXTRA_PERMISSION_GRANTED null!!!");
}
}
}
}
};
七、通訊
UsbDevice有了許可權,下面就可以進行通訊了。
這裡要用到:UsbInterface、UsbEndpoint(一進一出兩個endpoint,雙向通訊)、UsbDeviceConnection。
注意:通訊的過程不能在UI執行緒中進行。
得到授權後,將做一些通訊前的準備工作,如下:
private void initCommunication(UsbDevice device) {
tvInfo.append("initCommunication in\n");
if(1234 == device.getVendorId() && 5678 == device.getProductId()) {
tvInfo.append("initCommunication in right device\n");
int interfaceCount = device.getInterfaceCount();
for (int interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
UsbInterface usbInterface = device.getInterface(interfaceIndex);
if ((UsbConstants.USB_CLASS_CDC_DATA != usbInterface.getInterfaceClass())
&& (UsbConstants.USB_CLASS_COMM != usbInterface.getInterfaceClass())) {
continue;
}
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint ep = usbInterface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
mUsbEndpointIn = ep;
} else {
mUsbEndpointOut = ep;
}
}
}
if ((null == mUsbEndpointIn) || (null == mUsbEndpointOut)) {
tvInfo.append("endpoint is null\n");
mUsbEndpointIn = null;
mUsbEndpointOut = null;
mUsbInterface = null;
} else {
tvInfo.append("\nendpoint out: " + mUsbEndpointOut + ",endpoint in: " +
mUsbEndpointIn.getAddress()+"\n");
mUsbInterface = usbInterface;
mUsbDeviceConnection = mUsbManager.openDevice(device);
break;
}
}
}
}
傳送資料如下:
result = mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut, mData, (int)buffSize, 1500);//需要在另一個執行緒中進行
八、其他
作為一個普通的開發者,如果沒有USB裝置,那麼除錯程式是個問題。
可以使用AdbTest程式用OTG線連線兩個手機或平板試試。
有USB裝置的同事,會根據裝置的通訊協議規則去編碼。這裡面要用到byte與其他型別轉換,以及十六進位制的問題。
具體問題具體分析吧。這篇文章磕磕絆絆,就先到這裡了。