热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

提到串口编程,就不得不提到JNI,不得不提到JavaAPI中的文件描述符类:FileDescriptor;下面我分别对JNI、FileDescriptor以及串口的一些知识点和实现的源码进行分析说明,感兴趣的朋友可以了解下
提到串口编程,就不得不提到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的相关项目写法)
这是关键的代码:
代码如下:

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;
}
}


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

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 mDrivers = null;

public class Driver {
public Driver(String name, String root) {
mDriverName = name;
mDeviceRoot = root;
}

private String mDriverName;
private String mDeviceRoot;
Vector mDevices = null;

public Vector getDevices() {
if (mDevices == null) {
mDevices = new Vector();
File dev = new File("/dev");
File[] files = dev.listFiles();
int i;
for (i = 0; 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 getDrivers() throws IOException {
if (mDrivers == null) {
mDrivers = new Vector();
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 devices = new Vector();
// Parse each driver
Iterator itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator 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 devices = new Vector();
// Parse each driver
Iterator itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator 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()]);
}
}


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

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");
}
}


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

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;
}
}
}


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

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();
}
}


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

Please configure your serial port first.
You do not have read/write permission to the serial port.
The serial port can not be opened for an unknown reason.


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





50
75
110
134
150
200
300
600
1200
1800
2400
4800
9600
19200
38400
57600
115200
230400
460800
500000
576000
921600
1000000
1152000
1500000
2000000
2500000
3000000
3500000
4000000


50
75
110
134
150
200
300
600
1200
1800
2400
4800
9600
19200
38400
57600
115200
230400
460800
500000
576000
921600
1000000
1152000
1500000
2000000
2500000
3000000
3500000
4000000





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


android:layout_
android:layout_
android:orientation="vertical" >

android:id="@+id/EditTextReception"
android:layout_
android:layout_
android:layout_weight="1"
android:gravity="top"
android:hint="Reception"
android:isScrollCOntainer="true"
android:scrollbar >


android:id="@+id/EditTextEmission"
android:layout_
android:layout_
android:hint="Emission"
android:lines="1" >





8) SerialDemoActivity类的实现:
代码如下:

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; itext[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));
}
}
});
}
}


写到这里,代码基本上写完了。下面就是要实现JNI层的功能了,要实现JNI,必须首先生成头文件,头文件的生成方式也很简单, 我们编译工程,在终端输入 javah org.winplus.serial.utils.SerialPort 则会生成头文件:org_winplus_serial_utils_SerialPort.h,这个头文件的名字可以随意命名。我们将它命名为:SerialPort.h拷贝到新建的目录jni中,新建SerialPort.c 文件,这两个文件的代码就不贴出来了。直接到上传的代码中看吧。
(四)串口的应用,可实现扫描头,指纹识别等外围USB转串口的特色应用
还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!
推荐阅读
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
author-avatar
huyuping6215
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有