當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > Android串口調(diào)試助手實現(xiàn)
串行接口 (Serial Interface) 是指數(shù)據(jù)一位一位地順序傳送,其特點是通信線路簡單,只要一對傳輸線就可以實現(xiàn)雙向通信(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用于遠距離通信,但傳送速度較慢。一條信息的各位數(shù)據(jù)被逐位按順序傳送的通訊方式稱為串行通訊。串行通訊的特點是:數(shù)據(jù)位的傳送,按位順序進行,少只需一根傳輸線即可完成;成本低但傳送速度慢。串行通訊的距離可以從幾米到幾千米;根據(jù)信息的傳送方向,串行通訊可以進一步分為單工、半雙工和全雙工三種。
日常中的很多設(shè)備都是通過串口來傳輸數(shù)據(jù)的。所以,本項目在安卓的平臺上,建立了一個通過串口來收發(fā)數(shù)據(jù)的平臺。用戶可以通過設(shè)定不同的參數(shù)來連接不同的串口。
第 1 章 使用說明
軟件共分為三個部分:數(shù)據(jù)接收區(qū),數(shù)據(jù)發(fā)送區(qū),參數(shù)設(shè)置區(qū)。
使用之前需要設(shè)置參數(shù):需要打開的設(shè)備文件和打開的波特率。
點擊Open就可以打開串口,如果這時候串口有數(shù)據(jù)過來,就可以在左側(cè)顯示出來,同時可以設(shè)定是否以十六進制顯示數(shù)據(jù)。
如果想要向串口發(fā)送數(shù)據(jù),在下方輸入數(shù)據(jù),點擊Send就可以發(fā)送。
第 2 章 環(huán)境搭建
2.1 Android 開發(fā)環(huán)境的安裝與配置
Android應(yīng)用軟件開發(fā)需要的開發(fā)環(huán)境在路徑“光盤\Android應(yīng)用開發(fā)環(huán)境\”下:
JDK: JDK\JDK8\jdk-8u5-windows-i586.exe(32bit)或者jdk-8u5-windows-x64.exe(64bit)
(從JDK 8.0開始不支持Windows XP操作系統(tǒng),使用Windows XP的用戶可以使用JDK7目錄下的內(nèi)容)
ADT: adt-bundle-windows-x86.7z(32bit)或者adt-bundle-windows-x86_64.7z(64bit)
以下主要介紹在Windows環(huán)境下搭建Android開發(fā)環(huán)境的步驟和注意事項。
2.2 安裝JDK和配置Java開發(fā)環(huán)境
雙擊JDK\JDK8\jdk-8u5-windows-i586.exe(32bit操作系統(tǒng))或者jdk-8u5-windows-x64.exe(64bit操作系統(tǒng))進行安裝(從JDK 8.0開始不支持Windows XP操作系統(tǒng),使用Windows XP的用戶可以使用JDK7目錄下的內(nèi)容選擇代替JDK8目錄下的內(nèi)容)。接受許可證,選擇需要安裝的組件和安裝路徑后,單擊“下一步”按鈕,完成安裝過程。
安裝完成后,利用以下步驟檢查安裝是否成功:打開Windows CMD窗口,在CMD窗口中輸入java –version命令,如果屏幕出現(xiàn)下圖所示的代碼信息,說明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
解壓完畢后,直接執(zhí)行其中的eclipse\eclipse.exe文件,Eclipse可以自動找到用戶前期安裝的JDK路徑。
2.5 配置Eclipse
運行解壓目錄下的eclipse\eclipse.exe,為自己選擇一個工作目錄Workspace,不要有中文路徑,不選擇默認也可以。
需要為Eclipse關(guān)聯(lián)SDK的安裝路徑,即解壓路徑下的sdk目錄。在Eclipse中,點擊Window->Preferences,會看到其中添加了Android的配置,按圖所示的操作,然后點擊Apply,后點擊OK即可。
完成以上步驟后,設(shè)置Eclipse環(huán)境
勾選Android相關(guān)的工具,點擊OK(如果已經(jīng)勾選,則不理會)。
第 3 章 NDK環(huán)境配置
3.1 安裝NDK工具包
安裝包已經(jīng)放到“華清遠見開發(fā)環(huán)境”光盤當(dāng)中,名字為“android-ndk-r10d-windows-x86”,這個是針對32位系統(tǒng)使用的工具包,如果有64位的需求可以到我們提供的網(wǎng)盤上進行下載。
將安裝包拷貝到E:盤,雙擊程序即可在當(dāng)前路徑進行安裝。
3.2 配置Eclipse
打開Eclipse,點Window->Preferences->Android->NDK,設(shè)置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...添加環(huán)境變量NDKROOT,值為NDK的根目錄
接著,按照如下圖所示的位置,根據(jù)使用的SDK的版本的不同選擇不同的頭文件包,例如如果使用的是android4.0.3 的話,就選擇:E:\ android-ndk-r10d\platforms\android-15\arch-arm\usr\include
之后,再次編譯運行工程,即可成功。
第 1 章 源碼編譯
1.1 導(dǎo)入源碼
打開Eclipse環(huán)境,選擇File->Import。
然后,導(dǎo)入光盤資料中的“BlueHelper”工程,勾選下圖中的選項。
點擊finish完成工程的導(dǎo)入
1.1 運行程序
注意:如果在調(diào)試開發(fā)板的時候,出現(xiàn)ADB連接不上的問題(已知華清遠見FSPAD723開源平板),可以試著替換Android SDK的ADB工具(把光盤\Android應(yīng)用開發(fā)環(huán)境\ADB\ADB1.0.26\下的4個文件拷貝到用戶ADT解壓目錄下的sdk\platform-tools中)
開發(fā)期間,在實際的設(shè)備上運行Android程序與在模擬器上運行該程序的效果幾乎相同,需要做的就是用USB電纜連接手機與計算機,并安裝一個對應(yīng)的設(shè)備驅(qū)動程序。如果模擬器窗口已打開,請將其關(guān)閉。只要將開發(fā)平臺通過USB下載線與計算機相連,應(yīng)用程序就會在開發(fā)平臺上加載并運行。
在Eclipse中選擇“Run” →“Run”(或Debug)命令(或者在工程上點擊右鍵),這時會彈出一個窗口,讓你選擇用模擬器還是手機來顯示,如果選擇手機,即可在手機上運行該程序。
第 2 章 詳細設(shè)計
2.1 UartTool串口工具
因為本項目要連接上層java和底層c語言,所以需要先聲明本地方法。
NormalText Code
private static native int NativeFileOpen(String filename, int size);// 成功返回0,失敗-1
private static native int NativeFileClose();// 返回是否關(guān)閉成功
private static native int NativeFileRead(byte[] buf, int size);// 返回讀取數(shù)據(jù)的個數(shù)
private static native int NativeFileWrite(byte[] buf, int size);// 返回寫入的數(shù)據(jù)長度
串口初始化函數(shù),需要傳遞想要打開的串口的文件名和波特率。
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;
}
}
接下來要封裝讀函數(shù)和寫函數(shù),利用的是底層的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調(diào)試助手
本項目中,線程和UI線程通信是通過Handler 機制實現(xiàn)的。作用是將數(shù)據(jù)傳輸?shù)経I線程進行顯示。
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;
}
}
};
設(shè)定按鈕的監(jiān)聽時間,當(dāng)點擊Open按鈕的時候,按照設(shè)定的參數(shù)去打開對應(yīng)的串口,成功后啟動讀和寫的線程,同時設(shè)定按鈕文字為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, "請?zhí)顚懲暾?quot;.toString(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Uarthelper.this, "正在關(guān)閉串口。。。".toString(),
Toast.LENGTH_LONG).show();
threadon = false;
uart.uartWrite("s".getBytes());
UartQThread uartQThread = new UartQThread();
uartQThread.start();
open = false;
save.setText("Open".toString());
}
}
});
讀取數(shù)據(jù)的線程,調(diào)用底層的uartRead函數(shù),接收到數(shù)據(jù)后,將數(shù)據(jù)存入全局變量,然后通知主線程來顯示。
NormalText Code
class ReadThread extends Thread {
String tmp = null;
@Override
public void run() {
log.E("讀取數(shù)據(jù)線程啟動!");
while (threadon) {
tmp = uart.uartRead(10);
if (threadon) {
if (tmp == null) {
// log.E("接收數(shù)據(jù)出錯!");
} else {
log.E("接收數(shù)據(jù) " + tmp);
recdate += tmp;
NoteThread noteThread = new NoteThread();
noteThread.start();
}
}
}
log.E("讀取數(shù)據(jù)線程結(jié)束!");
}
}
如果想要發(fā)送數(shù)據(jù),需要調(diào)用底層的uartWrite函數(shù)。
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("發(fā)送數(shù)據(jù) " + date);
} else {
Toast.makeText(Uarthelper.this, "發(fā)送失敗".toString(),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(Uarthelper.this, "請先設(shè)置串口".toString(),
Toast.LENGTH_SHORT).show();
}
}
});
2.3 NDK程序詳解
2.3.1 jni.c
這個文件的作用主要是連結(jié)上層和底層之間的函數(shù)。
首先需要定義一個結(jié)構(gòu)體,這個結(jié)構(gòu)體表明了上層函數(shù)名稱和底層函數(shù)名稱的對應(yīng)關(guān)系。并且函數(shù)名稱的書寫是有要求的,Java_cn_com_farsight_tool_UartTool_NativeFileRead類似這樣的,格式為java+程序包名+函數(shù)名稱。
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]));
然后在這些函數(shù)中調(diào)用底層的函數(shù)來實現(xiàn)對應(yīng)的功能。
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函數(shù)在加載庫文件的時候會第一個執(zhí)行。
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函數(shù)的作用是將剛才的關(guān)系結(jié)構(gòu)體注冊進系統(tǒng)當(dāng)中,同時,尋找上層需要調(diào)用這些底層函數(shù)的類。
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
這個文件就是底層函數(shù)的具體實現(xiàn)了。uart_get函數(shù)的功能就是打開串口,根據(jù)上層傳遞過來的參數(shù)和波特率來打開設(shè)備文件。
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;
}
當(dāng)要讀取數(shù)據(jù)的時候,先將上層傳遞下來的緩沖清空,使用read函數(shù)進行數(shù)據(jù)的讀取,然后返回。
NormalText Code
int uart_read(unsigned char *buf, int size) { //返回讀取數(shù)據(jù)的個數(shù)
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;
}
直接調(diào)用write來向設(shè)備寫入數(shù)據(jù)。
NormalText Code
int uart_write(const unsigned char *buf, int size) { //返回寫入的數(shù)據(jù)長度
int result = write(fd, buf, size);
if (result > 0) {
LOGE("uart write is %s", buf);
}
return result;
}
后程序結(jié)束的時候,關(guān)閉設(shè)備文件。
NormalText Code
int uart_close() {
close(fd);
LOGE("UART close succeed!!!");
return 0;
}