Android 串口编程原理和实现方式(附源码)

日期:2019-08-22 分类:微信刷脸开发SDK资料 下载:2231 浏览:5500

点击下载

更多附件:
点击下载 点击下载

参考链接1:https://www.logcg.com/archives/2831.html#comments

参考链接2:https://blog.csdn.net/tangcheng_ok/article/details/7021470


    提到串口编程,就不得不提到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<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()]);

}

}


上面这个类在“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文件中添加:

    <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转串口的特色应用。

 ———————————————— 

版权声明:本文为CSDN博主「tangcheng_ok」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/tangcheng_ok/article/details/7021470


上一篇:对接收银机双工线 开发SDK

下一篇:华捷艾米摄像头开发SDK

0755 - 8336 8811
13632541019 18025353128