当前位置: 移动技术网 > 移动技术>移动开发>Android > Android开发之串口编程原理和实现方式

Android开发之串口编程原理和实现方式

2019年07月24日  | 移动技术网移动技术  | 我要评论
提到串口编程,就不得不提到jni,不得不提到javaapi中的文件描述符类:filedescriptor。下面我分别对jni、filedescriptor以及串口的一些知识点和实现的源码进行分析说明。这里主要是参考了开源项目android-serialport-api。

串口编程需要了解的基本知识点:对于串口编程,我们只需对串口进行一系列的设置,然后打开串口,这些操作我们可以参考串口调试助手的源码进行学习。在java中如果要实现串口的读写功能只需操作文件设备类:filedescriptor即可,其他的事都由驱动来完成不用多管!当然,你想了解,那就得看驱动代码了。这里并不打算对驱动进行说明,只初略阐述应用层的实现方式。

(一)jni
关于jni的文章网上有很多,不再多做解释,想详细了解的朋友可以查看云中漫步的技术文章,写得很好,分析也很全面,那么在这篇拙文中我强调3点:
1、如何将编译好的so文件打包到apk中?(方法很简单,直接在工程目录下新建文件夹 libs/armeabi,将so文件copy到此目录即可)
2、命名要注意的地方?(在编译好的so文件中,将文件重命名为:libfilename.so即可。其中filename.so是编译好后生成的文件)
3、makefile文件的编写(不用多说,可以直接参考package/apps目录下用到jni的相关项目写法)
这是关键的代码:
复制代码 代码如下:

<span style="font-size:18px;"> int fd;
speed_t speed;
jobject mfiledescriptor;

