前段时间因为工作需要研究了一下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. Flutter实战视频-移动电商-20.首页_火爆专区上拉加载效果

    20.首页_火爆专区上拉加载效果 上拉加载的插件比较都.没有一个一枝独秀的 可以自定义酷炫的header和footer 一直在更新 推荐使用这个插件: https://github.com/xuelo ...

  2. window.showModalDialog 在谷歌Uncaught TypeError: undefined is not a function

    if(navigator.userAgent.indexOf("Chrome") >0 ){var winOption = "height="+heigh ...

  3. SQL——登陆触发器实现限制IP

    [转载]原文地址:https://www.baidu.com/link?url=N-SM28ge21TTYky79dYk8otsjKgYCIpy-0RBSvMV25f8KSOsYczhxTOCzeNZ ...

  4. jquery冲突的关键字nodeName、nodeValue和nodeType!

    原文:http://blog.csdn.net/hdfyq/article/details/52805836 [缘由]在工作流数据库设计的时候,  都节点管理的功能.  结果有2个字段为  NODE_ ...

  5. Unity3D教程:无缝地形场景切换的解决方法

    http://www.unitymanual.com/6718.html 当我们开发一个大型项目的时候-会遇到这样的问题(地形场景的切换)这个只是字面意思-并不是重场景1的100  100 100坐标 ...

  6. WinMain和main

    WinMain的原型: int WINAPI WinMain(HINSTANCE hinstance,//程序本身的实例句柄                                  HINS ...

  7. 51nod1562(set&模拟)

    题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1562 题意:中文题诶- 思路:直接用set模拟 set< ...

  8. jzoj5983. 【北大2019冬令营模拟2019.1.1】多边形 (组合数学)

    这其实是道打表题--你看我代码就知道了-- 咳咳来点严谨证明好了-- 前方高能请注意 首先,正多边形近似于圆,可以看做在圆里内接多边形.圆内接多边形最多只有三个锐角.因为凸多边形的外角和为\(360\ ...

  9. [Swift]堆栈Stack:检索最小元素时间复杂度O(1)

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  10. JSP && Servlet | 上传图片到数据库

    参考博客: https://blog.csdn.net/qiyuexuelang/article/details/8861300 Servlet+Jsp实现图片或文件的上传功能 https://blo ...