广

android开发

  • IOS开发
  • android开发
  • PHP编程
  • JavaScript
  • ASP.NET
  • ASP编程
  • JSP编程
  • Java编程
  • 易语言
  • Ruby编程
  • Perl编程
  • AJAX
  • 正则表达式
  • C语言
  • 编程开发

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

    2018-04-05 08:31:46 次阅读 稿源:互联网
    广告
    提到串口编程,就不得不提到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 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!

    一起学吧部分文章转载自互联网,供读者交流和学习,若有涉及作者版权等问题请及时与我们联系,以便更正、删除或按规定办理。感谢所有提供资讯的网站,欢迎各类媒体与一起学吧进行文章共享合作。

    广告
    广告
    广告