/* check arguments */
{
speed = getbaudrate(baudrate);
if (speed == -1) {
/* 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 == -1)
{
/* 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;
}
}
</span>

(二)filedescritor
文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的fileinputstream 或fileoutputstream。这是api的描述,不太好理解,其实可简单的理解为:filedescritor就是对一个文件进行读写。
(三)实现串口通信细节
1) 建工程:serialdemo包名:org.winplus.serial,并在工程目录下新建jni和libs两个文件夹和一个org.winplus.serial.utils,如下图:
2) 新建一个类:serialportfinder,添加如下代码:
复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial.utils;

import java.io.file;
import java.io.filereader;
import java.io.ioexception;
import java.io.linenumberreader;
import java.util.iterator;
import java.util.vector;

import android.util.log;

public class serialportfinder {

private static final string tag = "serialport";

private vector<driver> mdrivers = null;

public class driver {
public driver(string name, string root) {
mdrivername = name;
mdeviceroot = root;
}

private string mdrivername;
private string mdeviceroot;
vector<file> mdevices = null;

public vector<file> getdevices() {
if (mdevices == null) {
mdevices = new vector<file>();
file dev = new file("/dev");
file[] files = dev.listfiles();
int i;
for (i = 0; i < files.length; i++) {
if (files[i].getabsolutepath().startswith(mdeviceroot)) {
log.d(tag, "found new device: " + files[i]);
mdevices.add(files[i]);
}
}
}
return mdevices;
}

public string getname() {
return mdrivername;
}
}

vector<driver> getdrivers() throws ioexception {
if (mdrivers == null) {
mdrivers = new vector<driver>();
linenumberreader r = new linenumberreader(new filereader(
"/proc/tty/drivers"));
string l;
while ((l = r.readline()) != null) {
// issue 3:
// since driver name may contain spaces, we do not extract
// driver name with split()
string drivername = l.substring(0, 0x15).trim();
string[] w = l.split(" +");
if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
log.d(tag, "found new driver " + drivername + " on "
+ w[w.length - 4]);
mdrivers.add(new driver(drivername, w[w.length - 4]));
}
}
r.close();
}
return mdrivers;
}

public string[] getalldevices() {
vector<string> devices = new vector<string>();
// parse each driver
iterator<driver> itdriv;
try {
itdriv = getdrivers().iterator();
while (itdriv.hasnext()) {
driver driver = itdriv.next();
iterator<file> itdev = driver.getdevices().iterator();
while (itdev.hasnext()) {
string device = itdev.next().getname();
string value = string.format("%s (%s)", device,
driver.getname());
devices.add(value);
}
}
} catch (ioexception e) {
e.printstacktrace();
}
return devices.toarray(new string[devices.size()]);
}

public string[] getalldevicespath() {
vector<string> devices = new vector<string>();
// parse each driver
iterator<driver> itdriv;
try {
itdriv = getdrivers().iterator();
while (itdriv.hasnext()) {
driver driver = itdriv.next();
iterator<file> itdev = driver.getdevices().iterator();
while (itdev.hasnext()) {
string device = itdev.next().getabsolutepath();
devices.add(device);
}
}
} catch (ioexception e) {
e.printstacktrace();
}
return devices.toarray(new string[devices.size()]);
}
}
</span>

上面这个类在“android-serialport-api串口工具测试随笔”中有详细的说明,我就不多说了。
3)新建serialport类,这个类主要用来加载so文件,通过jni的方式打开关闭串口
复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial.utils;

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 {

/* check access permission */
if (!device.canread() || !device.canwrite()) {
try {
/* missing read/write permission, trying to chmod the file */
process su;
su = runtime.getruntime().exec("/system/bin/su");
string cmd = "chmod 666 " + 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
private native static filedescriptor open(string path, int baudrate,
int flags);

public native void close();

static {
system.loadlibrary("serial_port");
}
}
</span>

4) 新建一个myapplication 继承android.app.application,用来对串口进行初始化和关闭串口
复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial;

import java.io.file;
import java.io.ioexception;
import java.security.invalidparameterexception;

import org.winplus.serial.utils.serialport;
import org.winplus.serial.utils.serialportfinder;

import android.content.sharedpreferences;

public class myapplication extends android.app.application {
public serialportfinder mserialportfinder = new serialportfinder();
private serialport mserialport = null;

public serialport getserialport() throws securityexception, ioexception, invalidparameterexception {
if (mserialport == null) {
/* read serial port parameters */
sharedpreferences sp = getsharedpreferences("android_serialport_api.sample_preferences", mode_private);
string path = sp.getstring("device", "");
int baudrate = integer.decode(sp.getstring("baudrate", "-1"));

/* check parameters */
if ( (path.length() == 0) || (baudrate == -1)) {
throw new invalidparameterexception();
}

/* open the serial port */
mserialport = new serialport(new file(path), baudrate, 0);
}
return mserialport;
}

public void closeserialport() {
if (mserialport != null) {
mserialport.close();
mserialport = null;
}
}
}
</span>

5) 新建一个继承抽象的activity类,主要用于读取串口的信息
复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial;

import java.io.ioexception;
import java.io.inputstream;
import java.io.outputstream;
import java.security.invalidparameterexception;

import org.winplus.serial.utils.serialport;

import android.app.activity;
import android.app.alertdialog;
import android.content.dialoginterface;
import android.content.dialoginterface.onclicklistener;
import android.os.bundle;

public abstract class serialportactivity extends activity {
protected myapplication mapplication;
protected serialport mserialport;
protected outputstream moutputstream;
private inputstream minputstream;
private readthread mreadthread;

private class readthread extends thread {

@override
public void run() {
super.run();
while (!isinterrupted()) {
int size;
try {
byte[] buffer = new byte[64];
if (minputstream == null)
return;

/**
* 这里的read要尤其注意,它会一直等待数据,等到天荒地老,海枯石烂。如果要判断是否接受完成,只有设置结束标识,或作其他特殊的处理。
*/
size = minputstream.read(buffer);
if (size > 0) {
ondatareceived(buffer, size);
}
} catch (ioexception e) {
e.printstacktrace();
return;
}
}
}
}

private void displayerror(int resourceid) {
alertdialog.builder b = new alertdialog.builder(this);
b.settitle("error");
b.setmessage(resourceid);
b.setpositivebutton("ok", new onclicklistener() {
public void onclick(dialoginterface dialog, int which) {
serialportactivity.this.finish();
}
});
b.show();
}

@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
mapplication = (myapplication) getapplication();
try {
mserialport = mapplication.getserialport();
moutputstream = mserialport.getoutputstream();
minputstream = mserialport.getinputstream();

/* create a receiving thread */
mreadthread = new readthread();
mreadthread.start();
} catch (securityexception e) {
displayerror(r.string.error_security);
} catch (ioexception e) {
displayerror(r.string.error_unknown);
} catch (invalidparameterexception e) {
displayerror(r.string.error_configuration);
}
}

protected abstract void ondatareceived(final byte[] buffer, final int size);

@override
protected void ondestroy() {
if (mreadthread != null)
mreadthread.interrupt();
mapplication.closeserialport();
mserialport = null;
super.ondestroy();
}
}
</span>

