当前位置:   article > 正文

android串口通信——android-serialport-api

android-serialport-api

一.串口通信原理

串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。
波特率:这是一个衡量符号传输速率的参数。

二、android-serialport-api

主要介绍一下内容:

1.android-serialport-api简单介绍
2.硬件地址和波特率的获取
3.发送和数据和接收数据

1.android-serialport-api简单介绍

android-serialport-api下有两个主要的类

参数    说明
SerialPort    获取串口的类(其实就是获取输入输出流)
SerialPortFinder    获取硬件地址的类

1.1 SerialPort的介绍

1.1.1调用的顺序如下:

SerialPort构造方法 –> jni open –>c open

 

需要一个操作串口的对象
创建SerialPort对象
JNI(使用c语言的open函数打开文件)
操作的文件对象,波特率,文件操作权限。
调用jni的open方法创建对象。
返回文件的描述(FileDescriptor)
返回需要的串口对象。

1.1.2 SerialPort的构造函数

public SerialPort(File device, int baudrate, int flags)

参数    说明
device    要操作的文件对象
baudrate    波特率
flags     文件操作的标志

1.1.3 C语言中的open函数

c语言中open函数是用来打开一个文件的。具体可以看这里C语言open()函数:打开文件函数

int open(const char * pathname, int flags);

参数 说明
pathname    pathname 指向欲打开的文件路径字符串
flags    文件的打开打开方式: O_RDONLY 以只读方式打开文件O_WRONLY 以只写方式打开文件O_RDWR 以可读写方式打开文件

返回值:若所有欲核查的权限都通过了检查则返回0 值, 表示成功, 只要有一个权限被禁止则返回-1.

SerialPort类

  1. package android_serialport_api;
  2. import java.io.File;
  3. import java.io.FileDescriptor;
  4. import java.io.FileInputStream;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.OutputStream;
  9. import android.util.Log;
  10. /**
  11.  1. 串口类
  12.  2. @author qiwenming
  13.  3.  */
  14. public class SerialPort {
  15.     private static final String TAG = "SerialPort";
  16.     /*
  17.      * Do not remove or rename the field mFd: it is used by native method close();
  18.      */
  19.     private FileDescriptor mFd;//文件描述
  20.     private FileInputStream mFileInputStream;
  21.     private FileOutputStream mFileOutputStream;
  22.     /**
  23.      *获得一个窗口 
  24.      * @param device 设备
  25.      * @param baudrate 波特率
  26.      * @param flags 标志
  27.      * @throws SecurityException
  28.      * @throws IOException
  29.      */
  30.     public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
  31.         /* Check access permission */ //检查权限
  32.         if (!device.canRead() || !device.canWrite()) {
  33.             try {
  34.                 //如果丢失权限,就再获取权限
  35.                 /* Missing read/write permission, trying to chmod the file */
  36.                 Process su;
  37.                 su = Runtime.getRuntime().exec("/system/bin/su");
  38.                 String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";
  39.                 //写命令
  40.                 su.getOutputStream().write(cmd.getBytes());
  41.                 if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
  42.                     throw new SecurityException();
  43.                 }
  44.             } catch (Exception e) {
  45.                 e.printStackTrace();
  46.                 throw new SecurityException();
  47.             }
  48.         }
  49.         //打开设备,这里面调用jni 的open方法
  50.         mFd = open(device.getAbsolutePath(), baudrate, flags);
  51.         if (mFd == null) {
  52.             Log.e(TAG, "native open returns null");
  53.             throw new IOException();
  54.         }
  55.         mFileInputStream = new FileInputStream(mFd);
  56.         mFileOutputStream = new FileOutputStream(mFd);
  57.     }
  58.     // Getters and setters
  59.     public FileInputStream getInputStream() {
  60.         return mFileInputStream;
  61.     }
  62.     public FileOutputStream getOutputStream() {
  63.         return mFileOutputStream;
  64.     }
  65.     //============== JNI=========================================
  66.     /**
  67.      * 打开串口设备的方法
  68.      * @param path 设备的绝对路径
  69.      * @param baudrate 波特率
  70.      * @param flags 标志
  71.      * @return
  72.      */
  73.     private native static FileDescriptor open(String path, int baudrate, int flags);
  74.     //关闭设备
  75.     public native void close();
  76.     //加载库文件
  77.     static {
  78.         System.loadLibrary("serial_port");
  79.     }
  80. }

