串行接口 (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;
}