分享

Android串口通讯SerialPort(使用篇)...

 创始元灵6666 2022-08-16 发布于河北

1.什么是串口

在不会使用串口通讯之前,暂且可以把它理解为“一个可通讯的口”;使用篇不深入探讨理论及原理。能理解串口如何使用之后,可以查看Android串口通讯SerialPort(浅谈原理)

2.添加依赖

1.)在 module 中的 build.gradle 中的 dependencies 中添加以下依赖:

  1. dependencies {
  2. //串口
  3. implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0'
  4. }

2.)低版本的 gradle 在Project 中的 build.gradle 中的 allprojects 中添加以下 maven仓库 (不添加任然无法加载SerialPort);

  1. allprojects {
  2. repositories {
  3. maven { url "https://" }//maven仓库
  4. }
  5. }

高版本的 gradle 已经废弃了 allprojects 在 settings.gradle 中 repositories 添加以下maven仓库(不添加任然无法加载SerialPort);

  1. dependencyResolutionManagement {
  2. repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  3. repositories {
  4. google()
  5. mavenCentral()
  6. jcenter() // Warning: this repository is going to shut down soon
  7. maven { url "https://" }//maven仓库
  8. }
  9. }

3.编写串口处理类

1.)串口处理类:SerialHandle ;简单概括这个类,就是通过串口对象去获取两个流(输入流、输出流),通过者两个流来监听数据或者写入指令,硬件收到后执行。同时注意配置参数(只要支持串口通讯的硬件,一般说明书上都会有写)

  1. package com.chj233.serialmode.serialUtil;
  2. import android.serialport.SerialPort;
  3. import android.util.Log;
  4. import java.io.BufferedInputStream;
  5. import java.io.File;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.OutputStream;
  9. import java.util.concurrent.ScheduledFuture;
  10. import java.util.concurrent.TimeUnit;
  11. /**
  12. * 串口实处理类
  13. */
  14. public class SerialHandle implements Runnable {
  15. private static final String TAG = "串口处理类";
  16. private String path = "";//串口地址
  17. private SerialPort mSerialPort;//串口对象
  18. private InputStream mInputStream;//串口的输入流对象
  19. private BufferedInputStream mBuffInputStream;//用于监听硬件返回的信息
  20. private OutputStream mOutputStream;//串口的输出流对象 用于发送指令
  21. private SerialInter serialInter;//串口回调接口
  22. private ScheduledFuture readTask;//串口读取任务
  23. /**
  24. * 添加串口回调
  25. *
  26. * @param serialInter
  27. */
  28. public void addSerialInter(SerialInter serialInter) {
  29. this.serialInter = serialInter;
  30. }
  31. /**
  32. * 打开串口
  33. *
  34. * @param devicePath 串口地址(根据平板的说明说填写)
  35. * @param baudrate 波特率(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)
  36. * @param isRead 是否持续监听串口返回的数据
  37. * @return 是否打开成功
  38. */
  39. public boolean open(String devicePath, int baudrate, boolean isRead) {
  40. return open(devicePath, baudrate, 7, 1, 2, isRead);
  41. }
  42. /**
  43. * 打开串口
  44. *
  45. * @param devicePath 串口地址(根据平板的说明说填写)
  46. * @param baudrate 波特率(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)
  47. * @param dataBits 数据位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)
  48. * @param stopBits 停止位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)
  49. * @param parity 校验位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)
  50. * @param isRead 是否持续监听串口返回的数据
  51. * @return 是否打开成功
  52. */
  53. public boolean open(String devicePath, int baudrate, int dataBits, int stopBits, int parity, boolean isRead) {
  54. boolean isSucc = false;
  55. try {
  56. if (mSerialPort != null) close();
  57. File device = new File(devicePath);
  58. mSerialPort = SerialPort // 串口对象
  59. .newBuilder(device, baudrate) // 串口地址地址,波特率
  60. .dataBits(dataBits) // 数据位,默认8;可选值为5~8
  61. .stopBits(stopBits) // 停止位,默认1;1:1位停止位;2:2位停止位
  62. .parity(parity) // 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
  63. .build(); // 打开串口并返回
  64. mInputStream = mSerialPort.getInputStream();
  65. mBuffInputStream = new BufferedInputStream(mInputStream);
  66. mOutputStream = mSerialPort.getOutputStream();
  67. isSucc = true;
  68. path = devicePath;
  69. if (isRead) readData();//开启识别
  70. } catch (Throwable tr) {
  71. close();
  72. isSucc = false;
  73. } finally {
  74. return isSucc;
  75. }
  76. }
  77. // 读取数据
  78. private void readData() {
  79. if (readTask != null) {
  80. readTask.cancel(true);
  81. try {
  82. Thread.sleep(160);
  83. } catch (InterruptedException e) {
  84. e.printStackTrace();
  85. }
  86. //此处睡眠:当取消任务时 线程池已经执行任务,无法取消,所以等待线程池的任务执行完毕
  87. readTask = null;
  88. }
  89. readTask = SerialManage
  90. .getInstance()
  91. .getScheduledExecutor()//获取线程池
  92. .scheduleAtFixedRate(this, 0, 150, TimeUnit.MILLISECONDS);//执行一个循环任务
  93. }
  94. @Override//每隔 150 毫秒会触发一次run
  95. public void run() {
  96. if (Thread.currentThread().isInterrupted()) return;
  97. try {
  98. int available = mBuffInputStream.available();
  99. if (available == 0) return;
  100. byte[] received = new byte[1024];
  101. int size = mBuffInputStream.read(received);//读取以下串口是否有新的数据
  102. if (size > 0 && serialInter != null) serialInter.readData(path, received, size);
  103. } catch (IOException e) {
  104. Log.e(TAG, "串口读取数据异常:" + e.toString());
  105. }
  106. }
  107. /**
  108. * 关闭串口
  109. */
  110. public void close(){
  111. try{
  112. if (mInputStream != null) mInputStream.close();
  113. }catch (Exception e){
  114. Log.e(TAG,"串口输入流对象关闭异常:" +e.toString());
  115. }
  116. try{
  117. if (mOutputStream != null) mOutputStream.close();
  118. }catch (Exception e){
  119. Log.e(TAG,"串口输出流对象关闭异常:" +e.toString());
  120. }
  121. try{
  122. if (mSerialPort != null) mSerialPort.close();
  123. mSerialPort = null;
  124. }catch (Exception e){
  125. Log.e(TAG,"串口对象关闭异常:" +e.toString());
  126. }
  127. }
  128. /**
  129. * 向串口发送指令
  130. */
  131. public void send(final String msg) {
  132. byte[] bytes = hexStr2bytes(msg);//字符转成byte数组
  133. try {
  134. mOutputStream.write(bytes);//通过输出流写入数据
  135. } catch (Exception e) {
  136. e.printStackTrace();
  137. }
  138. }
  139. /**
  140. * 把十六进制表示的字节数组字符串,转换成十六进制字节数组
  141. *
  142. * @param
  143. * @return byte[]
  144. */
  145. private byte[] hexStr2bytes(String hex) {
  146. int len = (hex.length() / 2);
  147. byte[] result = new byte[len];
  148. char[] achar = hex.toUpperCase().toCharArray();
  149. for (int i = 0; i < len; i++) {
  150. int pos = i * 2;
  151. result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1]));
  152. }
  153. return result;
  154. }
  155. /**
  156. * 把16进制字符[0123456789abcde](含大小写)转成字节
  157. * @param c
  158. * @return
  159. */
  160. private static int hexChar2byte(char c) {
  161. switch (c) {
  162. case '0':
  163. return 0;
  164. case '1':
  165. return 1;
  166. case '2':
  167. return 2;
  168. case '3':
  169. return 3;
  170. case '4':
  171. return 4;
  172. case '5':
  173. return 5;
  174. case '6':
  175. return 6;
  176. case '7':
  177. return 7;
  178. case '8':
  179. return 8;
  180. case '9':
  181. return 9;
  182. case 'a':
  183. case 'A':
  184. return 10;
  185. case 'b':
  186. case 'B':
  187. return 11;
  188. case 'c':
  189. case 'C':
  190. return 12;
  191. case 'd':
  192. case 'D':
  193. return 13;
  194. case 'e':
  195. case 'E':
  196. return 14;
  197. case 'f':
  198. case 'F':
  199. return 15;
  200. default:
  201. return -1;
  202. }
  203. }
  204. }

