2012年11月1日 星期四

Android USB 應用之 HelloUsbUart(RS-232)

在NDK這篇HelloUart中,有人詢問到開發平台轉移至智慧型手機上應該如何實現呢??
前面寫了一個方法是透過無線藍牙 -- Android Bluetooth 應用之 HelloBTUart(RS-232)
本文延續這個問題,提供了第二個答案就是直接使用有線的USB來做 Virtual COM port (VCP) 。

Android自從Honeycomb (3.1)版本後就開始支援USB hosting機制,也就是說從3.1版後Android提供了android.hardware.usb類別來使用,把早先Android從Accessory的角色變成Host及Accessory(Device)兩種都可以的角色。
至目前為止,Android在USB hosting機制從一開始只支援USB Keyboard、Mouse到現在Jelly Bean陸續已經可以支援到USB儲存裝置、攝影機、網卡、搖桿、....等總類非常繁多。
所以,本篇實作的部分主要是如何在Android上使用USB裝置(UsbDevice API)。

【免責聲明】:本實作有可能會造成Android裝置或是相關USB的損毀,恐危及到相關設備的保固或維修,請評估是否可以自行承擔相關風險,如無把握請勿嘗試將不負任何責任。

實作環境:
ASUS 變形金剛2(TF201)
Google  nexus 7
USB to RS-232 (PL-2303)
OTG線

開發環境:
Win 7 SP1
Java 1.6.0_35
Eclipse 3.7.2
Android SDK r20.0.3

前置作業
1.準備OTG線一條:
所謂USB OTG(On-The-Go)簡單來說就是原先當Devices裝置可以變成Host裝置來控制其他USB Devices,至於要詳細認識OTG的話可以參考許永和老師的USB OTG這篇文章
或者參考USB OTG官方網站

製作或購買OTG線:
Google nexus 7 所採用的是標準的Micro-USB,2007年1月4日USB實裝論壇(USB-IF)頒布了Micro-USB的插頭標準,目前在歐盟也訂定了充電裝置接頭一律使用Micro-USB。
所以目前除了 nexus 7外目前很多手機、平板的充電接頭都是使用Micro-USB當作充電接頭。
所以如果喜歡DIY的人可以到電子材料行購買以下材料:
Micro-USB 公頭 x1
USB標準USB母頭(A型USB插座)  x1
USB線材(紅黑綠白)

然後,按照下圖進行焊接:

從上圖我們可以看出Micro-USB分別+5V、D+、D-、ID、GND等五隻接腳,其中在Android裝置上定義 :
1.ID、GND短路變成OTG線。
2.ID、GND斷路變成充電線。
3.ID、GND接100K歐姆電阻變成OTG/充電線。(目前網路上確認SONY手機可以這樣做,我測試過在nexus 7上這個方式沒作用)
上述詳細資訊請參考:https://sites.google.com/site/sonicboomworld/my-projects/otg-diagrams

另外,比較進階的可以改造USB HUB(如下圖),這樣Android就可以使用更多USB裝置了(在 nexus 7上還是沒有充電的功能)。


不過.........上述只是把一些觀念作解釋,如果懶得DIY的 ....最簡單的方法就是花錢購買現成的就可以了。目前手上購買的USB OTG線是在台中電子街今X電子所購得,價格大約在NT$60左右。


2.基本觀念:
在Android裝置中,USB可以扮演Host 及 Accessory角色。
http://developer.android.com/guide/topics/connectivity/usb/index.html

程式中配合這兩種角色的不同會去呼叫不同的API;
如果Android的供電設備作為USB Host則使用UsbDevice API,例如 :滑鼠、搖桿、攝影機、USB集線器等。
http://developer.android.com/guide/topics/connectivity/usb/host.html

如果USB設備當做USB Host ,那就要使用UsbAccessory API。例如: IOIO、Arduino 之類。
http://developer.android.com/guide/topics/connectivity/usb/accessory.html

簡單來說,哪個裝置可以供電誰就是Host,兩者細節部分請參考官網說明

在程式撰寫過程中,必須先弄清楚一些USB基本觀念的部分,例如 USB Architectural 、Data Flow Types、USB Communication Flow、VID、PID、Endpoint、....等等。否則不但看不懂程式外,連改程式都會不知如何去著手。礙於篇幅及主題,USB觀念部分除了到Google上搜尋外,另外就打聽一下敏哥何時會在逢甲星期四晚上USB課程,好好的吸收一下(還好我有認真聽敏哥上課!!)。

