• Android串口编程入门


    基本常识

    串口通信:指串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以使用一根线发送数据的同时接收数据。在串口通信中,常用的协议包括RS232、RS-422和RS-485。

    在Android开发中,对串口数据的读取和写入,实际上是是通过I/O流读取、写入文件数据。
    串口用完记得关闭(文件关闭)。 串口关闭,即是文件流关闭。

    一、准备so库以及相关SDK

    用到开源库serialPort-api
    下载其中相关so库资源导入到项目中
    在这里插入图片描述
    其中libs文件夹中的armeabi、armeabi-v7a代表不同的cpu架构,可以根据自己app运行环境自动加载,通常只需要armeabi就可以了。android_serialport_api包下的两个文件则是用来加载so库以及提供相关接口的封装SDK;

    注意:这里的包名不可以修改,必须跟so库中的保持一致(这同样在以后对接其他外设SDK时,在加载so库方法经常会遇到方法名加载不到的原因)

    在拷贝完相关so库之后我们仍然需要在gradle中指定我们支持的cpu架构,保持libs包中一致即可:

        defaultConfig {
            ndk {
                abiFilters "armeabi","armeabi-v7a"  // "armeabi", "x86", "arm64-v8a"
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    SerialPort类:

    /*
     * Copyright 2009 Cedric Priscal
     * 
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     * 
     * http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License. 
     */
    
    package android_serialport_api;
    
    import android.util.Log;
    
    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 java.util.ArrayList;
    import java.util.Scanner;
    
    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, int parity) throws SecurityException, IOException {
    
    		/* Check access permission */
    		if (!device.canRead() || !device.canWrite()) {
    			try {
    				/* Missing read/write permission, trying to chmod the file */
    				//Runtime.getRuntime().exec 会提示需要ACCESS_SUPERUSER权限
    				Process su;
    				su = Runtime.getRuntime().exec("/system/bin/su");
    //				su = Runtime.getRuntime().exec("/system/xbin/su");
    				String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";
    				su.getOutputStream().write(cmd.getBytes());
    
    				WatchThread wt = new WatchThread(su);
    				wt.start();
    				if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
    					ArrayList<String> commandStream = wt.getStream();
    					wt.setOver(true);
    					throw new SecurityException();
    				}
    			} catch (Exception e) {
    				e.printStackTrace();
    				throw new SecurityException();
    			}
    		}
    
    		mFd = open(device.getAbsolutePath(), baudrate, flags, parity);
    		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, int parity);
    	public native void close();
    	static {
    		System.loadLibrary("serial_port");
    	}
    
    	class WatchThread extends Thread {
    		Process p;
    		boolean   over;
    		ArrayList<String> stream;
    		public WatchThread(Process p) {
    			this.p = p;
    			over = false;
    			stream = new ArrayList<String>();
    		}
    		public void run() {
    			try {
    				if(p == null)return;
    				Scanner br = new Scanner(p.getInputStream());
    				while (true) {
    					if (p==null || over) break;
    					while(br.hasNextLine()){
    						String tempStream = br.nextLine();
    						if(tempStream.trim()==null||tempStream.trim().equals(""))continue;
    						stream.add(tempStream);
    					}
    				}
    			} catch(Exception e){e.printStackTrace();}
    		}
    
    		public void setOver(boolean   over)   {
    			this.over   =   over;
    		}
    		public ArrayList<String> getStream() {
    			return stream;
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125

    从这个类中我们可以看到,通过System.loadLibrary来加载我们对应的so库,“serial_port”名称则对应libs文件夹下面的armeabi包中的so库名称,如果不是一致的则会抛出异常提示加载so库失败。

    static {
    		System.loadLibrary("serial_port");
    	}
    
    • 1
    • 2
    • 3

    当然我们有些也可以将so库放置在jni文件夹或者jnilib文件夹下面,如果我们so库防止的路径没有问题,但是在加载so库的时候仍然抛出异常提示找不到对应的so库,那么可能是我们程序并没有识别到相应的路径,这个时候我们就需要在gradle里面进行配置:

        sourceSets.main {
            jniLibs.srcDir "libs"
        }
    
    • 1
    • 2
    • 3

    同时可以看到该类中封装的打开和关闭串口的方法:

    	private native static FileDescriptor open(String path, int baudrate, int flags, int parity);
    	public native void close();
    
    • 1
    • 2

    如果没有权限的也需要配置相关的系统权限,包括我们进行串口初始化时需要的root权限也是必须要的,如果没有root权限则无法进行串口初始化。到此为止我们串口编程的基本准备工作已经全部准备就绪。

    二、编写串口通讯工具类

    首先我们需要编写的是串口通讯需要的工具类,该工具类主要解决一下几个问题:串口的打开、关闭、输入监听、输出等;同时该工具类作为全局唯一的操作类来进行串口通讯的统一管理:

    package com.xiye.reservesamplecabinet.manager
    
    import android_serialport_api.SerialPort
    import com.xiye.reservesamplecabinet.utils.L
    import com.xiye.reservesamplecabinet.utils.T
    import java.io.File
    import java.io.IOException
    import java.io.InputStream
    import java.io.OutputStream
    import java.security.InvalidParameterException
    
    /**
     * Created by Administrator on 2018/8/3.
     */
    class BoxActionManager private constructor() {
    
        companion object {
    
            val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { BoxActionManager() }
    
            private var mSerialPort: SerialPort? = null
            private var mOutputStream: OutputStream? = null
            private var mInputStream: InputStream? = null
            private var mReadDataThread: Thread? = null
    
            /**
             * 初始化锁控串口
             */
            @Throws(IOException::class)
            private fun initSerialPort() {
                if (mSerialPort == null) {
                    mSerialPort = getSerialPort(0, "dev/ttyS0", 9600)
                    if (mSerialPort == null) {
                        T.instance.showToast("串口初始化失败!")
                        return
                    }
                    mOutputStream = mSerialPort?.outputStream
                    mInputStream = mSerialPort?.inputStream
                }
            }
    
            /**
             * 初始化串口
             */
            private fun getSerialPort(parity: Int, path: String, baudrate: Int): SerialPort? {
                var mSerialPort: SerialPort? = null
                if (path.isEmpty() || baudrate == -1) {
                    throw InvalidParameterException()
                }
    
                try {
                    mSerialPort = SerialPort(File(path), baudrate, 0, 0)
                } catch (e: IOException) {
                    e.printStackTrace()
                } catch (e: SecurityException) {
                    e.printStackTrace()
                }
                return mSerialPort
            }
        }
    
        init {
            if (mSerialPort == null) {
                try {
                    initSerialPort()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
            initThread()
        }
    
        /**
         * 初始化串口读取线程
         */
        private fun initThread() {
            if (mReadDataThread == null) {
                mReadDataThread = Thread {
                    while (true) {
                        try {
                            if (mInputStream != null) {
                                val count = mInputStream!!.available()
                                val buffer = ByteArray(count)
                                val size = mInputStream!!.read(buffer)
                                val hexStr = TransUtils.bytes2hex(buffer)
                                if (size > 0) {
                                    onDataReceiver(buffer)
                                }
                                try {
                                    Thread.sleep(50L)
                                } catch (e: InterruptedException) {
                                    e.printStackTrace()
                                }
                            }
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }
                    }
                }
                mReadDataThread?.start()
            }
        }
    
        private fun onDataReceiver(buffer: ByteArray) {
        
        }
    
    
        @Synchronized
        fun openDoor(boardNo: Int, lockNo: Int) {
            if (checkSerialPort()) {
                return
            }
            val cmd = byteArrayOf(
                boardNo.toByte(), 0xF2.toByte(), 0x55, lockNo.toByte()
            )
            try {
                mOutputStream?.write(TransUtils.getSendDatas(cmd))
                mOutputStream?.flush()
            } catch (e: IOException) {
                T.instance.showToast(e.message)
            }
        }
    
    
        @Synchronized
        fun queryDoor(boardNo: Int, lockNo: Int) {
            if (checkSerialPort()) {
                return
            }
            val cmd = byteArrayOf(
                boardNo.toByte(), 0xF1.toByte(), 0x55, lockNo.toByte()
            )
            try {
                mOutputStream?.write(TransUtils.getSendDatas(cmd))
                mOutputStream?.flush()
            } catch (e: IOException) {
                T.instance.showToast(e.message)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    在初始化方法中可以看到mSerialPort = getSerialPort(0, “dev/ttyS0”, 9600),是指定了一个固定的串口进行初始化,我们也可以通过SerialPortFinder类中提供的方法进行一个动态的搜索串口并进行相关初始化,需要根据项目不同的需求来灵活使用。

    其中onDataReceiver方法则是监听到的输入信息,我们可以通过协议相关的约定进行一个解析来判断监听到的输入数据是否合法,同时解析出我们需要的数据帧。

    同样我们是通过**mOutputStream?.write()**方法来对数据输出,输出的数据帧同样是根据协议中的约定来进行组帧。

    感兴趣的同学可以通过NDK开发中jni配置及调用GPIO了解如何自己一步步编译属于自己的so库文件并运用到项目中

  • 相关阅读:
    Python快速上手爬虫的7大技巧
    git远程分支强制覆盖本地分支
    Linux学习笔记——Makefile的使用
    【Java】绘图入门和机制,绘图方法演示(绘制坦克)
    [JavaScript]_[初级]_[关于forin或for...in循环语句的用法]
    Springioc注释使用
    MyBatis(简化数据库操作的持久层框架)->基础入门
    C语言入门(八)一维数组
    RSA系列第1篇:RSA 介绍
    安卓实现网格布局的效果
  • 原文地址:https://blog.csdn.net/d38825/article/details/126365367