2.)串口回调SerialInter;简单概括一下这个类,就是将SerialHandle类中产生的结果,返回给上一层的业务代码,解偶合

  1. package com.chj233.serialmode.serialUtil;
  2. /**
  3. * 串口回调
  4. */
  5. public interface SerialInter {
  6. /**
  7. * 连接结果回调
  8. * @param path 串口地址(当有多个串口需要统一处理时,可以用地址来区分)
  9. * @param isSucc 连接是否成功
  10. */
  11. void connectMsg(String path,boolean isSucc);
  12. /**
  13. * 读取到的数据回调
  14. * @param path 串口地址(当有多个串口需要统一处理时,可以用地址来区分)
  15. * @param bytes 读取到的数据
  16. * @param size 数据长度
  17. */
  18. void readData(String path,byte[] bytes,int size);
  19. }

 3.)串口统一管理SerialManage;简单概括一下这个类,用于管理串口的连接以及发送等功能,尤其是发送指令,极短时间内发送多个指令(例如:1毫秒内发送10个指令),多个指令之间会相互干扰。可能执行了第一个指令,可能一个都没执行。这个类不是必须的,如果有更好的方法可以自己定义。

  1. package com.chj233.serialmode.serialUtil;
  2. import java.util.Queue;
  3. import java.util.concurrent.ConcurrentLinkedQueue;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.ScheduledExecutorService;
  6. import java.util.concurrent.ScheduledFuture;
  7. import java.util.concurrent.TimeUnit;
  8. /**
  9. * 串口管理类
  10. */
  11. public class SerialManage {
  12. private static SerialManage instance;
  13. private ScheduledExecutorService scheduledExecutor;//线程池 同一管理保证只有一个
  14. private SerialHandle serialHandle;//串口连接 发送 读取处理对象
  15. private Queue<String> queueMsg = new ConcurrentLinkedQueue<String>();//线程安全到队列
  16. private ScheduledFuture sendStrTask;//循环发送任务
  17. private boolean isConnect = false;//串口是否连接
  18. private SerialManage() {
  19. scheduledExecutor = Executors.newScheduledThreadPool(8);//初始化8个线程
  20. }
  21. public static SerialManage getInstance() {
  22. if (instance == null) {
  23. synchronized (SerialManage.class) {
  24. if (instance == null) {
  25. instance = new SerialManage();
  26. }
  27. }
  28. }
  29. return instance;
  30. }
  31. /**
  32. * 获取线程池
  33. *
  34. * @return
  35. */
  36. public ScheduledExecutorService getScheduledExecutor() {
  37. return scheduledExecutor;
  38. }
  39. /**
  40. * 串口初始化
  41. *
  42. * @param serialInter
  43. */
  44. public void init(SerialInter serialInter) {
  45. if (serialHandle == null) {
  46. serialHandle = new SerialHandle();
  47. startSendTask();
  48. }
  49. serialHandle.addSerialInter(serialInter);
  50. }
  51. /**
  52. * 打开串口
  53. */
  54. public void open() {
  55. isConnect = serialHandle.open("/dev/ttyS1", 9600, true);//设置地址,波特率,开启读取串口数据
  56. }
  57. /**
  58. * 发送指令
  59. *
  60. * @param msg
  61. */
  62. public void send(String msg) {
  63. /*
  64. 此处没有直接使用 serialHandle.send(msg); 方法去发送指令
  65. 因为 某些硬件在极短时间内只能响应一个指令,232通讯一次发送多个指令会有物理干扰,
  66. 让硬件接收到指令不准确;所以 此处将指令添加到队列中,排队执行,确保每个指令一定执行.
  67. 若不相信可以试试用serialHandle.send(msg)方法循环发送10个不同的指令,看看10个指令
  68. 的执行结果。
  69. */
  70. queueMsg.offer(msg);//向队列添加指令
  71. }
  72. /**
  73. * 关闭串口
  74. */
  75. public void colse() {
  76. serialHandle.close();//关闭串口
  77. }
  78. //启动发送发送任务
  79. private void startSendTask() {
  80. cancelSendTask();//先检查是否已经启动了任务 ? 若有则取消
  81. //每隔100毫秒检查一次 队列中是否有新的指令需要执行
  82. sendStrTask = scheduledExecutor.scheduleAtFixedRate(new Runnable() {
  83. @Override
  84. public void run() {
  85. if (!isConnect) return;//串口未连接 退出
  86. if (serialHandle == null) return;//串口未初始化 退出
  87. String msg = queueMsg.poll();//取出指令
  88. if (msg == null || "".equals(msg)) return;//无效指令 退出
  89. serialHandle.send(msg);//发送指令
  90. }
  91. }, 0, 100, TimeUnit.MILLISECONDS);
  92. }
  93. //取消发送任务
  94. private void cancelSendTask() {
  95. if (sendStrTask == null) return;
  96. sendStrTask.cancel(true);
  97. try {
  98. Thread.sleep(100);
  99. } catch (InterruptedException e) {
  100. e.printStackTrace();
  101. }
  102. sendStrTask = null;
  103. }
  104. }

4.使用串口

  1. package com.chj233.serialmode;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.View;
  6. import com.chj233.serialmode.serialUtil.SerialInter;
  7. import com.chj233.serialmode.serialUtil.SerialManage;
  8. public class MainActivity extends AppCompatActivity implements SerialInter {
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_main);
  13. SerialManage.getInstance().init(this);//串口初始化
  14. SerialManage.getInstance().open();//打开串口
  15. findViewById(R.id.send_but).setOnClickListener(new View.OnClickListener() {
  16. @Override
  17. public void onClick(View view) {
  18. SerialManage.getInstance().send("Z");//发送指令 Z
  19. }
  20. });
  21. }
  22. @Override
  23. public void connectMsg(String path, boolean isSucc) {
  24. String msg = isSucc ? "成功" : "失败";
  25. Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);
  26. }
  27. @Override//若在串口开启的方法中 传入false 此处不会返回数据
  28. public void readData(String path, byte[] bytes, int size) {
  29. // Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);
  30. }
  31. }

5.总结

串口通讯对于Android开发者来说,仅需关注如何连接、操作(发送指令)、读取数据;无论是232、485还是422,对于开发者来说连接、操作、读取代码都是一样的

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多