另外要討論的是需不需要 root 權限的問題,類似與硬體連接在Android下通常會需要 root 權限,主要是更改/dev目錄下裝置的權限,假如你使用的是 "Java Native Interface (JNI) Android實戰篇(使用NDK) -- HelloUart" 這篇的方式,你只要檢查當OTG連線時在有沒有 /dev/ttyUSBx的裝置,如果已有的情況下使用chmod 777 方法直接更改 /dev/ttyUSBx的權限,這時該文章的HelloUart可以達到同樣的功能。
同樣道理,如果你寫的是一個使用 USB 隨身碟,最簡單的方式寫個程式把該裝置掛載(mount)到一個目錄下就行了,這個時候同樣也會更改到 /dev目錄下 USB 隨身碟裝置的權限,所以同樣也需要用root權限。
至於使用root權限的程式撰寫方式,會再另外寫一篇文章來討論。

本篇主要是使用Android提供 USB 標準類別來做,所以執行本文的程式是不需要root的權限。
另外說明一下既然使用的是Android提供 UsbDevice API類別來寫程式,所以這篇文章的內容就跟NDK無關囉!!(不會用到C,不要再問ㄡ....^_^)

3.程式:
本文主要是實作以 nexus 7當作USB Host,以下是程式說明:
(1)獲取與設備通訊的權限

AndroidManifest.xml
在<intent-filter>與</intent-filter>加入
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
在</activity>前面加入
 <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"  android:resource="@xml/device_filter" />
在專案目錄 res 中建立一個 xml目錄,在xml目錄下建立一個device_filter.xml檔案,其內容定義USB的PID與UID這必須要視你連上的USB裝置而定,相關內容如下:



res/xml/device_filter.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-device vendor-id="1659" product-id="8963" />
</resources>


上述內容是以PL-2303 (USB to RS-232) 為範例,其中PS-2303的VID是067B轉 換成10進制等於1659;PID是2303轉 換成10進制等於8963。
上述的宣告,主要是使用在當USB插入時,系統偵測到鎖定一的VID及PID,會跳出如下圖的畫面;當選則確定後就會執行程式。

詳細參考:Manifest and resource file examples

(2)主程式部分我是參考  INDI server ( indiserver )所提供的原始碼中  PL2303 USB Serial Converter Driver (PL2303driver.java) ,網址如下:
https://code.google.com/p/indiserver/source/browse/INDIserver/branches/version2/src/de/hallenbeck/indiserver/communication_drivers/PL2303driver.java

主程式撰寫的部分,主要利用上述的程式外,程式內容大致上只要依序做到:
a. Initialization

mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
mUsbDevice = (UsbDevice) getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
mUsbIntf = mUsbDevice.getInterface(0);


mEndpointOut = mUsbIntf.getEndpoint(1); // endpoint addr 0x2 = output bulk
mEndpointIn = mUsbIntf.getEndpoint(2);   // endpoint addr 0x83 = input bulk


b.open()

  protected boolean openDevice() {
        try {
            mUsbConnection = mUsbManager.openDevice(mUsbDevice);
            mUsbConnection.claimInterface(mUsbIntf, true);
            Log.d(TAG, "Device opening... ");
            return true;
        } catch (Exception e) {
            Log.e(TAG, "Exception: " + e.getMessage());
            mUsbConnection = null;
        }
        return false;
    }


c. Register BroadcastReceiver

d. setup(BaudRate.B9600, DataBits.D8, StopBits.S1, Parity.NONE);  -->See PL2303driver.java

e. 用Thread或Timer做顯示 -->   getInputStream() -->See PL2303driver.java

f. 用Button.OnClickListener做送出 -->   getOutputStream()  -->See PL2303driver.java

g. close()

protected boolean closeDevice() {
        try {
            mUsbConnection.releaseInterface(mUsbIntf);
            mUsbConnection.close();
            mUsbConnection = null;
            mEndpointOut = null;
            mEndpointIn = null;
            Log.d(TAG, "Device closed. ");
        } catch (Exception e) {
            Log.e(TAG, "Exception: " + e.getMessage());
        }
        return false;
    }


至於程式碼部分,由於目前身邊工作太忙了沒時間可以好好的完整寫出,加上實在太多Bug及沒有考慮的因素,所以還在研究當中本文先秀出按照上述a~g個步驟所述方法目前初步的結果並且證明可行。
程式碼部分請參考文章後面所列出的參照說明的所有網頁,修改一下應該就可以寫出下面的結果了;日後待有空將程式寫完整後再慢慢分享程式碼。

4.執行結果:
以下是我做的初步執行結果:
環境:
(1) Nexus 7 <--> PL-2302 (USB to RS232) <--> GPS接收器 (沒座標,單純當作RS232產出訊息使用)
程式開始:
接著開始接收訊息:
執行過程:

後記:
1.以目前實作過程中USB裝置會使用Android裝置上的電力增加耗電量,加上Nexus 7 還找不到可以同時OTG也同時充電的方法,所以如果克服這個部分USB在Android裝置上利用會更加完整,或許未來有出 Nexus 7 Dock座應該就有解了。
[2013/02/01 測試了 ASUS原廠的Nexus 7 Dock座,同樣無法達到在OTG下同時充電]

2.寫類似的程式Debug也是難題之一,例如Nexus 7只有一個USB所以插上USB裝置時就無法使用DDMS來觀察Log,所以Debug部分我使用的是ASUS TF201來進行因為他的底座可以接電腦也有USB埠,解決了Debug的問題。(這個部分可以使用 adb tcpip方式完成,改天我在寫使用方式。)

3. 另外一種Debug方式比較複雜而且需要root還要花錢就是使用QuickSSHd,然後透過SSH client連到Android裝置上執行logcat。

4. Google play上類似的軟體:
OTG UART 超級終端機 Free
Slick USB 2 Serial Demo

5. 通常當遇到使用無線(藍牙)不穩定時,可以參考一下有線的方式連結,或許可以減少一些無謂的干擾。

==============2013/05/20補充=====================
原始碼部分,一直以來沒有時間可以去整理好,這幾天剛好在網路上看到有家廠商有釋出PL-2303原始碼,記得使用別人程式碼要尊重相關著作權以免觸法
http://www.oneping.com.tw/fileDownload.htm
下面連結是他們測試方法:
http://www.oneping.com.tw/Technical_Articles/t-PL2303-Android.htm

==============2013/11/24補充=====================
Prolific官方也提供了PL-2303的SDK,詳見:
http://www.prolific.com.tw/US/ShowProduct.aspx?p_id=230&pcid=41

==============相關閱讀=====================
Java Native Interface (JNI) Android實戰篇(使用NDK) -- HelloUart
Android Bluetooth 應用之 HelloBTUart(RS-232)

參考:
1.  INDI server ( indiserver ) , PL2303driver.java
https://code.google.com/p/indiserver/source/browse/INDIserver/branches/version2/src/de/hallenbeck/indiserver/communication_drivers/PL2303driver.java

2.嘗試在Android的USB主機功能。(日文,看不懂?!沒關係,用Google翻譯看吧!!)
http://blog.livedoor.jp/jun_dime/archives/51720766.html

3.Android USB Host + USB VCP Driver
http://www.ezequielaceto.com.ar/techblog/?cat=44

4.Simple OTG cable diagram
https://sites.google.com/site/sonicboomworld/my-projects/otg-diagrams

5.USB Host and Accessory, Android Developers
http://developer.android.com/guide/topics/connectivity/usb/index.html

6.USB Host
http://developer.android.com/guide/topics/connectivity/usb/host.html

