提到串口编程,就不得不提到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:

文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的FileInputStreamFileOutputStream。这是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<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 driverIterator<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 driverIterator<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()]);}}

上面这个类在“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 setterspublic InputStream getInputStream() {return mFileInputStream;}public OutputStream getOutputStream() {return mFileOutputStream;}// JNIprivate 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 {@Overridepublic 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();}@Overrideprotected 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);@Overrideprotected void onDestroy() {if (mReadThread != null)mReadThread.interrupt();mApplication.closeSerialPort();mSerialPort = null;super.onDestroy();}}

6)编写string.xml 以及baudrates.xml文件

在string.xml文件中添加:

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

在baudrates.xml文件中添加

<?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>

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

<?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>

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

写到这里,代码基本上写完了。下面就是要实现JNI层的功能了,要实现JNI,必须首先生成头文件,头文件的生成方式也很简单, 我们编译工程,在终端输入 javah org.winplus.serial.utils.SerialPort 则会生成头文件:org_winplus_serial_utils_SerialPort.h,这个头文件的名字可以随意命名。我们将它命名为:SerialPort.h拷贝到新建的目录jni中,新建SerialPort.c 文件,这两个文件的代码就不贴出来了。直接到上传的代码中看吧。


(四)串口的应用,可实现扫描头,指纹识别等外围USB转串口的特色应用。

还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!

源码下载地址==》

精心准备的串口编程资料,欢迎下载==》

原创文章,转载请注明出处:http://www.blog.csdn.net/tangcheng_ok

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. 一款常用的 Squid 日志分析工具
  3. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  4. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  5. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  6. 如何提高Android代码的安全性
  7. Android运行时异常“Binary XML file line # : Error inflating
  8. Android学习【7】manifest文件讲解
  9. Android沙盒开发之系统libc库定制修改

随机推荐

  1. textarea高度自适应自动展开
  2. javascript encodeURIComponent并将空格
  3. 使用append方法将对象转换为字符串
  4. 什么“返回此”在javascript函数中做什么
  5. 实现浮动广告的代码
  6. OnClick事件只获取第一个动态创建的行/ i
  7. 删除没有\ r \ n的换行符
  8. 为什么我的javascript/jquery代码不能像
  9. 从表的第一行和第一列中删除可选择的jque
  10. 【JavaScript】案例一:使用JS完成注册页面