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

提到串口编程,就不得不提到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 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!

(0)

相关推荐

  • nodejs简单实现操作arduino

    用Javascript来操作硬件早就不是一件稀奇的事情了. 所以作为一名电子专业出身的FE,我也打算尝试一下用js来驱动arduino: 要想操作这些底层硬件,肯定是需要一些工具的,我这里介绍的工具主要是 cylonjs 和 gort cylonjs其实就是一个操作"机器"的js框架,官网的介绍是这样的: Cylon.js is a JavaScript framework for robotics, physical computing, and the Internet of Th

  • 深入分析Visual C++进行串口通信编程的详解

    利用Visual C++在windows环境下设计异步串行通信程序可以使用不同的方法.一种方法可以使用windows系统提供的串行口API函数:另一种方法可以直接使用Microsoft公司提供的ActiveX控件MSCOMM.OCX.利用MSCOMM.OCX控件进行串行口程序设计相对比较简单,只要对该控件的属性.事件和方法进行设置和操作,就能完成简单的串行通信功能.而直接使用windows系统提供的串行口API函数则相对较为灵活.试验中,可根据自己的情况任意其中一种进行编程.以下针对如何使用wi

  • Android串口操作方法实例

    1.首先下载一个libserial_port.so,新建目录libs/armeabi,将so文件放到该目录下. 2.定义串口类,在类的构建函数中修改权限,打开设备,创建输入流和输出流,通过native接口访问串口打开关闭函数 复制代码 代码如下: public class SerialPort {  /*Do not remove or rename the field mFd: it is used by native method close();*/  public SerialPort(

  • C#串口通信程序实例详解

    创建C#串口通信程序之命名空间 System.IO.Ports命名空间中最重用的是SerialPort 类. 创建C#串口通信程序之创建SerialPort 对象 通过创建SerialPort 对象,我们可以在程序中控制串口通信的全过程. 我们将要用到的SerialPort 类的方法: ReadLine():从输入缓冲区读一新行的值,如果没有,会返回NULLWriteLine(string):写入输出缓冲Open():打开一个新的串口连接Close():关闭 复制代码 代码如下: SerialP

  • 服务器sas硬盘+sata串口硬盘win2003系统安装方法分享

    服务器配置如下: XEON5506(2.13) 内存:KST 4GB DDR3 ECC 硬盘:ST300G SAS-15000转*1+1T 西部数据企业级硬盘 主板:华硕Z8ANA-D6+6480*1 机箱:1U机架式 电源:1U 400W 组装好以后,安装系统后发现在win2003安装界面硬盘分区都正常,但到了选择sas硬盘的c盘时,总提示1T硬盘没有可识别的分区什么的,因为跟sata接口的硬盘没有关系,为什么总提示这个,所以将sata硬盘线拔下,直接用一个sas硬盘安装系统,正常通过,安装好

  • 详细解读Java的串口编程

    常见问题 JavaComm 和 RxTX 安装时有一些与众不同的地方.强烈建议按照安装说明一点点的安装.如果安装说明要求一个jar文件或一个共享库必须在某一特定的文件夹下,那这就意味着需要严肃对待.如果说明要求一个特定的文件或设备需要拥有一个特定的所有权或访问权,这也意味着需要严肃处理.很多安装问题都只是因为没有按照安装说明要求的去做而引起的. 特别要注意的是一些版本的JavaComm会带有两个安装说明.一个用于java 1.2及以后的版本,一个用于java 1.1版本.使用错误的安装说明会导致

  • Python操作串口的方法

    本文实例讲述了Python操作串口的方法.分享给大家供大家参考.具体如下: 首先需确保安装了serial模块,如果没安装的话就安装一下python-pyserial. 一个Python实现的串口Echo import serial import sys try: ser = serial.Serial('/dev/ttyUSB0', 9600) except Exception, e: print 'open serial failed.' exit(1) print 'A Serial Ech

  • Arduino 数据类型转换(单机片)详细介绍

    Arduino 数据类型转换 如何轻松玩转Arduino单片机,当我在进行数据转换的时候,遇到了问题,尝试了C语言和C++中的好多函数,都没有达到将float型数据转换为char型的目的.苦苦查阅资料后,终于找到了一个大神级函数!!!dtostrf(),可以轻松实现数据类型from   float   to  char .            get it 格式如下: char* dtostrf(double _val,signed char _width, unsigned char pre

  • python简单程序读取串口信息的方法

    本文实例讲述了python简单程序读取串口信息的方法.分享给大家供大家参考.具体分析如下: 这段代码需要调用serial模块,通过while循环不断读取串口数据 import time import serial ser = serial.Serial( #下面这些参数根据情况修改 port='COM1', baudrate=9600, parity=serial.PARITY_ODD, stopbits=serial.STOPBITS_TWO, bytesize=serial.SEVENBIT

  • PHP通过串口实现发送短信

    随技术进步,短信收发领域按时间先后产生了三种模式:BLOCK MODE,基于AT指令的TEXT MODE,基于AT指令的PDU MODE.其中,TEXT MODE比较简单,多款诺基亚手机均支持此款模式.西门子的手机大多数只支持PDU MODE.PDU 模式是收发短信的一种方法,短信正文经过十六进制编码后被传送.目前,PDU已取代BLOCK MODE. SMS是由Etsi所制定的一个规范(GSM 03.40 和GSM 03.38).当使用7-bits编码时,它可以发送最多160个字符;但用8-bi

  • Android 串口通信编程及串口协议分析

    Android 串口通信编程:嵌入式编程和可穿戴设备及智能设备都会用到串口,这里就带大家分析下, 一,android串口通信 串口通信采用一个第三方开源项目,实现串口数据收发. 1. 使用了http://code.google.com/p/android-serialport-api/的项目的serialport api和jni: 2. 支持4串口同时收发,有定时自动发送功能,收发模式可选Txt或Hex模式: 3.  n,8,1,没得选: 4. 为减轻界面卡顿的情况,接收区的刷新采用单独的线程进

随机推荐