5

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

 2 years ago
source link: https://www.51cto.com/article/715693.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近时间做的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;
        }
    }
}

3.串口回调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);
 
}

4.串口统一管理SerialManage;

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.使用串口

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);
    }
}

6.串口常见进制与进制工具类

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

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开发者来说,仅需关注如何连接、操作(发送指令)、读取数据。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK