2011年5月16日 星期一

Java Native Interface (JNI) Android實戰篇(使用NDK) -- HelloUart

經過Java Native Interface (JNI)入門 -- 觀念篇Java Native Interface (JNI) 實戰篇後,延續著前兩篇的觀念第三篇繼續討論在Android上實作,本篇主要學習目的是在Android上實現針對UART的傳輸題目就沿用Hello系列稱為HelloUart。


執行環境:
A.開發平台
DMA-6410L or DMA-6410XP
Kernel 2.6.29
Android 2.1 (Eclair)

B.開發環境
個人電腦
Ubuntu 10.10
Eclipse 3.5.2
Android NDK, r5b

C.測試環境
個人電腦 (Win7)-DMAComDebuger.exe
DMA-6410
GPS接收器



建立一個HelloUart專案,程式部分架構如下圖所示:


其中MainActivity.java是主要,Uart2C.java的部分是將Java對C的部分獨立出來寫成一個java,我覺得將這部分獨立這樣寫程式的方式很好,主要是參考一些Demo程式而來。
另外jni目錄夾要自己建立,裡面的必須要自行建立Android.mk及hello-uart.c兩個檔案;
還有libs是由NDK所自動產生詳見先前所撰寫的"第一支Android NDK程式--HelloJni"。

程式說明:
1. main.xml 使用 RelativeLayout (相對佈局)方式,用到有:
ScrollView : @+id/uart_scrollview
TextView : @+id/uart_view
EditText : @+id/uart_edit
Spinner : @+id/uart_select 、 @+id/uart_mode
Button : @+id/uart_button0 (OPEN) 、 @+id/uart_button1 (CLEAR) 、 @+id/uart_button2 (CLOSE) 、 @+id/uart_button3 (EXIT) 、 @+id/uart_button4 (SEND)
這部份算基本功礙於篇幅這部分就忽略說明,我所建立的執行畫面如下:


2.MainActivity.java
主要使用 timer.schedule(task, 1000, 100)這行作為訊息接收;


透過 Spinner 來選擇串列埠及Baud rate;

Button0 處理uart的開啟


Button1 清除接收畫面

Button2 處理uart的關閉

Button3 處理程式關閉

Button4 處理送出訊息


3.Uart2C.java
主要是呼叫hello-uart.c,並宣告native方法。


4.Android.mk
在jni目錄下建立了一隻hello-uart.c,因此編輯一個android.mk如下:


5.hello-uart.c
這裡須注意的是一定要按照Android規範,例如:
JNIEXPORT jint JNICALL Java_idv_android_hellouart_Uart2C_openUart(JNIEnv *env,jobject mc, jint i)
在Java_..._openUart部分一定要照下列規範否則編譯後Android程式會找不到相對應的native函數名,例如:

Java_Android Package Name_類名稱_native函數名,並注意底線及大小寫
Android Package Name : idv_android_hellouart
類名稱 : Uart2C
native函數名 : openUart

以下是程式內容,
程式開頭:

開啟UART

關閉UART

設定UART

送出訊息

接收訊息


編譯程式:
上述程式完成後存檔,由於這次我的開發環境在Ubuntu下,所以在編譯C的部分我使用的方法如下:
編輯 .profile 增加如下內容:
#Android NDK
SDK_ROOT= android-sdk-linux_x86 所在目錄,須注意要有可讀寫權限
NDK_ROOT= android-ndk-r5b 所在目錄,須注意要有可讀寫權限
PATH=$SDK_ROOT/tools:$NDK_ROOT:$PATH
NDK_Sample=$NDK_ROOT/samples
export PATH NDK_ROOT NDK_Sample
編輯完成後存檔後重新登入。


到程式所在目錄下執行ndk-build:


在Eclipse下執行Run把apk及so安裝至Android開發平台即完成。

執行結果:
下圖是執行後的畫面:
環境:
開發平台 <--> PL-2303 <--> GPS接收器

開發平台 <--> UART1 <--> GPS接收器

開發平台 <--> UART1 <--> Null modem 線(交叉線) <--> PC RS-232 ComPort <--> DMAComDebuger



以上是HelloUart程式內容及講解,這隻程式應該還有不少可以修正的Bug,我在執行時有時候開啟時會遇到VM abort導致跳出程式,絕大多看到的訊息時接受到亂碼所導致,這部分改良程式或測試環境後也許可以更好些。

所以各位如果遇到類似上述問題:
1.不要懷疑記得使用logcat來找問題;使用logcat至少可以解90%以上的問題。
2.建議使用 開發平台 <--> UART1 <--> Null modem 線(交叉線) <--> PC RS-232 ComPort <--> 超級終端機 or DMAComDebuger(DMA-6410XP所附光碟中) 的環境來測試傳送與接收,與PC連結 Baud rate 建議設9600或115200。
3.如果要測試 開發平台 <--> PL-2303 要先確認 kernel是否載入PL-2303相關驅動,否則建議使用第2項方式進行測試。
4.HelloUart是參考DMA-6410XP所附光碟中原始碼,詳見光碟內容。

參考資料:
1.DMA-6410XP所附光碟
2.在Android下建立RS232通訊應用程式心得分享
http://cheng-min-i-taiwan.blogspot.com/2010/04/androidrs232.html

