前段时间因为工作需要研究了一下android的串口通信,网上有很多讲串口通信的文章,我在做的时候也参考了很多文章,现在就将我学习过程中的一些心得分享给大家,希望可以帮助大家在学习的时候少走一些弯路,有的地方可能我的理解有偏差,也请大家见谅。

网上讲的很多案例都是基于手机端的串口通信Demo,但是在实际开发过程中,串口通信主要用于开发板应用的开发,即不是我们常见的手机android应用,而基于开发板的开发和手机应用开发还是有区别的,我在这里就不赘述二者的区别了,因为我们今天讲的重点是串口通信的实现。

那么,android的串口同信到底是如何实现的呢?在实现过程中又有哪些地方是需要我们特别注意的呢?

首先,我们可以参看google官方的源码,但是google源码的下载链接现在已经打不开了,我们可以从github下载到我们的源码:https://github.com/cepr/android-serialport-api,这个源码里面包含了两个工程,我们应该选择这个android-serialport-api这个工程打开,打开之后,我们可以看到很多类文件,不要看着里面有很多类,其实只有几个类是最关键的:

SerialPort.java是这个项目当中最核心的类,用于通过调用底层C函数,实现串口的打开和关闭。SerialPort.java如下(我将原来的英文注释改为了中文注释):

/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package android_serialport_api; import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import android.util.Log; public class SerialPort { private static final String TAG = "SerialPort"; /*
* Do not remove or rename the field mFd: it is used by native method close();
*/
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { // 检查是否获取了指定串口的读写权限
if (!device.canRead() || !device.canWrite()) {
try {
// 如果没有获取指定串口的读写权限,则通过挂在到linux的方式修改串口的权限为可读写
Process su;
su = Runtime.getRuntime().exec("/system/bin/su");
String cmd = "chmod 777 " + device.getAbsolutePath() + "\n"
+ "exit\n";
su.getOutputStream().write(cmd.getBytes());
if ((su.waitFor() != 0) || !device.canRead()
|| !device.canWrite()) {
throw new SecurityException();
}
} catch (Exception e) {
e.printStackTrace();
throw new SecurityException();
}
} mFd = open(device.getAbsolutePath(), baudrate, flags);
if (mFd == null) {
Log.e(TAG, "native open returns null");
throw new IOException();
}
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
} // Getters and setters
public InputStream getInputStream() {
return mFileInputStream;
} public OutputStream getOutputStream() {
return mFileOutputStream;
} // JNI:调用java本地接口,实现串口的打开和关闭
/**
* 串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位
* 其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1
* @param path 串口设备的据对路径
* @param baudrate 波特率
* @param flags 这个参数我暂时认为是校验位, 对于本项目这个参数的意义不大。
* @return
*/
private native static FileDescriptor open(String path, int baudrate, int flags);//打开串口
public native void close();//关闭串口
static {
System.loadLibrary("serial_port");// 载入底层C文件
}
}

那么上面的两个函数open()和close()在C中是怎样实现的呢?我们可以在本工程目录下面看到的jni文件夹有一个SerialPort.c的文件,这里面实现了这两个方法:

/*
* Copyright 2009-2011 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ #include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h> #include "SerialPort.h" #include "android/log.h"
static const char *TAG="serial_port";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) static speed_t getBaudrate(jint baudrate)
{
switch(baudrate) {
case : return B0;
case : return B50;
case : return B75;
case : return B110;
case : return B134;
case : return B150;
case : return B200;
case : return B300;
case : return B600;
case : return B1200;
case : return B1800;
case : return B2400;
case : return B4800;
case : return B9600;
case : return B19200;
case : return B38400;
case : return B57600;
case : return B115200;
case : return B230400;
case : return B460800;
case : return B500000;
case : return B576000;
case : return B921600;
case : return B1000000;
case : return B1152000;
case : return B1500000;
case : return B2000000;
case : return B2500000;
case : return B3000000;
case : return B3500000;
case : return B4000000;
default: return -;
}
} /*
* Class: android_serialport_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
{
int fd;
speed_t speed;
jobject mFileDescriptor; /* Check arguments */
{
speed = getBaudrate(baudrate);
if (speed == -) {
/* TODO: throw an exception */
LOGE("Invalid baudrate");
return NULL;
}
} /* Opening device */
{
jboolean iscopy;
const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
fd = open(path_utf, O_RDWR | flags);
LOGD("open() fd = %d", fd);
(*env)->ReleaseStringUTFChars(env, path, path_utf);
if (fd == -)
{
/* Throw an exception */
LOGE("Cannot open port");
/* TODO: throw an exception */
return NULL;
}
} /* Configure device */
{
struct termios cfg;
LOGD("Configuring serial port");
if (tcgetattr(fd, &cfg))
{
LOGE("tcgetattr() failed");
close(fd);
/* TODO: throw an exception */
return NULL;
} cfmakeraw(&cfg);
cfsetispeed(&cfg, speed);
cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg))
{
LOGE("tcsetattr() failed");
close(fd);
/* TODO: throw an exception */
return NULL;
}
} /* Create a corresponding file descriptor */
{
jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
} return mFileDescriptor;
} /*
* Class: cedric_serial_SerialPort
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
(JNIEnv *env, jobject thiz)
{
jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); LOGD("close(fd = %d)", descriptor);
close(descriptor);
}

我们在用到自己的项目的时候,这个文件直接copy到我们的项目的jni文件(自己创建)目录下面就可以了,但需要注意的是,我们在建立自己项目的时候,为了避免不必要的错误,建议也按照本工程的结构来创建,即:建立一个名为android_serialport_api的包,然后把两个关键的类SerialPort.java和SerialPortFinder.java直接copy到这个包下面,这样做是为了保持和本地C函数中的方法名同步,如果不这样不这样做也可以,但是你需要根据你自己建立的包的名字来更改SerialPort.c中的函数名,我建议这样做也是为了避免在对JNI不是很了解的情况下陷入不必要的困扰。

细心的你们可能会发现,在android_serialport_api中还有一个类SerialPortFinder.java,那么这个类使用来干嘛的呢?其实这个类我们可用可不用,因为它是用来帮助我们找到设备中所有可用的串口的,即:如果你事先不清楚设备当中有哪些串口,那么你可以通过这个类来查询出设备中所有可用的串口,但是如果已经知道了串口的名字,那么这个类也可以不用,我们只需通过直接指定串口名的方式:SerialPort sp = new SerialPort(new File(path), baudrate, 0)打开串口,其中path就是串口的路径,baudrate为波特率,最后一个参数写0就可以了,如:SerialPort sp = new SerialPort(new File("/dev/ttyS0"), 9600, 0),当设备连接到电脑的时候,我们在DDMS中可以看到,所有的串口均在dev目录下面。如果我们要照出所有的串口,那么我们在建立自己的项目的时候,需要参考的类还有SerialPortPreferences.java,这个类继承了PreferenceActivity,其作用是用来展示出查询出来的所有串口,我们要用到自己的项目里面的时候直接copy即可。

这个项目里面还有一个类Application.java,这个类使用来干嘛的呢,它其实就是通过继承android.app.Application自定义了一个用来进行全局方法调用的Application,使用它我们可以很方便的在任何一个组件当中调用它里面所报看的变量和方法,这是我对其简单的理解,如果你要把这个类用到自己的项目当中来,那么需要注意的是,你需要改动清单文件中的配置,在清单文件的application节点中加入一行代码android:name="Application"即可,Application是我们自己写的Application的名字。这个类我们同样可以不用。

其实,整个项目可以进行极大的简化,我们可以只是用两个类便可实现串口通信,一个是SerialPort.java,另一个则是我们用来打开串口的Activity(在这个Activity中构造SerialPort实例便可),在此,我写了一个简化的Demo供大家参考,Demo下载地址:http://files.cnblogs.com/files/1992monkey/MySerialPort.rar

Android串口通信的更多相关文章

  1. Android串口通信(基于Tiny6410平台)

    友善之臂的Android系统有他们自己编写的一个串口通信程序,网上没有找到他的源代码,而且界面操作不在一个界面,不是很方便,这里我自己写了一个粗糙点的串口通信程序. 同样这里还是调用友善之臂的frie ...

  2. Android串口通信(Android Studio)

    gilhub上已有开源项目: https://github.com/cepr/android-serialport-api 可以直接使用

  3. Android串口开发

    参考资料: https://www.jianshu.com/p/9249ed03e745 GitHUb地址: https://github.com/AIlll/AndroidSerialPort An ...

  4. android 串口开发第二篇:利用jni实现android和串口通信

    一:串口通信简介 由于串口开发涉及到jni,所以开发环境需要支持ndk开发,如果未配置ndk配置的朋友,或者对jni不熟悉的朋友,请查看上一篇文章,android 串口开发第一篇:搭建ndk开发环境以 ...

  5. Android 串口蓝牙通信开发Java版本

    Android串口BLE蓝牙通信Java版 0. 导语 Qt on Android 蓝牙通信开发 我们都知道,在物联网中,BLE蓝牙是通信设备的关键设备.在传统的物联网应用中,无线WIFI.蓝牙和Zi ...

  6. Android Studio 的蓝牙串口通信(附Demo源码下载)

    根据相关代码制作了一个开源依赖包,将以下所有的代码进行打包,直接调用即可完成所有的操作.详细说明地址如下,如果觉得有用可以GIthub点个Star支持一下: 项目官网 Kotlin版本说明文档 Jav ...

  7. Android 蓝牙串口通信工具类 SerialPortUtil 3.0.+

    建议使用4.+版本,避免一些不必要的bug.4.+版本文档地址:https://www.cnblogs.com/shanya/articles/16062256.html SerialPortUtil ...

  8. BluetoothChat用于蓝牙串口通信的修改方法

    本人最近在研究嵌入式的串口通信,任务是要写一个手机端的遥控器用来遥控双轮平衡小车.界面只用了一个小时就写好了,重要的问题是如何与板子所带的SPP-CA蓝牙模块进行通信. SPP-CA模块自带代码,在这 ...

  9. Win10 IoT C#开发 4 - UART 串口通信

    Windows 10 IoT Core 是微软针对物联网市场的一个重要产品,既可以开发设备UI与用户交互式操作,又可以控制GPIO等接口,使得原来嵌入式繁琐的开发变得简单.通过Remote Debug ...

随机推荐

  1. Websocket 学习

    一.含义 WebSocket 是一种在单个TCP连接上进行全双工通讯的协议.   WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket ...

  2. 【网络爬虫】【java】微博爬虫(五):防止爬虫被墙的几个技巧(总结篇)

    爬虫的目的就是大规模地.长时间地获取数据,跟我们正常浏览器获取数据相比,虽然机理相差不大,但总是一个IP去爬网站,大规模集中对服务器访问,时间一长就有可能被拒绝.关于爬虫长时间爬取数据,可能会要求验证 ...

  3. 极客时间_Vue开发实战_06.Vue组件的核心概念(2):事件

    06.Vue组件的核心概念(2):事件 通过emit传递给父组件 我们点击了重置失败,上层的div的click=handleDivClick是接收不到.重置失败的点击的行为的 通常情况下,你不用.st ...

  4. 洛谷 - P1020 - 导弹拦截 - 最长上升子序列

    https://www.luogu.org/problemnew/show/P1020 终于搞明白了.根据某定理,最少需要的防御系统的数量就是最长上升子序列的数量. 呵呵手写二分果然功能很多,想清楚自 ...

  5. ZOJ3164【区间dp】

     题意: 有n个人,有一种关系叫做8g关系,给出m个关系,给出n个人的阵列 问你最多能拿走多少人,拿走以后相邻就是相邻了 思路: 典型的区间dp: dp[i][j] 代表 i-j 最多能去多少人: 如 ...

  6. POJ 3067【树状数组】

    题意: 给你两行数字,n个m个,然后给你k条线直接把两个数连起来,问有多少个交叉的 思路: 假定上一行是起点,下一行是终点. 把路按照起点从大到下排序, 然后可以直接对每条路查询,这条路目前的交叉数, ...

  7. 七大查找算法(Python)

    查找算法 -- 简介 查找(Searching)就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素.    查找表(Search Table):由同一类型的数据元素构成的集合    ...

  8. php 发送邮件(实例)

    html部分 <!DOCTYPE html> <html> <head> <title></title> <script type=& ...

  9. Kera高层API

    目录 Keras != tf.keras Outline1 Metrics Step1.Build a meter Step2.Update data Step3.Get Average data C ...

  10. JPA-day02 项目结构 编写增删改查测试类