> 文档中心 > Android串口通讯SerialPort(使用篇)

Android串口通讯SerialPort(使用篇)


 

1.什么是串口

在不会使用串口通讯之前,暂且可以把它理解为“一个可通讯的口”;使用篇不深入探讨理论及原理。

2.添加依赖

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

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

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

allprojects {    repositories { maven { url "https://jitpack.io" }//maven仓库    }}

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

dependencyResolutionManagement {    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)    repositories { google() mavenCentral() jcenter() // Warning: this repository is going to shut down soon maven { url "https://jitpack.io" }//maven仓库    }}

3.编写串口处理类

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

package com.chj233.serialmode.serialUtil;import android.serialport.SerialPort;import android.util.Log;import java.io.BufferedInputStream;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.concurrent.ScheduledFuture;import java.util.concurrent.TimeUnit;/** * 串口实处理类 */public class SerialHandle implements Runnable {    private static final String TAG = "串口处理类";    private String path = "";//串口地址    private SerialPort mSerialPort;//串口对象    private InputStream mInputStream;//串口的输入流对象    private BufferedInputStream mBuffInputStream;//用于监听硬件返回的信息    private OutputStream mOutputStream;//串口的输出流对象 用于发送指令    private SerialInter serialInter;//串口回调接口    private ScheduledFuture readTask;//串口读取任务    /**     * 添加串口回调     *     * @param serialInter     */    public void addSerialInter(SerialInter serialInter) { this.serialInter = serialInter;    }    /**     * 打开串口     *     * @param devicePath 串口地址(根据平板的说明说填写)     * @param baudrate   波特率(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)     * @param isRead     是否持续监听串口返回的数据     * @return 是否打开成功     */    public boolean open(String devicePath, int baudrate, boolean isRead) { return open(devicePath, baudrate, 7, 1, 2, isRead);    }    /**     * 打开串口     *     * @param devicePath 串口地址(根据平板的说明说填写)     * @param baudrate   波特率(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)     * @param dataBits   数据位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)     * @param stopBits   停止位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)     * @param parity     校验位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)     * @param isRead     是否持续监听串口返回的数据     * @return 是否打开成功     */    public boolean open(String devicePath, int baudrate, int dataBits, int stopBits, int parity, boolean isRead) { boolean isSucc = false; try {     if (mSerialPort != null) close();     File device = new File(devicePath);     mSerialPort = SerialPort // 串口对象      .newBuilder(device, baudrate) // 串口地址地址,波特率      .dataBits(dataBits) // 数据位,默认8;可选值为5~8      .stopBits(stopBits) // 停止位,默认1;1:1位停止位;2:2位停止位      .parity(parity) // 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)      .build(); // 打开串口并返回     mInputStream = mSerialPort.getInputStream();     mBuffInputStream = new BufferedInputStream(mInputStream);     mOutputStream = mSerialPort.getOutputStream();     isSucc = true;     path = devicePath;     if (isRead) readData();//开启识别 } catch (Throwable tr) {     close();     isSucc = false; } finally {     return isSucc; }    }    // 读取数据    private void readData() { if (readTask != null) {     readTask.cancel(true);     try {  Thread.sleep(160);     } catch (InterruptedException e) {  e.printStackTrace();     }     //此处睡眠:当取消任务时 线程池已经执行任务,无法取消,所以等待线程池的任务执行完毕     readTask = null; } readTask = SerialManage  .getInstance()  .getScheduledExecutor()//获取线程池  .scheduleAtFixedRate(this, 0, 150, TimeUnit.MILLISECONDS);//执行一个循环任务    }    @Override//每隔 150 毫秒会触发一次run    public void run() { if (Thread.currentThread().isInterrupted()) return; try {     int available = mBuffInputStream.available();     if (available == 0) return;     byte[] received = new byte[1024];     int size = mBuffInputStream.read(received);//读取以下串口是否有新的数据     if (size > 0 && serialInter != null) serialInter.readData(path, received, size); } catch (IOException e) {     Log.e(TAG, "串口读取数据异常:" + e.toString()); }    }    /**     * 关闭串口     */    public void close(){ try{     if (mInputStream != null) mInputStream.close(); }catch (Exception e){     Log.e(TAG,"串口输入流对象关闭异常:" +e.toString()); } try{     if (mOutputStream != null) mOutputStream.close(); }catch (Exception e){     Log.e(TAG,"串口输出流对象关闭异常:" +e.toString()); } try{     if (mSerialPort != null) mSerialPort.close();     mSerialPort = null; }catch (Exception e){     Log.e(TAG,"串口对象关闭异常:" +e.toString()); }    }    /**     * 向串口发送指令     */    public void send(final String msg) { byte[] bytes = hexStr2bytes(msg); try {     mOutputStream.write(bytes); } catch (Exception e) {     e.printStackTrace(); }    }    /**     * 把十六进制表示的字节数组字符串,转换成十六进制字节数组     *     * @param     * @return byte[]     */    private byte[] hexStr2bytes(String hex) { int len = (hex.length() / 2); byte[] result = new byte[len]; char[] achar = hex.toUpperCase().toCharArray(); for (int i = 0; i < len; i++) {     int pos = i * 2;     result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1])); } return result;    }    /**     * 把16进制字符[0123456789abcde](含大小写)转成字节     * @param c     * @return     */    private static int hexChar2byte(char c) { switch (c) {     case '0':  return 0;     case '1':  return 1;     case '2':  return 2;     case '3':  return 3;     case '4':  return 4;     case '5':  return 5;     case '6':  return 6;     case '7':  return 7;     case '8':  return 8;     case '9':  return 9;     case 'a':     case 'A':  return 10;     case 'b':     case 'B':  return 11;     case 'c':     case 'C':  return 12;     case 'd':     case 'D':  return 13;     case 'e':     case 'E':  return 14;     case 'f':     case 'F':  return 15;     default:  return -1; }    }}

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

package com.chj233.serialmode.serialUtil;/** * 串口回调 */public interface SerialInter {    /**     * 连接结果回调     * @param path 串口地址(当有多个串口需要统一处理时,可以用地址来区分)     * @param isSucc 连接是否成功     */    void connectMsg(String path,boolean isSucc);    /**     * 读取到的数据回调     * @param path 串口地址(当有多个串口需要统一处理时,可以用地址来区分)     * @param bytes 读取到的数据     * @param size 数据长度     */    void readData(String path,byte[] bytes,int size);}

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

package com.chj233.serialmode.serialUtil;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledFuture;import java.util.concurrent.TimeUnit;/** * 串口管理类 */public class SerialManage {    private static SerialManage instance;    private ScheduledExecutorService scheduledExecutor;//线程池 同一管理保证只有一个    private SerialHandle serialHandle;//串口连接 发送 读取处理对象    private Queue queueMsg = new ConcurrentLinkedQueue();//线程安全到队列    private ScheduledFuture sendStrTask;//循环发送任务    private boolean isConnect = false;    private SerialManage() { scheduledExecutor = Executors.newScheduledThreadPool(8);    }    public static SerialManage getInstance() { if (instance == null) {     synchronized (SerialManage.class) {  if (instance == null) {      instance = new SerialManage();  }     } } return instance;    }    /**     * 获取线程池     *     * @return     */    public ScheduledExecutorService getScheduledExecutor() { return scheduledExecutor;    }    /**     * 串口初始化     *     * @param serialInter     */    public void init(SerialInter serialInter) { if (serialHandle == null) {     serialHandle = new SerialHandle();     startSendTask(); } serialHandle.addSerialInter(serialInter);    }    /**     * 打开串口     */    public void open() { isConnect = serialHandle.open("ttyS1", 9600, true);    }    /**     * 发送指令     *     * @param msg     */    public void send(String msg) { /*  此处没有直接使用 serialHandle.send(msg); 方法去发送指令  因为 某些硬件(PLC)在极短时间内只能响应一个指令(232通讯一次发送多个指令会有物理干扰,让硬件接收到指令不准确);  所以 此处将指令添加到队列中,排队执行,确保每个指令一定执行  */ queueMsg.offer(msg);//向队列添加指令    }    /**     * 关闭串口     */    public void colse() { serialHandle.close();    }    //启动发送发送任务    private void startSendTask() { cancelSendTask();//先检查是否已经启动了任务 ? 若有则取消 //每隔100毫秒检查一次 队列中是否有新的指令需要执行 sendStrTask = scheduledExecutor.scheduleAtFixedRate(new Runnable() {     @Override     public void run() {  if (!isConnect) return;//串口未连接 退出  if (serialHandle == null) return;//串口未初始化 退出  String msg = queueMsg.poll();  if (msg == null || "".equals(msg)) return;//无效指令 退出  serialHandle.send(msg);//发送指令     } }, 0, 100, TimeUnit.MILLISECONDS);    }    //取消发送任务    private void cancelSendTask() { if (sendStrTask == null) return; sendStrTask.cancel(true); try {     Thread.sleep(100); } catch (InterruptedException e) {     e.printStackTrace(); } sendStrTask = null;    }}

4.使用串口

package com.chj233.serialmode;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import com.chj233.serialmode.serialUtil.SerialInter;import com.chj233.serialmode.serialUtil.SerialManage;public class MainActivity extends AppCompatActivity implements SerialInter {    @Override    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SerialManage.getInstance().init(this);//串口初始化 SerialManage.getInstance().open();//打开串口 findViewById(R.id.send_but).setOnClickListener(new View.OnClickListener() {     @Override     public void onClick(View view) {  SerialManage.getInstance().send("Z");//发送指令 Z      } });    }    @Override    public void connectMsg(String path, boolean isSucc) { String msg = isSucc ? "成功" : "失败"; Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);    }    @Override    public void readData(String path, byte[] bytes, int size) {// Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);    }}

5.总结

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

四四频道