android开发板串口通讯-深入浅出的分析和使用详解

引言

最近时间做的android开发板上控制电机,都是通过串口进行对接和通讯。对串口接触下来,发现真的可以做很多有意思的东西,很多硬件设备都可以通过串口进行通讯,比如:打印机、ATM吐卡机、IC/ID卡读卡等,以及物联网相关的设备;

一、串口简介

串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口;

串行接口(SerialInterface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢;

1、串口-波特率

串口传输速率,用来衡量数据传输的快慢,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。波特率与距离成反比,波特率越大传输距离相应的就越短;

2、串口-数据位

这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息;

3、串口-停止位

用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢;

4、串口-校验位

在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位;

5、串口地址

不同操作系统的串口地址,Android是基于Linux的所以一般情况下使用Android系统的设备串口地址为/dev/ttyS0;

/dev目录下的串口都有哪些

  • /dev的串口包括:虚拟串口,真实串口,USB转串口
  • 真实串口:/dev/tty0..tty1这个一般为机器自带COM口
  • 虚拟串口:/dev/ttyS1…ttyS2…ttyS3…均为虚拟console,同样可以作为输入输出口
  • USB转串口:/dev/tty/USB0

二、Android串口实现

串口通讯和服务器之间的通讯是一样的,都是传一些参数过去,然后返回一些数据回来;

不过串口通讯管这些参数叫做指令,而这些指令是由硬件的通讯协议而定的,通讯协议不同,指令自然也不同;

设备文档上都有介绍相应的协议参数,比如电机角度、电机速度、电机复位、当前电机的角度等;

在Android上使用串口比较快速的方式就是直接套用google官方的串口demo代码(android-serialport-api),基本上能够应付很多在Android设备使用串口的场景;

这次介绍下,在项目中用到的开源库com.github.licheedev:Android-SerialPort-API:2.0.0

1、build.gradle中的dependencies中添加以下依赖:

dependencies {    //串口    implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0'}Project 中的 build.gradle 中的 allprojects 中添加以下 maven仓库 allprojects {    repositories {        maven { url "https://jitpack.io" }//maven仓库    }}

2、串口处理封装

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; /** * 串口实处理类-jason */public class SerialHandleUtils implements Runnable {     private static final String TAG = "串口处理类";    private String path = "";//串口地址    private SerialPort mSerialPort;//串口对象    private InputStream mInputStream;//串口的输入流对象    private BufferedInputStream mBuffInputStream;//用于监听硬件返回的信息    private OutputStream mOutputStream;//串口的输出流对象 用于发送指令    private SerialMonitor serialInter;//串口回调接口    private ScheduledFuture readTask;//串口读取任务     /**     * 添加串口回调     *     * @param serialInter     */    public void addSerialInter(SerialMonitor 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);//字符转成byte数组        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.串口回调SerialMonitor;

简单概括一下这个类,就是将SerialHandle类中产生的结果,返回给上一层的业务代码

/** * 串口回调 */public interface SerialMonitor {     /**     * 连接结果回调     * @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;

/** * 串口管理类 */public class SerialManage {     private static SerialManage instance;    private SerialHandle serialHandle;//串口连接 发送 读取处理对象    private boolean isConnect = false;//串口是否连接     private SerialManage() {    }     public static SerialManage getInstance() {        if (instance == null) {            synchronized (SerialManage.class) {                if (instance == null) {                    instance = new SerialManage();                }            }        }        return instance;    }     /**     * 串口初始化     *     * @param      */    public void init(SerialMonitor serialInter) {        if (serialHandle == null) {            serialHandle = new SerialHandle();        }        serialHandle.addSerialInter(serialInter);     }     /**     * 打开串口     */    public void open() {        isConnect = serialHandle.open("/dev/ttyS1", 9600, true);//设置地址,波特率,开启读取串口数据    }     /**     * 发送指令     *     * @param msg     */    public void send(String msg) {           serialHandle.send(msg)    }     /**     * 关闭串口     */    public void colse() {        serialHandle.close();//关闭串口    }}

4.使用串口

import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View; 

public class MainActivity extends AppCompatActivity implements SerialMonitor { @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//若在串口开启的方法中 传入false 此处不会返回数据 public void readData(String path, byte[] bytes, int size) {// Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes); }}

5、串口常见进制与进制工具类

开发中比较常见进制与进制,进制与字节间的转换,比如:十六进制转十进制,字节数组转十六进制字符串等

public class DataConversionUtils {

/** * 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数 * @param num * @return */ public static int isOdd(int num) { return num & 0x1; }

/** * 将int转成byte * @param number * @return */ public static byte intToByte(int number){ return hexToByte(intToHex(number)); }

/** * 将int转成hex字符串 * @param number * @return */ public static String intToHex(int number){ String st = Integer.toHexString(number).toUpperCase(); return String.format("%2s",st).replaceAll(" ","0"); }

/** * 字节转十进制 * @param b * @return */ public static int byteToDec(byte b){ String s = byteToHex(b); return (int) hexToDec(s); }

/** * 字节数组转十进制 * @param bytes * @return */ public static int bytesToDec(byte[] bytes){ String s = encodeHexString(bytes); return (int) hexToDec(s); }

/** * Hex字符串转int * * @param inHex * @return */ public static int hexToInt(String inHex) { return Integer.parseInt(inHex, 16); }

/** * 字节转十六进制字符串 * @param num * @return */ public static String byteToHex(byte num) { char[] hexDigits = new char[2]; hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16); hexDigits[1] = Character.forDigit((num & 0xF), 16); return new String(hexDigits).toUpperCase(); }

/** * 十六进制转byte字节 * @param hexString * @return */ public static byte hexToByte(String hexString) { int firstDigit = toDigit(hexString.charAt(0)); int secondDigit = toDigit(hexString.charAt(1)); return (byte) ((firstDigit << 4) + secondDigit); }

private static int toDigit(char hexChar) { int digit = Character.digit(hexChar, 16); if(digit == -1) { throw new IllegalArgumentException( "Invalid Hexadecimal Character: "+ hexChar); } return digit; }

/** * 字节数组转十六进制 * @param byteArray * @return */ public static String encodeHexString(byte[] byteArray) { StringBuffer hexStringBuffer = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { hexStringBuffer.append(byteToHex(byteArray[i])); } return hexStringBuffer.toString().toUpperCase(); }

/** * 十六进制转字节数组 * @param hexString * @return */ public static byte[] decodeHexString(String hexString) { if (hexString.length() % 2 == 1) { throw new IllegalArgumentException( "Invalid hexadecimal String supplied."); } byte[] bytes = new byte[hexString.length() / 2]; for (int i = 0; i < hexString.length(); i += 2) { bytes[i / 2] = hexToByte(hexString.substring(i, i + 2)); } return bytes; }

/** * 十进制转十六进制 * @param dec * @return */ public static String decToHex(int dec){ String hex = Integer.toHexString(dec); if (hex.length() == 1) { hex = '0' + hex; } return hex.toLowerCase(); }

/** * 十六进制转十进制 * @param hex * @return */ public static long hexToDec(String hex){ return Long.parseLong(hex, 16); }

/** * 十六进制转十进制,并对卡号补位 */ public static String setCardNum(String cardNun){ String cardNo1= cardNun; String cardNo=null; if(cardNo1!=null){ Long cardNo2=Long.parseLong(cardNo1,16); //cardNo=String.format("%015d", cardNo2); cardNo = String.valueOf(cardNo2); } return cardNo; }}
图片

总结

串口通讯使用到进程、Linux指令、JNI…,但抛开现象看本质,最终目标还是获得一个输入输出流去进行读写操作;

串口通讯对于Android开发者来说,仅需关注如何连接、操作(发送指令)、读取数据;

2022-08-05 11:00 发表于浙江

阅读原文

简介:一个有10多年经验开发的android、java、前端等语言的老程序员,在这里一起聊聊技术,一起聊聊生活、一起聊聊中年危机的生存之道,一起进步一起加油,感兴趣的欢迎订阅;不定时的更新。欢迎关注微信公众号:Android开发编程
(0)
打赏 喜欢就点个赞支持下吧 喜欢就点个赞支持下吧

声明:本文来自“Android开发编程”,分享链接:https://www.zyxiao.com/p/309919    侵权投诉

网站客服
网站客服
内容投稿 侵权处理
分享本页
返回顶部