Android串口調試助手實現
時間:2018-08-16 來(lai)源(yuan):未知
串(chuan)行(xing)接口 (Serial Interface) 是(shi)指(zhi)數(shu)據(ju)(ju)一(yi)位(wei)(wei)一(yi)位(wei)(wei)地(di)順(shun)序(xu)(xu)傳(chuan)(chuan)送(song),其特(te)點是(shi)通(tong)信(xin)線(xian)路簡單(dan),只要一(yi)對(dui)傳(chuan)(chuan)輸(shu)線(xian)就可(ke)以實(shi)現雙(shuang)向通(tong)信(xin)(可(ke)以直(zhi)接利用(yong)電話線(xian)作為(wei)(wei)傳(chuan)(chuan)輸(shu)線(xian)),從而大(da)大(da)降低了成本,特(te)別適用(yong)于遠(yuan)距離(li)通(tong)信(xin),但(dan)傳(chuan)(chuan)送(song)速(su)度較慢。一(yi)條信(xin)息的各位(wei)(wei)數(shu)據(ju)(ju)被逐(zhu)位(wei)(wei)按順(shun)序(xu)(xu)傳(chuan)(chuan)送(song)的通(tong)訊方式稱為(wei)(wei)串(chuan)行(xing)通(tong)訊。串(chuan)行(xing)通(tong)訊的特(te)點是(shi):數(shu)據(ju)(ju)位(wei)(wei)的傳(chuan)(chuan)送(song),按位(wei)(wei)順(shun)序(xu)(xu)進(jin)(jin)行(xing),少只需一(yi)根傳(chuan)(chuan)輸(shu)線(xian)即可(ke)完成;成本低但(dan)傳(chuan)(chuan)送(song)速(su)度慢。串(chuan)行(xing)通(tong)訊的距離(li)可(ke)以從幾米(mi)到幾千米(mi);根據(ju)(ju)信(xin)息的傳(chuan)(chuan)送(song)方向,串(chuan)行(xing)通(tong)訊可(ke)以進(jin)(jin)一(yi)步分為(wei)(wei)單(dan)工(gong)(gong)、半雙(shuang)工(gong)(gong)和全雙(shuang)工(gong)(gong)三種。
日常中(zhong)的很(hen)多設(she)備都是通(tong)過(guo)串(chuan)(chuan)口來(lai)傳輸(shu)數據的。所(suo)以(yi),本項目(mu)在安卓的平臺(tai)上(shang),建(jian)立了(le)一(yi)個通(tong)過(guo)串(chuan)(chuan)口來(lai)收發(fa)數據的平臺(tai)。用戶可以(yi)通(tong)過(guo)設(she)定不同的參數來(lai)連接不同的串(chuan)(chuan)口。
第(di) 1 章 使用說(shuo)明

軟件共分為三個部分:數(shu)據接收區(qu),數(shu)據發(fa)送(song)區(qu),參數(shu)設置區(qu)。
使用之(zhi)前需要(yao)設置參數:需要(yao)打開的(de)設備文件和打開的(de)波(bo)特率(lv)。
點擊Open就可以打開串口,如(ru)果這(zhe)時(shi)候串口有數據(ju)過來,就可以在(zai)左側(ce)顯(xian)示出(chu)來,同時(shi)可以設(she)定是否(fou)以十六(liu)進制顯(xian)示數據(ju)。
如果想要向串口發送(song)數據,在(zai)下方(fang)輸入數據,點擊Send就可以發送(song)。
第(di) 2 章 環境搭(da)建
2.1 Android 開發環境的安(an)裝與配置(zhi)
Android應(ying)用軟件開(kai)發需要(yao)的開(kai)發環境(jing)在路徑“光盤(pan)\Android應(ying)用開(kai)發環境(jing)\”下:
JDK: JDK\JDK8\jdk-8u5-windows-i586.exe(32bit)或者jdk-8u5-windows-x64.exe(64bit)
(從JDK 8.0開始不支持(chi)Windows XP操(cao)作系(xi)統,使(shi)用Windows XP的(de)用戶可以(yi)使(shi)用JDK7目錄下(xia)的(de)內容)
ADT: adt-bundle-windows-x86.7z(32bit)或者(zhe)adt-bundle-windows-x86_64.7z(64bit)
以(yi)下主(zhu)要介紹在Windows環境下搭建Android開發(fa)環境的步驟和注(zhu)意(yi)事項。
2.2 安裝JDK和配置Java開發環境
雙擊JDK\JDK8\jdk-8u5-windows-i586.exe(32bit操作(zuo)系統)或者(zhe)jdk-8u5-windows-x64.exe(64bit操作(zuo)系統)進行(xing)安(an)(an)裝(zhuang)(從JDK 8.0開(kai)始不支(zhi)持Windows XP操作(zuo)系統,使(shi)用Windows XP的用戶可以使(shi)用JDK7目錄下(xia)的內(nei)(nei)容選(xuan)擇代替JDK8目錄下(xia)的內(nei)(nei)容)。接受許可證(zheng),選(xuan)擇需要(yao)安(an)(an)裝(zhuang)的組(zu)件和(he)安(an)(an)裝(zhuang)路徑后,單擊“下(xia)一步”按鈕,完成安(an)(an)裝(zhuang)過程(cheng)。

