开源中文网

您的位置: 首页 > Android开发 > 正文

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

Tags:串口 原理 方式
关于开源中文网 - 联系我们 - 广告服务 - 网站地图 - 版权声明