Android串口調試助手實現
時間:2018-08-16 來源:未知
串行接口 (Serial Interface) 是指數據一位一位地順序傳送,其特點是通信線路簡單,只要一對傳輸線就可以實現雙向通信(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用于遠距離通信,但傳送速度較慢。一條信息的各位數據被逐位按順序傳送的通訊方式稱為串行通訊。串行通訊的特點是:數據位的傳送,按位順序進行,少只需一根傳輸線即可完成;成本低但傳送速度慢。串行通訊的距離可以從幾米到幾千米;根據信息的傳送方向,串行通訊可以進一步分為單工、半雙工和全雙工三種。
日常中的很多設備都是通過串口來傳輸數據的。所以,本項目在安卓的平臺上,建立了一個通過串口來收發數據的平臺。用戶可以通過設定不同的參數來連接不同的串口。
第 1 章 使用說明

軟件共分為三個部分:數據接收區,數據發送區,參數設置區。
使用之前需要設置參數:需要打開的設備文件和打開的波特率。
點擊Open就可以打開串口,如果這時候串口有數據過來,就可以在左側顯示出來,同時可以設定是否以十六進制顯示數據。
如果想要向串口發送數據,在下方輸入數據,點擊Send就可以發送。
第 2 章 環境搭建
2.1 Android 開發環境的安裝與配置
Android應用軟件開發需要的開發環境在路徑“光盤\Android應用開發環境\”下:
JDK: JDK\JDK8\jdk-8u5-windows-i586.exe(32bit)或者jdk-8u5-windows-x64.exe(64bit)
(從JDK 8.0開始不支持Windows XP操作系統,使用Windows XP的用戶可以使用JDK7目錄下的內容)
ADT: adt-bundle-windows-x86.7z(32bit)或者adt-bundle-windows-x86_64.7z(64bit)
以下主要介紹在Windows環境下搭建Android開發環境的步驟和注意事項。
2.2 安裝JDK和配置Java開發環境
雙擊JDK\JDK8\jdk-8u5-windows-i586.exe(32bit操作系統)或者jdk-8u5-windows-x64.exe(64bit操作系統)進行安裝(從JDK 8.0開始不支持Windows XP操作系統,使用Windows XP的用戶可以使用JDK7目錄下的內容選擇代替JDK8目錄下的內容)。接受許可證,選擇需要安裝的組件和安裝路徑后,單擊“下一步”按鈕,完成安裝過程。

安裝完成后,利用以下步驟檢查安裝是否成功:打開Windows CMD窗口,在CMD窗口中輸入java –version命令,如果屏幕出現下圖所示的代碼信息,說明JDK安裝成功。
XP下安裝JDK7如下:

非XP下安裝JDK8如下:

2.3 解壓adt-bundle-windows
JDK安裝成功后,使用軟件解壓ADT目錄下的adt-bundle-windows-x86.7z(32bit)或者adt-bundle-windows-x86_64.7z(64bit)。
注意:解壓路徑不包含中文;

2.4 運行Eclipse
解壓完畢后,直接執行其中的eclipse\eclipse.exe文件,Eclipse可以自動找到用戶前期安裝的JDK路徑。

2.5 配置Eclipse
運行解壓目錄下的eclipse\eclipse.exe,為自己選擇一個工作目錄Workspace,不要有中文路徑,不選擇默認也可以。


需要為Eclipse關聯SDK的安裝路徑,即解壓路徑下的sdk目錄。在Eclipse中,點擊Window->Preferences,會看到其中添加了Android的配置,按圖所示的操作,然后點擊Apply,后點擊OK即可。

完成以上步驟后,設置Eclipse環境

勾選Android相關的工具,點擊OK(如果已經勾選,則不理會)。


第 3 章 NDK環境配置
3.1 安裝NDK工具包
安裝包已經放到“華清遠見開發環境”光盤當中,名字為“android-ndk-r10d-windows-x86”,這個是針對32位系統使用的工具包,如果有64位的需求可以到我們提供的網盤上進行下載。
將安裝包拷貝到E:盤,雙擊程序即可在當前路徑進行安裝。
3.2 配置Eclipse
打開Eclipse,點Window->Preferences->Android->NDK,設置NDK路徑,例如E:\ android-ndk-r10d

新建一個Android工程,在工程上右鍵點擊Android Tools->Add Native Support...,然后給我們的.so文件取個名字,例如:my-ndk

這時候工程就會多一個jni的文件夾,jni下有Android.mk和my-ndk.cpp文件。Android.mk是NDK工程的Makefile,my-ndk.cpp就是NDK的源文件。
完成了,然后運行。運行之前先編譯NDK,然后在編譯JAVA代碼。編譯也許會遇到Unable to launch cygpath. Is Cygwin on the path等問題?如何解決?如下:
工程右鍵,點Properties->C/C++ Build的Building Settings中去掉Use default build command,然后輸入${NDKROOT}/ndk-build.cmd

在C/C++ Build中點擊Environment,點Add...添加環境變量NDKROOT,值為NDK的根目錄

接著,按照如下圖所示的位置,根據使用的SDK的版本的不同選擇不同的頭文件包,例如如果使用的是android4.0.3 的話,就選擇:E:\ android-ndk-r10d\platforms\android-15\arch-arm\usr\include

之后,再次編譯運行工程,即可成功。
第 1 章 源碼編譯
1.1 導入源碼
打開Eclipse環境,選擇File->Import。

然后,導入光盤資料中的“BlueHelper”工程,勾選下圖中的選項。

點擊finish完成工程的導入
1.1 運行程序
注意:如果在調試開發板的時候,出現ADB連接不上的問題(已知華清遠見FSPAD723開源平板),可以試著替換Android SDK的ADB工具(把光盤\Android應用開發環境\ADB\ADB1.0.26\下的4個文件拷貝到用戶ADT解壓目錄下的sdk\platform-tools中)

開發期間,在實際的設備上運行Android程序與在模擬器上運行該程序的效果幾乎相同,需要做的就是用USB電纜連接手機與計算機,并安裝一個對應的設備驅動程序。如果模擬器窗口已打開,請將其關閉。只要將開發平臺通過USB下載線與計算機相連,應用程序就會在開發平臺上加載并運行。
在Eclipse中選擇“Run” →“Run”(或Debug)命令(或者在工程上點擊右鍵),這時會彈出一個窗口,讓你選擇用模擬器還是手機來顯示,如果選擇手機,即可在手機上運行該程序。


第 2 章 詳細設計
2.1 UartTool串口工具
因為本項目要連接上層java和底層c語言,所以需要先聲明本地方法。
NormalText Code
private static native int NativeFileOpen(String filename, int size);// 成功返回0,失敗-1
private static native int NativeFileClose();// 返回是否關閉成功
private static native int NativeFileRead(byte[] buf, int size);// 返回讀取數據的個數
private static native int NativeFileWrite(byte[] buf, int size);// 返回寫入的數據長度
串口初始化函數,需要傳遞想要打開的串口的文件名和波特率。
NormalText Code
public Boolean uartInit(String filename, int size) {
if ((fd = NativeFileOpen(filename, size)) != -1) {
uartInit = true;
return true;
} else {
log.E("%%%%% _uart_init() == -1 !!!! %%%%%");
return false;
}
}
接下來要封裝讀函數和寫函數,利用的是底層的read和write。
NormalText Code
public String uartRead(int num) {
byte[] data = new byte[num];
int re = 0;
if ((re = NativeFileRead(data, data.length)) > 0) {
return new String(data, 0, re);
} else {
log.E("%%%%% _uart_read() != num !!!! %%%%%");
return null;
}
}
NormalText Code
public Boolean uartWrite(byte[] data) {
if (NativeFileWrite(data, data.length) > 0) {
return true;
} else {
log.E("%%%%% _uart_write(data) == -1 !!!! %%%%%");
return false;
}
}
2.2 Uarthelper調試助手
本項目中,線程和UI線程通信是通過Handler 機制實現的。作用是將數據傳輸到UI線程進行顯示。
NormalText Code
private Handler handler = new Handler() {
@SuppressLint("SimpleDateFormat")
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
if (!check.isChecked()) {
treceive.append(recdate + " ");
} else {
treceive.append("0x" + printHex(recdate.getBytes()) + " ");
}
scroll.scrollTo(0,
treceive.getMeasuredHeight() - scroll.getHeight());
recdate = "";
break;
default:
break;
}
}
};
設定按鈕的監聽時間,當點擊Open按鈕的時候,按照設定的參數去打開對應的串口,成功后啟動讀和寫的線程,同時設定按鈕文字為Close,屏幕進行提示打開成功。
NormalText Code
save.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (open == false) {
if (!fd.getText().toString().equalsIgnoreCase("")) {
if (uart.uartInit("/dev/" + fd.getText().toString(),
rate_t) == true) {
Toast.makeText(Uarthelper.this, "打開成功".toString(),
Toast.LENGTH_LONG).show();
log.E("打開文件 " + "/dev/" + fd.getText().toString());
open = true;
threadon = true;
readThread = new ReadThread();
readThread.start();
save.setText("Close".toString());
} else {
Toast.makeText(Uarthelper.this,
"文件打開失敗".toString(), Toast.LENGTH_SHORT)
.show();
}
} else {
Toast.makeText(Uarthelper.this, "請填寫完整".toString(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Uarthelper.this, "正在關閉串口。。。".toString(),
Toast.LENGTH_LONG).show();
threadon = false;
uart.uartWrite("s".getBytes());
UartQThread uartQThread = new UartQThread();
uartQThread.start();
open = false;
save.setText("Open".toString());
}
}
});
讀取數據的線程,調用底層的uartRead函數,接收到數據后,將數據存入全局變量,然后通知主線程來顯示。
NormalText Code
class ReadThread extends Thread {
String tmp = null;
@Override
public void run() {
log.E("讀取數據線程啟動!");
while (threadon) {
tmp = uart.uartRead(10);
if (threadon) {
if (tmp == null) {
// log.E("接收數據出錯!");
} else {
log.E("接收數據 " + tmp);
recdate += tmp;
NoteThread noteThread = new NoteThread();
noteThread.start();
}
}
}
log.E("讀取數據線程結束!");
}
}
如果想要發送數據,需要調用底層的uartWrite函數。
NormalText Code
send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (uart.uartInit == true) {
String date = tsent.getText().toString();
if (uart.uartWrite(date.getBytes()) == true) {
// tsent.setText("");
log.E("發送數據 " + date);
} else {
Toast.makeText(Uarthelper.this, "發送失敗".toString(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Uarthelper.this, "請先設置串口".toString(),
Toast.LENGTH_SHORT).show();
}
}
});
2.3 NDK程序詳解
2.3.1 jni.c
這個文件的作用主要是連結上層和底層之間的函數。
首先需要定義一個結構體,這個結構體表明了上層函數名稱和底層函數名稱的對應關系。并且函數名稱的書寫是有要求的,Java_cn_com_farsight_tool_UartTool_NativeFileRead類似這樣的,格式為java+程序包名+函數名稱。
NormalText Code
static JNINativeMethod gMethods[] = { { "NativeFileOpen",
"(Ljava/lang/String;I)I",
(void *) Java_cn_com_farsight_tool_UartTool_NativeFileOpen }, {
"NativeFileRead", "([BI)I",
(void *) Java_cn_com_farsight_tool_UartTool_NativeFileRead }, {
"NativeFileWrite", "([BI)I",
(void *) Java_cn_com_farsight_tool_UartTool_NativeFileWrite }, {
"NativeFileClose", "()I",
(void *) Java_cn_com_farsight_tool_UartTool_NativeFileClose }, };
int register_cn_com_farsight_tool_UartTool(JNIEnv *env) {
return jniRegisterNativeMethods(env, kClassPathName, gMethods,
sizeof(gMethods) / sizeof(gMethods[0]));
然后在這些函數中調用底層的函數來實現對應的功能。
NormalText Code
jint Java_cn_com_farsight_tool_UartTool_NativeFileRead(JNIEnv* env,
jobject thiz, jbyteArray buf, jint size) {
unsigned char *buf_char = (char*) ((*env)->GetByteArrayElements(env, buf,
NULL));
int result = uart_read(buf_char, size);
(*env)->ReleaseByteArrayElements(env, buf, buf_char, 0);
return result;
}
2.3.2 onLoad.cpp
這個文件的主要作用是完成加載底層的庫文件的時候的工作。OnLoad函數在加載庫文件的時候會第一個執行。
NormalText Code
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = JNI_ERR;
sVm = vm;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
if (register_cn_com_farsight_tool_UartTool(env) != JNI_OK) {
LOGE("can't load register_cn_com_farsight_tool_UartTool()");
goto end;
}
LOGE("loaded succeed");
result = JNI_VERSION_1_4;
end: return result;
}
jniRegisterNativeMethods函數的作用是將剛才的關系結構體注冊進系統當中,同時,尋找上層需要調用這些底層函數的類。
NormalText Code
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
LOGE("Registering %s natives\n", className);
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'\n", className);
return -1;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'\n", className);
return -1;
}
return 0;
}
2.3.3 uart.c
這個文件就是底層函數的具體實現了。uart_get函數的功能就是打開串口,根據上層傳遞過來的參數和波特率來打開設備文件。
NormalText Code
int uart_get(char *filename, int rate) { //成功返回0,失敗-1
struct termios opt;
// uart
if ((fd = open(filename, O_RDWR | O_NOCTTY, 0777)) == -1) {
LOGE("UART open error!!! :%s ", strerror(errno));
return -1;
}
//初始化串口
tcgetattr(fd, &opt);
LOGE("rate is %d", rate);
switch (rate) {
case 4800:
cfsetispeed(&opt, B4800);
cfsetospeed(&opt, B4800);
break;
case 9600:
cfsetispeed(&opt, B9600);
cfsetospeed(&opt, B9600);
break;
case 19200:
cfsetispeed(&opt, B19200);
cfsetospeed(&opt, B19200);
break;
case 38400:
cfsetispeed(&opt, B38400);
cfsetospeed(&opt, B38400);
break;
case 115200:
LOGE("rate is %d", rate);
cfsetispeed(&opt, B115200);
cfsetospeed(&opt, B115200);
break;
default:
cfsetispeed(&opt, B115200);
cfsetospeed(&opt, B115200);
break;
}
opt.c_cflag |= (CLOCAL | CREAD);
opt.c_cflag &= ~CSIZE;
opt.c_cflag &= ~CRTSCTS;
opt.c_cflag |= CS8;
opt.c_iflag |= IGNPAR;
opt.c_cflag &= ~CSTOPB;
opt.c_oflag = 0;
opt.c_lflag = 0;
tcsetattr(fd, TCSANOW, &opt);
LOGE("UART open %s succeed!!!", filename);
return 0;
}
當要讀取數據的時候,先將上層傳遞下來的緩沖清空,使用read函數進行數據的讀取,然后返回。
NormalText Code
int uart_read(unsigned char *buf, int size) { //返回讀取數據的個數
int len = 0;
memset(buf, 0, size);
int result = read(fd, buf, size);
if (result <= 0) {
LOGE("uart_read(%d) is error %s", size, strerror(errno));
return -1;
}
// while (result < size) {
// len = read(fd, buf + result, size - result);
// result += len;
// }
LOGE("uart_read is %s", buf);
return result;
}
直接調用write來向設備寫入數據。
NormalText Code
int uart_write(const unsigned char *buf, int size) { //返回寫入的數據長度
int result = write(fd, buf, size);
if (result > 0) {
LOGE("uart write is %s", buf);
}
return result;
}
后程序結束的時候,關閉設備文件。
NormalText Code
int uart_close() {
close(fd);
LOGE("UART close succeed!!!");
return 0;
}