安(an)裝(zhuang)完(wan)成(cheng)(cheng)后,利用以下(xia)步驟檢查安(an)裝(zhuang)是否成(cheng)(cheng)功:打開Windows CMD窗口(kou),在CMD窗口(kou)中輸入java –version命令,如果屏(ping)幕(mu)出(chu)現(xian)下(xia)圖所(suo)示(shi)的(de)代碼信息,說明JDK安(an)裝(zhuang)成(cheng)(cheng)功。
XP下安裝JDK7如下:

非XP下安裝(zhuang)JDK8如下:

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

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

2.5 配置Eclipse
運行解(jie)壓目(mu)(mu)錄下的eclipse\eclipse.exe,為(wei)自己選擇(ze)一(yi)個(ge)工作目(mu)(mu)錄Workspace,不要有中文路徑(jing),不選擇(ze)默認(ren)也(ye)可以(yi)。


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

完成以上步驟后,設(she)置Eclipse環(huan)境

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


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

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

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

在C/C++ Build中點(dian)擊Environment,點(dian)Add...添加環(huan)境(jing)變量NDKROOT,值(zhi)為NDK的(de)根目錄

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

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

然(ran)后(hou),導入光盤資料中(zhong)的“BlueHelper”工程(cheng),勾選下(xia)圖中(zhong)的選項。

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

開(kai)(kai)發(fa)期間(jian),在實(shi)際的設(she)備上運行Android程(cheng)(cheng)序(xu)與在模擬器上運行該程(cheng)(cheng)序(xu)的效(xiao)果幾乎相同,需要(yao)做的就是用USB電(dian)纜連(lian)接手機與計算機,并安(an)裝一個(ge)對應的設(she)備驅動程(cheng)(cheng)序(xu)。如果模擬器窗口(kou)已打(da)開(kai)(kai),請將其關閉(bi)。只要(yao)將開(kai)(kai)發(fa)平臺(tai)通過USB下載(zai)線與計算機相連(lian),應用程(cheng)(cheng)序(xu)就會(hui)在開(kai)(kai)發(fa)平臺(tai)上加載(zai)并運行。
在(zai)(zai)Eclipse中選擇(ze)“Run” →“Run”(或(huo)Debug)命令(ling)(或(huo)者在(zai)(zai)工程上(shang)點擊右鍵),這時會彈出一(yi)個窗(chuang)口,讓你選擇(ze)用模(mo)擬(ni)器還(huan)是手機來(lai)顯示,如果選擇(ze)手機,即可在(zai)(zai)手機上(shang)運行該程序。