SerialPort.c 中open函数

  1. /*
  2.  * Class:     android_serialport_SerialPort
  3.  * Method:    open
  4.  * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
  5.  */
  6. JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
  7.   (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
  8. {
  9.     //变量定义,参数检查
  10.     ......
  11.     /* Opening device */
  12.     //打开设备
  13.     {
  14.         jboolean iscopy;
  15.         const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
  16.         LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
  17.         //使用c语言的open函数打开文件
  18.         fd = open(path_utf, O_RDWR | flags);
  19.         LOGD("open() fd = %d", fd);
  20.         (*env)->ReleaseStringUTFChars(env, path, path_utf);
  21.         if (fd == -1)
  22.         {
  23.             /* Throw an exception */
  24.             LOGE("Cannot open port");
  25.             /* TODO: throw an exception */
  26.             return NULL;
  27.         }
  28.     }
  29.     //设备的一些设置
  30.     ......
  31.     /* Create a corresponding file descriptor */
  32.     //创建我们需要的 file descriptor
  33.     {
  34.         jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
  35.         jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
  36.         jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
  37.         mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
  38.         (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
  39.     }
  40.     return mFileDescriptor;
  41. }

2.发送和数据和接收数据

2.1数据的发送

数据的发送很简单,通过SerialPort对象的getOutputStream()方法获取到输出流,然后把数据写入到这个流中就行了。

  1.       .....
  2.       mOutputStream = mSerialPort.getOutputStream();
  3.       // 把字符指令转为字节
  4.         byte[] text = StringUtils.hexStringToBytes(sendData.commandStr);
  5.         try {
  6.             mOutputStream.write(text);
  7.             mOutputStream.flush();
  8.         } catch (IOException e) {
  9.             e.printStackTrace();
  10.         }

2.2 数据的接收

数据的接收,通过SerialPort对象的getInputStream()方法获取到输入流,然后读流的就行了。不过一般会开一个线程去读数据

  1. ......
  2. try {
  3.             mSerialPort = mApplication.getSerialPort();
  4.             mOutputStream = mSerialPort.getOutputStream();
  5.             mInputStream = mSerialPort.getInputStream();
  6.             /* Create a receiving thread */
  7.             mReadThread = new ReadThread();
  8.             mReadThread.start();
  9.         } catch (SecurityException e) {
  10.             DisplayError(R.string.error_security);
  11.         } catch (IOException e) {
  12.             DisplayError(R.string.error_unknown);
  13.         } catch (InvalidParameterException e) {
  14.             DisplayError(R.string.error_configuration);
  15.         }
  16. ....
  17. /**
  18. *读取流中的数据
  19. */
  20. private class ReadThread extends Thread {
  21.         @Override
  22.         public void run() {
  23.             super.run();
  24.             while(!isInterrupted()) {
  25.                 int size;
  26.                 try {
  27.                     byte[] buffer = new byte[64];
  28.                     if (mInputStream == null) return;
  29.                     size = mInputStream.read(buffer);
  30.                     if (size > 0) {
  31.                         onDataReceived(buffer, size);
  32.                     }
  33.                 } catch (IOException e) {
  34.                     e.printStackTrace();
  35.                     return;
  36.                 }
  37.             }
  38.         }
  39.     }


具体的话可以看项目中的SerialPortActivity这个类,但是这个只是一个例子而已,真正在使用中,我们要根据自己的要求来修改,后面的blog中会介绍。

3.硬件地址和波特率的获取

我们都知道要操作一个串口设备,我们需要知道它的地址和波特率。其中波特率的话是一般根据硬件的说明来设定的,所以这个的话,我们只需要看说明就行了,当然在选择的波特率的时候,我们可以提供一些值来做选择。

3.1 波特率

我们可以创建一个集合来存储波特率,用到的时候来选择就行。

  1. /**
  2.  * @author qiwenming
  3.  * @creation 2015-6-18 下午4:10:00
  4.  * @instruction 波特率的集合
  5.  */
  6. public class BaudRateListConstants {
  7.     private static List<Integer> list = new ArrayList<Integer>();
  8.     public static List<Integer> getBaudRateList(){
  9.         if(list.size()<=0){
  10.             list.add(50);
  11.             list.add(75);
  12.             list.add(110);
  13.             list.add(134);
  14.             list.add(150);
  15.             list.add(200);
  16.             list.add(300);
  17.             list.add(600);
  18.             list.add(1200);
  19.             list.add(1800);
  20.             list.add(2400);
  21.             list.add(4800);
  22.             list.add(9600);
  23.             list.add(38400);
  24.             list.add(57600);
  25.             list.add(115200);
  26.             list.add(230400);
  27.             list.add(460800);
  28.             list.add(500000);
  29.             list.add(576000);
  30.             list.add(921600);
  31.             list.add(1000000);
  32.             list.add(1152000);
  33.             list.add(1500000);
  34.             list.add(2000000);
  35.             list.add(2500000);
  36.             list.add(3000000);
  37.             list.add(3500000);
  38.             list.add(4000000);
  39.         }
  40.         return list;
  41.     }
  42. }

3.2 硬件地址的获取

其实硬件地址,也是一个集合来存储着,用得使用,指定硬件地址。我们说andorid-serialport-api中主要的java文件有两个,其中SerialPort我们已经说了,剩下的SerialPortFinder就是获取硬件地址的类。
获取硬件地址的基本步骤:

 

读取/proc/tty/drivers文件
获取到基本地址,然后读取/dev下的所有文件,文件的开始地址与基础地址匹配的就是串口设备,文件的绝对路径地址存入到集合中。
串口设备的真实地址集合

或者如下:

开始
读取/proc/tty/drivers文件
获取到基本地址,然后读取/dev下的所有文件, 文件的开始地址与基础地址匹配的就是串口设备, 文件的绝对路径地址存入到集合中
串口设备的真实地址集合
结束


3.2.1读取 /proc/tty/drivers 文件中的内容

读取 /proc/tty/drivers 文件中的内容,判断哪些是串口设备的基础地址(通过serail这个关键字来判断)。我们可以看到drivers的文件内容如下(外设不同,文件内容不同)

drivers文件

 流程图如下:

1.在读取文件的内容的时候,是一行一行的读取的。(如下面的那条数据)
2.读取到一行数据的时候,窃取第一串字符为文件名。(如下面的那条数据,文件名为:serial)
3.按照” +”(一个或者多个空格)分割一行数据。(如下面的数据分割后的结果是: w[0]=”serial”,w[1]=”/dev/ttyS”,w[2]=”4”,w[3]=”64-67”,w[4]=”serial”)
4.判断上面分割的字符数组的长度是否大于等于5,并且最后一个字符串是不是”serial”,满足条件传递filename到3.2.2中执行 3.2.2 。不满足读取下一行,直到读取完成。

serial               /dev/ttyS       4 64-67 serial


3.2.2通过获取到基础地址,去查找真正的串口设备地址。把地址集合存起来。

流程图如下:

下面是/dev 目录的切图(部分)

这里写图片描述

1.获取/dev下的所有文件,得到一个文件数组files。
2.循环files,等到files[i]。
3.获取files[i]的绝度路径,判断这个绝对路径是不是以3.2.1中传递过来的基础地址filename开头。(例如:我们传递过来的是基础地址是:/dev/ttyS,那么我们这里满足条件的就是:/dev/ttyS0,/dev/ttyS1,/dev/ttyS2,/dev/ttyS3,这四个设备就是串口设备)
4.满足3说明是串口设备,保存到集合中,不满足执行5。
5.判读是否已经循环结束。结束返回集合,没有结束执行2。

SerialPortFinder

  1. /*
  2. * Copyright 2009 Cedric Priscal
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package android_serialport_api;
  17. import java.io.File;
  18. import java.io.FileReader;
  19. import java.io.IOException;
  20. import java.io.LineNumberReader;
  21. import java.util.Iterator;
  22. import java.util.Vector;
  23. import android.util.Log;
  24. /**
  25. * 串口通讯的 寻检器
  26. *
  27. * @author qiwenming
  28. *
  29. */
  30. public class SerialPortFinder {
  31. /**
  32. * 设备类
  33. *
  34. * @author qiwenming
  35. */
  36. public class Driver {
  37. public Driver(String name, String root) {
  38. mDriverName = name;
  39. mDeviceRoot = root;
  40. }
  41. private String mDriverName;//设备名称
  42. private String mDeviceRoot;//设备根节点
  43. Vector<File> mDevices = null;//设备集合
  44. /**
  45. * 获取设备集合 这个是特定类型的设备 比如USB等
  46. * @return
  47. */
  48. public Vector<File> getDevices() {
  49. //如果设备已经添加过,就不添加了,否则就必须添加
  50. if (mDevices == null) {//如果
  51. mDevices = new Vector<File>();
  52. File dev = new File("/dev");
  53. //获取 /dev 下的设备
  54. File[] files = dev.listFiles();
  55. int i;
  56. for (i = 0; i < files.length; i++) {
  57. //这里是拿文件的路径和我们 传递进来的路径进行对比,看看开头是不是我们传递捡来的 ,传递进来的函数是 外部类 getDrivers()
  58. //比如 我们传递进来的文件路径是: /dev/ttyUSB 那么我们获取道的文件的绝对路径 如:/dev/ttyUSB1 /dev/ttyUSB2
  59. //就满足下面这个条件 ,就会加到集合中去
  60. if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
  61. Log.d(TAG, "Found new device: " + files[i]);
  62. mDevices.add(files[i]);
  63. }
  64. }
  65. }
  66. return mDevices;
  67. }
  68. public String getName() {
  69. return mDriverName;
  70. }
  71. }
  72. private static final String TAG = "SerialPort";
  73. private Vector<Driver> mDrivers = null;
  74. /**
  75. * 获取设备
  76. * @return
  77. * @throws IOException
  78. * 其实就是读取 /proc/tty/drivers 这个文件
  79. * drivers中有设备的地址的总地址添加到一个集合中
  80. */
  81. Vector<Driver> getDrivers() throws IOException {
  82. if (mDrivers == null) {
  83. mDrivers = new Vector<Driver>();
  84. //读取行的流对象
  85. LineNumberReader r = new LineNumberReader(new FileReader(
  86. "/proc/tty/drivers"));
  87. String l;
  88. while ((l = r.readLine()) != null) {
  89. // Issue 3:
  90. // Since driver name may contain spaces, we do not extract
  91. // driver name with split()
  92. //看下面这个会发现 原来我们的 这个第一字符到第二个字符之间是21个,这也就是为什么这里面要用的 0x15 这个原因了
  93. //dev/tty /dev/tty 5 0 system:/dev/tty
  94. String drivername = l.substring(0, 0x15).trim();//其实就是获取第一个非空格字符串
  95. String[] w = l.split(" +");//这里是正则表达式:" +" 表示有一个或者多个空格
  96. if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
  97. Log.d(TAG, "Found new driver " + drivername + " on "
  98. + w[w.length - 4]);
  99. mDrivers.add(new Driver(drivername, w[w.length - 4]));
  100. }
  101. }
  102. r.close();//关闭流
  103. }
  104. return mDrivers;
  105. }
  106. /**
  107. * 得到所有的设备的名称
  108. * @return
  109. */
  110. public String[] getAllDevices() {
  111. Vector<String> devices = new Vector<String>();
  112. // Parse each driver
  113. Iterator<Driver> itdriv;
  114. try {
  115. itdriv = getDrivers().iterator();//获取道设备的根地址
  116. while (itdriv.hasNext()) {//迭代获取具体的根路径
  117. Driver driver = itdriv.next();
  118. Iterator<File> itdev = driver.getDevices().iterator();//获取道包含根地址的设备对象的集合
  119. while (itdev.hasNext()) {//迭代获得具体的设备
  120. String device = itdev.next().getName();
  121. String value = String.format("%s (%s)", device,
  122. driver.getName());
  123. devices.add(value);
  124. }
  125. }
  126. } catch (IOException e) {
  127. e.printStackTrace();
  128. }
  129. return devices.toArray(new String[devices.size()]);
  130. }
  131. /**
  132. * 获取道设备的路径
  133. * @return
  134. */
  135. public String[] getAllDevicesPath() {
  136. Vector<String> devices = new Vector<String>();
  137. // Parse each driver
  138. Iterator<Driver> itdriv;
  139. try {
  140. itdriv = getDrivers().iterator();
  141. while (itdriv.hasNext()) {
  142. Driver driver = itdriv.next();
  143. Iterator<File> itdev = driver.getDevices().iterator();
  144. while (itdev.hasNext()) {
  145. //获取
  146. String device = itdev.next().getAbsolutePath();
  147. devices.add(device);
  148. }
  149. }
  150. } catch (IOException e) {
  151. e.printStackTrace();
  152. }
  153. return devices.toArray(new String[devices.size()]);
  154. }
  155. }

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

转载于:https://blog.csdn.net/qiwenmingshiwo/article/details/49557889

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/731089
推荐阅读
相关标签
  

闽ICP备14008679号