6)编写string.xml 以及baudrates.xml文件
在string.xml文件中添加:
复制代码 代码如下:

<span style="font-size:18px;"> <string name="error_configuration">please configure your serial port first.</string>
<string name="error_security">you do not have read/write permission to the serial port.</string>
<string name="error_unknown">the serial port can not be opened for an unknown reason.</string>
</span>

在baudrates.xml文件中添加
复制代码 代码如下:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<resources>

<string-array name="baudrates_name">
<item>50</item>
<item>75</item>
<item>110</item>
<item>134</item>
<item>150</item>
<item>200</item>
<item>300</item>
<item>600</item>
<item>1200</item>
<item>1800</item>
<item>2400</item>
<item>4800</item>
<item>9600</item>
<item>19200</item>
<item>38400</item>
<item>57600</item>
<item>115200</item>
<item>230400</item>
<item>460800</item>
<item>500000</item>
<item>576000</item>
<item>921600</item>
<item>1000000</item>
<item>1152000</item>
<item>1500000</item>
<item>2000000</item>
<item>2500000</item>
<item>3000000</item>
<item>3500000</item>
<item>4000000</item>
</string-array>
<string-array name="baudrates_value">
<item>50</item>
<item>75</item>
<item>110</item>
<item>134</item>
<item>150</item>
<item>200</item>
<item>300</item>
<item>600</item>
<item>1200</item>
<item>1800</item>
<item>2400</item>
<item>4800</item>
<item>9600</item>
<item>19200</item>
<item>38400</item>
<item>57600</item>
<item>115200</item>
<item>230400</item>
<item>460800</item>
<item>500000</item>
<item>576000</item>
<item>921600</item>
<item>1000000</item>
<item>1152000</item>
<item>1500000</item>
<item>2000000</item>
<item>2500000</item>
<item>3000000</item>
<item>3500000</item>
<item>4000000</item>
</string-array>

</resources>
</span>

7)开始编写界面了:在main.xml布局文件中添加两个编辑框,一个用来发送命令,一个用来接收命令:
复制代码 代码如下:

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<edittext
android:id="@+id/edittextreception"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:gravity="top"
android:hint="reception"
android:isscrollcontainer="true"
android:scrollbarstyle="insideoverlay" >
</edittext>

<edittext
android:id="@+id/edittextemission"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="emission"
android:lines="1" >
</edittext>

</linearlayout>
</span>

8) serialdemoactivity类的实现:
复制代码 代码如下:

<span style="font-size:18px;">package org.winplus.serial;

import java.io.ioexception;

import android.os.bundle;
import android.view.keyevent;
import android.widget.edittext;
import android.widget.textview;
import android.widget.textview.oneditoractionlistener;

public class serialdemoactivity extends serialportactivity{
edittext mreception;

@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.main);

// settitle("loopback test");
mreception = (edittext) findviewbyid(r.id.edittextreception);

edittext emission = (edittext) findviewbyid(r.id.edittextemission);
emission.setoneditoractionlistener(new oneditoractionlistener() {
public boolean oneditoraction(textview v, int actionid, keyevent event) {
int i;
charsequence t = v.gettext();
char[] text = new char[t.length()];
for (i=0; i<t.length(); i++) {
text[i] = t.charat(i);
}
try {
moutputstream.write(new string(text).getbytes());
moutputstream.write('\n');
} catch (ioexception e) {
e.printstacktrace();
}
return false;
}
});
}

@override
protected void ondatareceived(final byte[] buffer, final int size) {
runonuithread(new runnable() {
public void run() {
if (mreception != null) {
mreception.append(new string(buffer, 0, size));
}
}
});
}
}
</span>

写到这里,代码基本上写完了。下面就是要实现jni层的功能了,要实现jni,必须首先生成头文件,头文件的生成方式也很简单, 我们编译工程,在终端输入 javah org.winplus.serial.utils.serialport 则会生成头文件:org_winplus_serial_utils_serialport.h,这个头文件的名字可以随意命名。我们将它命名为:serialport.h拷贝到新建的目录jni中,新建serialport.c 文件,这两个文件的代码就不贴出来了。直接到上传的代码中看吧。
(四)串口的应用,可实现扫描头,指纹识别等外围usb转串口的特色应用
还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网