第 2 章 詳細設計
2.1 UartTool串口(kou)工具
因為本(ben)(ben)項目要連接上層java和底層c語言,所以(yi)需(xu)要先聲明(ming)本(ben)(ben)地方法。
NormalText Code
private static native int NativeFileOpen(String filename, int size);// 成功返(fan)回0,失敗-1
private static native int NativeFileClose();// 返回是(shi)否關閉(bi)成(cheng)功
private static native int NativeFileRead(byte[] buf, int size);// 返回讀取數(shu)據的(de)個數(shu)
private static native int NativeFileWrite(byte[] buf, int size);// 返回(hui)寫入(ru)的數據長度
串口(kou)初(chu)始化函(han)數,需要傳(chuan)遞想要打開的串口(kou)的文件名和波特(te)率(lv)。
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;
}
}
接下來要封裝(zhuang)讀函(han)數(shu)和(he)寫函(han)數(shu),利用的(de)是底層的(de)read和(he)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調(diao)試助手
本項目中,線(xian)程和(he)UI線(xian)程通(tong)信是通(tong)過Handler 機制實現的。作用是將數據傳(chuan)輸(shu)到UI線(xian)程進(jin)行顯示。
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;
}
}
};
設定(ding)按鈕(niu)(niu)的監(jian)聽(ting)時間,當點擊Open按鈕(niu)(niu)的時候(hou),按照設定(ding)的參數(shu)去打開對應的串口,成(cheng)功(gong)后啟動讀和寫的線程,同時設定(ding)按鈕(niu)(niu)文字為Close,屏幕進行提示打開成(cheng)功(gong)。
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, "打開成(cheng)功".toString(),
Toast.LENGTH_LONG).show();
log.E("打開文(wen)件 " + "/dev/" + fd.getText().toString());
open = true;
threadon = true;
readThread = new ReadThread();
readThread.start();
save.setText("Close".toString());
} else {
Toast.makeText(Uarthelper.this,
"文件打(da)開失敗(bai)".toString(), Toast.LENGTH_SHORT)
.show();
}
} else {
Toast.makeText(Uarthelper.this, "請填(tian)寫(xie)完(wan)整".toString(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Uarthelper.this, "正在關(guan)閉串口。。。".toString(),
Toast.LENGTH_LONG).show();
threadon = false;
uart.uartWrite("s".getBytes());
UartQThread uartQThread = new UartQThread();
uartQThread.start();
open = false;
save.setText("Open".toString());
}
}
});
讀取數(shu)據(ju)的(de)線(xian)程,調用底(di)層的(de)uartRead函數(shu),接收到數(shu)據(ju)后,將(jiang)數(shu)據(ju)存入全局變量,然后通(tong)知主線(xian)程來顯示。
NormalText Code
class ReadThread extends Thread {
String tmp = null;
@Override
public void run() {
log.E("讀取數(shu)據線(xian)程啟動!");
while (threadon) {
tmp = uart.uartRead(10);
if (threadon) {
if (tmp == null) {
// log.E("接收數據出錯(cuo)!");
} else {
log.E("接收(shou)數據 " + tmp);
recdate += tmp;
NoteThread noteThread = new NoteThread();
noteThread.start();
}
}
}
log.E("讀取數據線程結束!");
}
}
如果想要發送數(shu)(shu)據,需(xu)要調用底層(ceng)的uartWrite函數(shu)(shu)。
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("發送數據(ju) " + date);
} else {
Toast.makeText(Uarthelper.this, "發送失敗".toString(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Uarthelper.this, "請先設(she)置串口".toString(),
Toast.LENGTH_SHORT).show();
}
}
});
2.3 NDK程序詳解
2.3.1 jni.c
這個(ge)文件的作(zuo)用主要是連結上(shang)層和底層之間的函數(shu)。
首(shou)先需要(yao)定義一個(ge)結構體(ti),這(zhe)個(ge)結構體(ti)表明了上層函(han)數(shu)(shu)(shu)名(ming)(ming)(ming)稱和底層函(han)數(shu)(shu)(shu)名(ming)(ming)(ming)稱的對應關系。并且函(han)數(shu)(shu)(shu)名(ming)(ming)(ming)稱的書寫是(shi)有要(yao)求的,Java_cn_com_farsight_tool_UartTool_NativeFileRead類(lei)似這(zhe)樣的,格式(shi)為java+程序包(bao)名(ming)(ming)(ming)+函(han)數(shu)(shu)(shu)名(ming)(ming)(ming)稱。
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]));
然后在這些函(han)數(shu)(shu)中調用(yong)底層的函(han)數(shu)(shu)來實現對應(ying)的功能。
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
這個文(wen)件(jian)的(de)主要作用是完成加載底層的(de)庫文(wen)件(jian)的(de)時(shi)候的(de)工作。OnLoad函(han)數在(zai)加載庫文(wen)件(jian)的(de)時(shi)候會第一(yi)個執行。
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函數的(de)作用是(shi)將剛才的(de)關系結構(gou)體注冊進系統當中,同時,尋找(zhao)上層需要調用這些底(di)層函數的(de)類。
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
這個文(wen)(wen)件就(jiu)是底(di)層函數的(de)具體實現了。uart_get函數的(de)功能就(jiu)是打開串口(kou),根(gen)據上層傳遞過來(lai)的(de)參數和波特率(lv)來(lai)打開設備(bei)文(wen)(wen)件。
NormalText Code
int uart_get(char *filename, int rate) { //成功返回(hui)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;
}
當(dang)要(yao)讀(du)取數(shu)據的(de)(de)時候,先(xian)將上層傳遞下來的(de)(de)緩沖清空,使用read函數(shu)進行數(shu)據的(de)(de)讀(du)取,然后返(fan)回(hui)。
NormalText Code
int uart_read(unsigned char *buf, int size) { //返回(hui)讀取(qu)數據的個(ge)數
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;
}
直(zhi)接調用write來向設備(bei)寫入(ru)數據。
NormalText Code
int uart_write(const unsigned char *buf, int size) { //返回(hui)寫入的數據長度
int result = write(fd, buf, size);
if (result > 0) {
LOGE("uart write is %s", buf);
}
return result;
}
后程序(xu)結束的時候,關閉設備(bei)文(wen)件。
NormalText Code
int uart_close() {
close(fd);
LOGE("UART close succeed!!!");
return 0;
}