Rs232,Rs485-java硬件串口通讯 - 知乎 - 入门参考

一、背景描述

公司最近在做一款车间产线质检工具桌面软件,其中客户提出不合格情况达到一定的比例后,车间操作台红蓝报警灯 响铃并告警,该款工具底层使用java 语言开发。所以开始java 打通硬件的通讯的调研。

以下是客户的硬件报文通讯协议

二、连接配置过程

经过一段时间的调研和研究,对串口通讯有了基础的认知,串口通讯一般依赖于物理传输介质,通过该种方式传输信号。我使用的是RS232-USB转接线母头,USB口端接入电脑,母头端接入硬件电路板公头

公头是硬件电路板预留三条线出来,和RS232母头连接,

连接后,在电脑端会形成新的串口,串口号需要通过的串口调试工具xcom2.6助手来获取。打开这个助手会默认显示内置的串口号,接入硬件电路板 再查看串口号是否有变化

三、硬件编码过程

串口通讯是很老的技术,对于java而言 有不少依赖包 ,主要是rxtx相关系列的,在开发过程中,有不少老的博文,引入的依赖的包 至少三个并且,操作系统不一样,还需要额外的配置,比较复杂。经过调研后总结了一个比较轻量化的接入方式。

1.引入POM

        <dependency>
            <groupId>com.github.purejavacomm</groupId>
            <artifactId>purejavacomm</artifactId>
            <version>1.0.2.RELEASE</version>
        </dependency>

2.通讯模块代码

package com.taopu.smart.serialcom;


import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.StringUtil;
import org.springframework.util.StringUtils;
import purejavacomm.CommPortIdentifier;
import purejavacomm.SerialPort;
import purejavacomm.SerialPortEvent;
import purejavacomm.SerialPortEventListener;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;

/**
 * @author:ms.y
 * @create 2019/1/24-8:48
 *  * 串口监听器
 *  * // FC00 6A 0000803F 5ED2 16
 *  * FC 头部
 *  * 00 地址
 *  * 6A表示 功能类型

 *
 *  https://www.23bei.com/tool/59.html FC005A0000803F 经过CRC校验 产出CRC0 和 CRC1
 *
 *  拼接CRC: FC00 6A 0000803F 5ED2
 *  拼接尾部 16
 *
 *
 *  预警硬件通信协议文档
 * 例:打开预警:FC006A000000103ECA16
 *
 * -FC:前缀
 * -00:地址
 * -6A:功能项[亮光+声音]
 * -00000010 打开
 * -00000000 关闭
 * -00000001-0000000A 范围声音分为10个档位值
 * -3ECA 校验位CRC0CRC1
 * -16 拼接后缀
 */
@Slf4j
public class CommUtil implements SerialPortEventListener {

    public static final String PREFIX_SIX_SPLIT="FC,00,6A,00,00,00";


    public static  String PORT_NAME = "";
    private static final int BIT_RATE = 9600;
    public static final int DATA_BITS = SerialPort.DATABITS_8;
    public static final int STOP_BIT = SerialPort.STOPBITS_1;
    public static final int PARITY_BIT = SerialPort.PARITY_NONE;

    private static SerialPort serialPort;
    private static InputStream in;
    private static OutputStream out;
    private static CommUtil commUtil;

    private CommUtil() {
    }

