讲师博文
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;

}

扫码申领本地嵌入式教学实录全套视频及配套源码

上一篇:linux make命令安装详解

下一篇:fork函数的小误区

400-611-6270

Copyright © 2004-2024 华清远见教育科技集团 版权所有
京ICP备16055225号-5京公海网安备11010802025203号