==============延伸閱讀=====================
[ Android RS232 ] 在Android系統下實作RS232應用程式

==============相關閱讀=====================
Android Bluetooth 應用之 HelloBTUart(RS-232)

Android USB 應用之 HelloUsbUart(RS-232)

17 則留言:

  1. 感謝蚊子提供這麼棒的文章

    回覆刪除
  2. 您好
    最近參考您的文章學習JNI在Android中的應用
    作為我之後再Android開發板上的RS232應用的學習
    程式大致上沒甚麼問題
    唯獨res\layout\main.xml我沒辦法像您的範例一樣將每個物件
    分配的那麼漂亮
    可否跟您索取程式碼供我學習
    另外想請問您在這個範例中
    於AndroidManifest.xml檔案中有需要修改的地方嗎?
    我的信箱是fatalcord@gmail.com
    由衷感謝

    回覆刪除
  3. 另外我依照您的程式範本寫了一個專案
    但在模擬器執行時按下OPEN程式就會強制關閉
    請問這是正常的現象嗎?
    初學JNI,有許多不懂的地方
    如果有造成您的不便,深感抱歉

    回覆刪除
  4. 我的經驗,強制關閉的話大多會出現的問題:
    1.可能找不到so檔案
    2.所呼叫的native參數在so檔案內對應不到,詳見文章中5.hello-uart.c說明,我的經驗通常是大小寫錯誤了。
    3.Activity輸出(顯示)錯誤,例如傳回值為int但顯示為String之類。
    4.使用C++(副檔名為cpp),主要要多寫一個ON_Load程式,這部分過幾天我分享文章出來說明。
    其他部分,建議用logcat+Log來追中~因為我就遇過我的RS-232干擾後收到非ASCII的亂碼導致程式中斷。

    回覆刪除
  5. 作者已經移除這則留言。

    回覆刪除
  6. 您好,想請教一下
    若將開發平台轉移至智慧型手機上
    應該如何實現呢??

    因為智慧型手機似乎不能看到目前將設備辨識為何個comport
    所以想向您請教一下

    回覆刪除
  7. 您好:
    關於在智慧型手機做類似的功能,手機原廠大多不會提供相關硬體對應的資訊,況且手機廠商所提供的核心驅動部分我們也無法進行客製或修改,所以類似的功能要在手機上實作通常機會不大;不過在手機上同樣可以使用JNI,如果要一定要在手機上使用RS-232功能的話,建議使用IOIO會簡單些,不妨試試看。

    回覆刪除
  8. 作者已經移除這則留言。

    回覆刪除
  9. 作者已經移除這則留言。

    回覆刪除
  10. 板主您好:有幾個問題想請教一下;我所使用的開發平台為華亨的6410,我按照文章中所教的步驟,已經把APK安裝到平台上了,但是設定好COM PORT與鮑率之後,都無法開啟串列埠,請問這是什麼情況呢?

    回覆刪除
  11. 這可能會是系統中沒有COM Port您可以查詢看看/dev目錄下是否有您指定的tty...例如ttyS0,ttyS1 or ttyUSB等

    回覆刪除
  12. 請問如果我資料夾是放在eclipse的workspace 要怎麼設定 .profile ?
    才能產生.so檔呢

    回覆刪除
    回覆
    1. 我的作法很簡單
      $ cd /cygdrive/c/..../Workspace/HelloUart
      $ ndk-build
      Cygwin : Generating dependency file converter script
      Compile thumb : hello-uart <= hello-uart.c
      SharedLibrary : libhello-uart.so
      Install : libhello-uart.so => libs/armeabi/libhello-uart.so

      您試試看....

      刪除
  13. 蚊子您好:請教一下關於JNI傳送字元陣列顯示於EditText上的問題,
    目前寫一個簡單的JNI傳送於EditText上作顯示,JNI的程式如下:

    Method-1:
    char buf[3]={3,22,11};
    return (*env)->NewStringUTF(env, buf);

    Method-2:
    jbyteArray LTH;
    char lth[32];
    int i,j;
    LTH = (*env)->NewByteArray(env, 32);
    for(i=0;i<32;i++){
    lth[i] = '12';
    }

    for(j=0;j<32;j++){
    (*env)->SetByteArrayRegion(env, LTH, j, 1, lth[j]);
    }
    return LTH;

    以上兩種方法在EditText上作顯示時,都會以亂碼的方式呈現,
    請教蚊子大大是否有其他更好的方式作為顯示呢!!

    回覆刪除
    回覆
    1. 因為你程式裡面用的是char試看看return不要傳回用String,用NewCharArray看看(或其他)。可以Google搜尋一下"The Java Native Interface Programmer's Guide and Specification"這個PDF裡面可以找到。

      刪除
    2. 嗯 上網看一下它們是使用ByteArray的方式,我在看試試看,謝謝蚊子大大歐!!

      刪除
  14. 板主您好:

    有個問題想請教一下
    在 open uart port時, 是否 /dev/ttyS1 的權限要為 666才能開啟
    否則是否 open後會回傳 -1
    如果是一般手機沒 root權限是否 open就不能用了 ?

    謝謝

    回覆刪除