1. 程式人生 > >Android 使用 Usb Accessory 模式與 linux 下位機進行通訊

Android 使用 Usb Accessory 模式與 linux 下位機進行通訊

這段時間研究了下Usb Accessory模式, 這個模式正常的使用方法是用來和專為Android裝置設計的USB主機硬體通訊的.
這種裝置需要使用Accessory Development Kit (ADK)來開發, 遵循Android Open Accessory (AOA)協議.

但是對於我們安卓開發者來說想嘗試下這個模式總不可能自己去用ADK來編寫一個驅動吧, 所以本文會在linux裡使用libusb庫來模擬aoa協議來作為外設端.

Android端:

USB Accessory有兩種API可供選擇, 第一種是android.hardware.usb包, 只能用在Android 3.1及之後的系統上, 不需要額外新增library, 第二種是com.android.future.usb包, 需要額外新增 Google APIs add-on library , 也就是谷歌服務, 但是可以執行在Android 2.3.4以及之後的系統上, 這裡我們使用第一種來演示, 第二種可以去

官方文件進行了解 .

首先在AndroidManifest檔案內新增如下配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.gavinandre.usbaccessory">
    <uses-feature android:name="android.hardware.usb.accessory"/>
    <application
        ...
<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檔案

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory
        manufacturer="Lutixia"
        model="Demo"
        version="1.0"
        />
</resources>

獲取Usb Accessory裝置後開啟Accessory模式

public AccessoryCommunicator(final Context context) {
        this.context = context;

        usbManager = (UsbManager) this.context.getSystemService(Context.USB_SERVICE);
        final UsbAccessory[] accessoryList = usbManager.getAccessoryList();

        if (accessoryList == null || accessoryList.length == 0) {
            onError("no accessory found");
        } else {
            openAccessory(accessoryList[0]);
        }
    }

注意: 官方文件內提供了兩種方法獲取UsbAccessory裝置, 第一種就是我使用的方法(UsbAccessory[] accessoryList = usbManager.getAccessoryList();)可以一次性獲取多個Accessory裝置, 但是目前Android系統還不支援同時與多個裝置進行通訊, 第二種方法是在廣播內獲取Accessory裝置, 但是需要取得許可權, 具體可以去官方文件檢視.

Accessory的開啟和關閉, 傳輸資料使用FileInputStream和FileOutputStream來進行讀寫

private void openAccessory(UsbAccessory accessory) {
        fileDescriptor = usbManager.openAccessory(accessory);
        if (fileDescriptor != null) {

            FileDescriptor fd = fileDescriptor.getFileDescriptor();
            inStream = new FileInputStream(fd);
            outStream = new FileOutputStream(fd);

            //接收訊息的執行緒
            new CommunicationThread().start();

            //傳送訊息的執行緒
            sendHandler = new Handler() {
                public void handleMessage(Message msg) {
                    try {
                        outStream.write((byte[]) msg.obj);
                    } catch (final Exception e) {
                        onError("USB Send Failed " + e.toString() + "\n");
                    }
                }
            };
        } else {
            onError("could not connect");
        }
    }

public void closeAccessory() {
        running = false;
        try {
            if (fileDescriptor != null) {
                fileDescriptor.close();
            }
        } catch (IOException e) {
        } finally {
            fileDescriptor = null;
        }
}

讀取訊息執行緒實現, 通過迴圈來不斷讀取不同的訊息

private class CommunicationThread extends Thread {
        @Override
        public void run() {
            running = true;

            while (running) {
                byte[] msg = new byte[Constants.BUFFER_SIZE_IN_BYTES];
                try {
                    //Handle incoming messages
                    int len = inStream.read(msg); //等待訊息時會阻塞在這裡
                    while (inStream != null && len > 0 && running) {
                        receive(msg, len);
                        Thread.sleep(10);
                        len = inStream.read(msg);
                    }
                } catch (final Exception e) {
                    onError("USB Receive Failed " + e.toString() + "\n");
                    closeAccessory();
                }
            }
        }
    }

介面部分具體看demo

Linux端:

首先需要安裝libusb和usb的標頭檔案

sudo apt-get install libusb-dev
sudo apt-get install libusb-1.0-0-dev

安裝好後用usb連線android和linux, 在linux下使用lsusb命令檢視usb裝置
這裡寫圖片描述
我的裝置是0e8d:2008, 記錄下自己裝置的VID和PID號

然後新增規則, 在ATTR{idVendor}==”0e8d”內替換你自己的VID

sudo vim /etc/udev/rules.d/51-android.rules
//在51-android.rules內新增兩條規則
SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"
//儲存後要新增許可權
sudo chmod a+r /etc/udev/rules.d/51-android.rules

開啟demo裡的usbacc.c檔案, 更改你自己的VID和PID

/* Android device vendor/product */
#define AD_VID 0x0e8d
#define PID 0x2008

將makefile和usbacc.c放在同一目錄後使用make編譯並執行

make
...
./usbacc

這時如果出現Error setting up accessory的話, 使用lsusb檢視一下usb資訊
這裡寫圖片描述
用你accessory模式的vid和pid替換usbacc.c裡相應的數值

#define GOOGLE_VID 0x18d1
#define ACCESSORY_PID 0x2d01 /* accessory with adb */
#define ACCESSORY_PID_ALT 0x2d00 /* accessory without adb */

Android裝置如果打開了除錯模式的話,在如下位置使用ACCESSORY_PID_ALT來代替ACCESSORY_PID

if ((handle = libusb_open_device_with_vid_pid(NULL,
              GOOGLE_VID, ACCESSORY_PID_ALT)) == NULL) {

如果出現LIBUSB_ERROR_IO錯誤如下:

Error: LIBUSB_ERROR_IO
Input/output error.
Done, no errors

就使用如下命令可以看到bEndpointAddress值, 18d1:2d01是你進入accessory後的vid和pid

lsusb -v -d 18d1:2d00 | grep bEndpointAddress
        bEndpointAddress     0x81  EP 1 IN
        bEndpointAddress     0x02  EP 2 OUT

在這裡進行相應更改

#define EP_IN 0x81
#define EP_OUT 0x02

如果一切正常的話, 第一次進入accessory應該會出現如下提示
這裡寫圖片描述
如果沒有出現該提示但是卻進入accessory模式了, 可能會出現linux端能接收但是不能傳送的情況, 這時試試換臺手機測試, 我在這裡卡了兩天……

最後看一下通訊的效果
這裡寫圖片描述
這裡寫圖片描述