18 則留言:

  1. 感謝蚊子提供寶貴文章。

    回覆刪除
  2. 最近想讓視訊攝影機透過OTG連結到手機上,請問我該怎麼做?

    http://brain.cc.kogakuin.ac.jp/research/usb-e.html
    這是我在網路上找到一篇成功的例子,
    他說的要在內核配置中開啟一些參數,但那個配置文件是在哪裡啊?

    麻煩大大能幫小弟講解一下~~謝謝

    回覆刪除
    回覆
    1. 這個網頁的程式可以用,不過你要確定:
      1.你的cam有支援V4L2(Video4Linux),http://www.ideasonboard.org/uvc/你到這個網站就可以查到了。
      2.這個程式使用NDK來寫與本篇不太一樣。
      3.NDK程式直接呼叫/dev/video0的裝置,所以你必須要root後執行chmod 777 /dev/video0
      3.執行他的程式就可以用了。

      刪除
  3. 不好意思因為剛接觸ANDROID又對LINUX不熟......
    大大您的意思是只要執行chmod 777就能夠執行那個程式了??不需要修改內核配置嗎?
    還有那個/dev/video0路徑在哪裡?我用手機裡的R.E管理器找不到 = =

    回覆刪除
    回覆
    1. 1.目前試過ASUS 變形金剛2(TF201) 及 Google nexus 7 這兩台可以不用修改kernel可以抓到/dev/video0, HTC手機不行,手上也沒其他裝置可測試所以其他就不清楚了.
      2.chmod 777 主要是權限設定給uid為shell可以使用,權限問題非三言兩語就說清楚這部份如果對Unix/Linux檔案系統不熟悉的話,可能找有關Linux檔案權限的或書局用力K一下了...

      刪除
  4. 您好!
    很感謝您的分享!讓我多了解了很多專業知識~
    其實我很想寫類似您的或是"OTG UART 超級終端機 Free"這樣的程式~
    但是我對於Java 及 Android的涉獵都很有限~
    只有看過一本"Android 初學特訓班"
    主要學到的都是版面layout及component的功能~
    就算您提供了開發步驟但我還是遇到困難~
    小弟太愚昧了~所以可以請問步驟a的程式要加在哪裡呢!
    是不是在MainActivity.java呢?

    因為我可以說是沒有Android的設計經驗~
    斗膽請問您是否願意分享source code供參考呢?
    謝謝!

    回覆刪除
  5. 大大~我想請問一下
    如果Nexus 7<-->OTG<-->USB HUB<-->{PL-2302 (USB to RS232) <-->GPS接收器.RFID讀卡機.等...

    這樣的架構目前ANDROID做得到嗎?

    回覆刪除
    回覆
    1. 可以的,參考本文及連結出去的那個原始碼網站可以完成這樣的需求。
      另外需要注意的是:
      1.PL-2303比較新版本的我還沒時間測試,目前我還不確定新版的PL-2303 Nexus 7能否使用。
      2.USB底層的觀念要弄清楚,不然程式很難寫。

      刪除
  6. 請問,如果是使用FDTI 的FT232R USB UART晶片
    在程式上撰寫需要注意的地方??

    回覆刪除
    回覆
    1. 1.PID,VID
      2. /dev目錄下有沒有抓到裝置,如果沒有請確認OTG線(換別家OTG試試),另外就是Kernel有沒有驅動(可以用dmesg檢查)
      3.USB底層的觀念要弄清楚,Data Flow Types、USB Communication Flow、Endpoint、....等等要先K書研究一下,其他只要按照我上面寫的a~g項目去做就行了,參考中有一些網頁有你可以逛逛看^^

      刪除
  7. 请教大大个问题:
    我用的是UsbDevice API,我想实现的需求是:一个U盘插在我的Android设备上的USB Host口,1.能检测到U盘
    2.在我的程序里界面上能显示U盘的内容(有那些文件夹,文件,类似于资源管理器)。
    现在确实能检测到U盘,第一条实现了
    第2条还没实现,我还没想到怎么遍历U盘里文件或文件夹
    UsbDevice API里有两种数据传输方法
    1.controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout)

    Performs a control transaction on endpoint zero for this device.
    2.bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)
    Performs a bulk transaction on the given endpoint.
    这两种方法也只是传输数据,要实现我说的显示U盘的内容(有那些文件夹,文件,类似于资源管理器)怎么实现那?
    请大大指教下

    回覆刪除
  8. 有那位大大知道不?盼解答

    回覆刪除
  9. 請問可以接案嗎?

    我需要OTG->傳送接收HEX 範例與控制器溝通
    email:wcs975@gmail.com

    回覆刪除
  10. 敏哥您好
    之前已經在Android 上實做該USB功能並且也可正常使用
    但最近(前段時間) Android 手機或平板更新後, 插上USB都出現無法辨識裝置

    想請教一下是否Android sdk 做了什麼修改, 或是我有什麼地方沒有注意到的

    如果能解答...萬分感激

    回覆刪除
  11. 你好,接觸android沒多久,對於架構還不太熟
    我想要做的是
    android手機(app) <-->usb OTG<-->FIDI FT311D(usb to uart)<-->sensor module

    我想要在app上面做一些操作去控制我的sensor module
    然後從sensor module讀回一些binary,經過轉換機制在app上面顯示成圖案

    請問這樣做得到嗎?
    可否提供些意見,感恩

    回覆刪除