    public static synchronized CommUtil getInstance() {
        if (commUtil == null) {
            commUtil = new CommUtil();
            commUtil.init();
        }
        return commUtil;
    }
    public ArrayList<String> findPort() {
        //获得当前所有可用串口
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();

        ArrayList<String> portNameList = new ArrayList<>();
        //将可用串口名添加到List并返回该List
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();
            portNameList.add(portName);
        }
        return portNameList;
    }
    public void init() {
        try {
            if(StringUtils.isEmpty(PORT_NAME)){
                log.error("init PORT_NAME is null");
            }else {
                log.info("init PORT_NAME is :{}",PORT_NAME);
            }
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(PORT_NAME);
            if (portIdentifier.isCurrentlyOwned()) {
                log.error("Port is currently in use");
            } else if (portIdentifier.getPortType() == 1) {
                serialPort = (SerialPort) portIdentifier.open(PORT_NAME, 1000);
                serialPort.setSerialPortParams(BIT_RATE, DATA_BITS, STOP_BIT, PARITY_BIT);

                in = serialPort.getInputStream();
                out = serialPort.getOutputStream();

                serialPort.addEventListener(this);
                serialPort.notifyOnDataAvailable(true);
            } else {
                log.error("Error: Only serial ports are handled by this example.");
            }
        } catch (Exception e) {
            log.error("init failed",e);
        }
    }

    public void send(String message) {
        try {
            log.info("sendmsg:{}",message);
            byte[] bytes = hexStrToByteArray(message);
            out.write(bytes);
            Thread.sleep(1000);
        } catch (Exception e) {
            log.error("send failed",e);
        }
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.DATA_AVAILABLE:
                receive();
                break;
        }
    }

    public String receive() {
        byte[] buffer = new byte[128];
        int data;
        String result = null;
        try {
            int len = 0;
            while ((data = in.read()) > -1) {
                buffer[len++] = (byte) data;
            }
            byte[] copyValue = new byte[len];
            System.arraycopy(buffer, 0, copyValue, 0, len);
            result = ByteArrayToString(copyValue);
        } catch (Exception e) {
            log.error("receive",e);
        }
        return result;
    }

    public void close() {
        try {
            in.close();
            out.close();
            serialPort.notifyOnDataAvailable(false);
            serialPort.removeEventListener();
            serialPort.close();
        } catch (Exception e) {
            log.error("close",e);
        }
    }

    //16进制转byte数组
    public static byte[] hexStrToByteArray(String str) {
        if (str == null) {
            return null;
        }
        if (str.length() == 0) {
            return new byte[0];
        }
        byte[] byteArray = new byte[str.length() / 2];
        for (int i = 0; i < byteArray.length; i++) {
            String subStr = str.substring(2 * i, 2 * i + 2);
            byteArray[i] = ((byte) Integer.parseInt(subStr, 16));
        }
        return byteArray;
    }





    public static String ByteArrayToString(byte[] by) {
        String str = "";
        for (int i = 0; i < by.length; i++) {
            String hex = Integer.toHexString(by[i] & 0xFF);
            if (hex.length() == 1) {
                hex = "0" + hex;
            }
            str += hex.toUpperCase();
        }
        return str;
    }



    public static void main(String[] args) {
//        String dec2Hex = dec2Hex(9);
//        CommUtil commUtil = CommUtil.getInstance();
//        if (Objects.isNull(commUtil)) {
//            log.error("commUtil is null ");
//            return;
//        }
//        commUtil.send("FC005A0000803F5ED216");
//
//        log.error("commUtil send success ");
//        commUtil.close();
//        System.err.println(dec2Hex);

        byte[] fc005A0000803FS = hexStrToByteArray("FC 00 5A 00 00 00 00 7F 02 16");
        System.err.println(fc005A0000803FS);

        int[] data = new int[7];
        data[0]=252;
        data[1]=0;
        data[2]=90;
        data[3]=0;
        data[4]=0;
        data[5]=0;
        data[6]=9;

        System.err.println(data);

    }
}

3.CRC16 校验代码

package com.taopu.smart.serialcom;

/**
 * 基于Modbus CRC16的校验算法工具类
 */
public class Crc16Util {

    /**
     * 获取源数据和验证码的组合byte数组
     * @param strings 可变长度的十六进制字符串
     * @return
     */
    public static byte[] getData(String...strings) {
        byte[] data = new byte[]{};
        for (int i = 0; i<strings.length;i++) {
            int x = Integer.parseInt(strings[i], 16);
            byte n = (byte)x;
            byte[] buffer = new byte[data.length+1];
            byte[] aa = {n};
            System.arraycopy( data,0,buffer,0,data.length);
            System.arraycopy( aa,0,buffer,data.length,aa.length);
            data = buffer;
        }
        return getData(data);
    }
    /**
     * 获取源数据和验证码的组合byte数组
     * @param aa 字节数组
     * @return
     */
    private static byte[] getData(byte[] aa) {
        byte[] bb = getCrc16(aa);
        byte[] cc = new byte[aa.length+bb.length];
        System.arraycopy(aa,0,cc,0,aa.length);
        System.arraycopy(bb,0,cc,aa.length,bb.length);
        return cc;
    }
    /**
     * 获取验证码byte数组,基于Modbus CRC16的校验算法
     */
    private static byte[] getCrc16(byte[] arr_buff) {
        int len = arr_buff.length;

        // 预置 1 个 16 位的寄存器为十六进制FFFF, 称此寄存器为 CRC寄存器。
        int crc = 0xFFFF;
        int i, j;
        for (i = 0; i < len; i++) {
            // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
            crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
            for (j = 0; j < 8; j++) {
                // 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位
                if ((crc & 0x0001) > 0) {
                    // 如果移出位为 1, CRC寄存器与多项式A001进行异或
                    crc = crc >> 1;
                    crc = crc ^ 0xA001;
                } else
                    // 如果移出位为 0,再次右移一位
                    crc = crc >> 1;
            }
        }
        return intToBytes(crc);
    }
    /**
     * 将int转换成byte数组,低位在前,高位在后
     * 改变高低位顺序只需调换数组序号
     */
    private static byte[] intToBytes(int value)  {
        byte[] src = new byte[2];
        src[1] =  (byte) ((value>>8) & 0xFF);
        src[0] =  (byte) (value & 0xFF);
        return src;
    }
    /**
     * 将字节数组转换成十六进制字符串
     */
    public static String byteTo16String(byte[] data) {
        StringBuffer buffer = new StringBuffer();
        for (byte b : data) {
            buffer.append(byteTo16String(b));
        }
        return buffer.toString();
    }
    /**
     * 将字节转换成十六进制字符串
     * int转byte对照表
     * [128,255],0,[1,128)
     * [-128,-1],0,[1,128)
     */
    public static String byteTo16String(byte b) {
        StringBuffer buffer = new StringBuffer();
        int aa = (int)b;
        if (aa<0) {
            buffer.append(Integer.toString(aa+256, 16)+" ");
        }else if (aa==0) {
            buffer.append("00 ");
        }else if (aa>0 && aa<=15) {
            buffer.append("0"+Integer.toString(aa, 16)+" ");
        }else if (aa>15) {
            buffer.append(Integer.toString(aa, 16)+" ");
        }
        return buffer.toString();
    }



    public static void main(String[] args) {


            // 判断

        byte[] dd = Crc16Util.getData("FC", "00","6A","00","00","00","01");


//        byte[] dd = Crc16Util.getData("FC", "00","6A","00","00","00","01");

        String str = Crc16Util.byteTo16String(dd).toUpperCase();


        System.out.println(str);
    }

}

4 业务调用代码-增加crc16校验位

public void giveAlarm(TcpParams tcpParams){
        //获取当前规则
        List<FormulaItem> formulaItems=formulaService.getCurrentFormulaItem();
        if(formulaItems==null||formulaItems.isEmpty()){
            return;
        }
        Map<Byte,Integer> formulaItemMap=formulaItems.stream().collect(Collectors.toMap(FormulaItem::getType,FormulaItem::getAmount));
        //获取对应缺陷数量
        MasterRoll masterRoll = masterRollService.currentMasterRoll();
        //获取告警是否开启
        String status=redisTemplate.opsForValue().get(ALARM_STATUS).toString();
        if(status.equals("1")){
            return;
        }
        List<TypeCountVO> typeCountVOS=defectRecordMapperEx.statistic(masterRoll.getId(),null,Convert.toByte(tcpParams.getDetail().getDefectType()));
        if(!typeCountVOS.isEmpty()){
            //缺陷数量超过规定
            if(typeCountVOS.get(0).getAmount()>formulaItemMap.get(Convert.toByte(tcpParams.getDetail().getDefectType()))){
                // 触发预警
                StringBuffer buffer = new StringBuffer();
                if (!StringUtils.isEmpty(defaultOpenAlarmPrefix)) {
                    String[] split = defaultOpenAlarmPrefix.split(",");
                    log.info("giveAlarm split:{}",split);

                    byte[] data = Crc16Util.getData(split[0], split[1], split[2], split[3], split[4], split[5],split[6]);
                    buffer.append(Crc16Util.byteTo16String(data).toUpperCase());
                } else {
                    // 用自定义的触发告警
                    byte[] data = Crc16Util.getData("FC", "00", "6A", "00", "00", "00", "00");
                    buffer.append(Crc16Util.byteTo16String(data).toUpperCase());
                }
                buffer.append("16");

                String sendMsg= buffer.toString();
                sendMsg=sendMsg.replace(" ","");
                log.info("giveAlarm sendMsg:{}",sendMsg);
                CommUtil instance = CommUtil.getInstance();
                instance.send(sendMsg);
            }
        }

    }

协议对接内容

预警硬件通信协议文档
例:打开预警:FC006A000000103ECA16 

-FC:前缀
-00:地址
-6A:功能项[亮光+声音]
-00000010 打开
-00000000 关闭
-00000001-0000000A 范围声音分为10个档位值
-3ECA 校验位CRC0CRC1
-16 拼接后缀

最后成功通讯,控制电路板开关灯!!!!


原网址: 访问
创建于: 2023-11-22 10:06:43
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论