提到串口编程,就不得不提到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;
-
-
- {
- speed = getBaudrate(baudrate);
- if (speed == -1) {
-
- LOGE("Invalid baudrate");
- return NULL;
- }
- }
-
-
- {
- 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)
- {
-
- LOGE("Cannot open port");
-
- return NULL;
- }
- }
-
-
- {
- struct termios cfg;
- LOGD("Configuring serial port");
- if (tcgetattr(fd, &cfg))
- {
- LOGE("tcgetattr() failed");
- close(fd);
-
- return NULL;
- }
-
- cfmakeraw(&cfg);
- cfsetispeed(&cfg, speed);
- cfsetospeed(&cfg, speed);
-
- if (tcsetattr(fd, TCSANOW, &cfg))
- {
- LOGE("tcsetattr() failed");
- close(fd);
-
- return NULL;
- }
- }
- </span>
(二)FileDescritor:
文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的FileInputStream
或FileOutputStream
。这是API的描述,不太好理解,其实可简单的理解为:FileDescritor就是对一个文件进行读写。
(三)实现串口通信细节
1) 建工程:SerialDemo包名:org.winplus.serial,并在工程目录下新建jni和libs两个文件夹和一个org.winplus.serial.utils,如下图:
2) 新建一个类:SerialPortFinder,添加如下代码:
上面这个类在“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";
-
-
-
-
-
- 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 {
-
- 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);
- }
-
-
- public InputStream getInputStream() {
- return mFileInputStream;
- }
-
- public OutputStream getOutputStream() {
- return mFileOutputStream;
- }
-
-
- 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) {
-
- SharedPreferences sp = getSharedPreferences("android_serialport_api.sample_preferences", MODE_PRIVATE);
- String path = sp.getString("DEVICE", "");
- int baudrate = Integer.decode(sp.getString("BAUDRATE", "-1"));
-
-
- if ( (path.length() == 0) || (baudrate == -1)) {
- throw new InvalidParameterException();
- }
-
-
- 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;
-
-
-
-
- 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();
-
-
- 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);
-
-
- 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转串口的特色应用。
原创文章,转载请注明出处:http://www.blog.csdn.net/tangcheng_ok
还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!