Java串口通信 RXTX 解決過程


背景介紹:

  由於第一次用Java與硬件通信,網上查了許多資料,在這進行整理,便於以后學習。本人串口測試是USB串口設備連接電腦,在設備管理器中找到端口名稱(也可以通過一些虛擬串口工具模擬)。

下面主要簡述獲取串口消息返回值的一些問題,在最下面將會附上完整代碼。

准備工作:

    RXTX包:mfz-rxtx-2.2-20081207-win-x64.zip,解壓,RXTXcomm.jar加入項目依賴庫里,rxtxParallel.dll和rxtxSerial.dll放入jdk的bin目錄下(我使用的jdk1.8)

RXTX工具類編寫:

  編寫基礎方法:獲取可用端口名,開啟端口,發送命令,接受命令,關閉端口

import gnu.io.*;

import javax.sound.midi.SoundbankResource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;

import static com.why.rxtx.utill.HexadecimalUtil.get16NumAdd0;
import static com.why.rxtx.utill.HexadecimalUtil.hexStringToByteArray;
import static com.why.rxtx.utill.OrderUtil.retuenLogOrder;
import static com.why.rxtx.utill.OrderUtil.send;

/**
 * 使用rxtx連接串口工具類
 */
public class RXTXUtil {
   
    private static final String DEMONAME = "串口測試";

    /**
     * 檢測系統中可用的端口
     */
    private CommPortIdentifier portId;
    /**
     * 獲得系統可用的端口名稱列表
     */
    private static Enumeration<CommPortIdentifier> portList;
    /**
     * 輸入流
     */
    private static InputStream inputStream;
    /**
     * RS-232的串行口
     */
    private static SerialPort serialPort;
    /**
     * 返回結果
     */
    private static String res=null;
    

    

    /**
     * 獲得系統可用的端口名稱列表
     * @return 可用端口名稱列表
     */
    @SuppressWarnings("unchecked")
    public static void getSystemPort(){
        List<String> systemPorts = new ArrayList<>();
        //獲得系統可用的端口
        portList = CommPortIdentifier.getPortIdentifiers();
        while(portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();//獲得端口的名字
            systemPorts.add(portName);
        }

    }

    /**
     * 開啟串口
     * @param serialPortName 串口名稱
     * @param baudRate 波特率
     * @return 串口對象
     */
    public static void openSerialPort(String serialPortName,int baudRate) {
        try {
            //通過端口名稱得到端口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName);
            //打開端口,(自定義名字,打開超時時間)
            CommPort commPort = portIdentifier.open(serialPortName, 5000);
            //判斷是不是串口
            if (commPort instanceof SerialPort) {
                serialPort = (SerialPort) commPort;
                //設置串口參數(波特率,數據位8,停止位1,校驗位無)
                serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                //    System.out.println("開啟串口成功,串口名稱:"+serialPortName);
            }else {
                //是其他類型的端口
                throw new NoSuchPortException();
            }
        } catch (NoSuchPortException e) {
            e.printStackTrace();
        } catch (PortInUseException e) {
            e.printStackTrace();
        } catch (UnsupportedCommOperationException e) {
            e.printStackTrace();
        }

    }

    /**
     * 向串口發送數據
     * @param order 發送的命令
     */
    public static void sendData( String order) {
        //16進制表示的字符串轉換為字節數組
        byte[] data =hexStringToByteArray(order);
        OutputStream os = null;
        try {
            os = serialPort.getOutputStream();//獲得串口的輸出流
            os.write(data);
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉流操作
            try {
                if (os != null) {
                    os.close();
                    os = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 從串口讀取數據
     * @return 讀取的數據
     */
    public static String  readData() {
        //保存串口返回信息
        StringBuffer res=new StringBuffer(40);
        InputStream is = null;
        byte[] bytes = null;
        try {
            is = serialPort.getInputStream();//獲得串口的輸入流
            int bufflenth = is.available();//獲得數據長度
            while (bufflenth != 0) {
                bytes = new byte[bufflenth];//初始化byte數組
                is.read(bytes);
                bufflenth = is.available();
            }
            if(bytes!=null) {
                for (int i = 0; i < bytes.length; i++) {
                    //轉換成16進制數(FF)
                    res.append(get16NumAdd0((bytes[i]&0xff)+"",2));
                }
            }
            System.out.println("res: "+res.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                    is = null;
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

        return res.toString();
    }

    /**
     * 關閉串口
     *
     */
    public static void closeSerialPort() {
        if(serialPort != null) {
            serialPort.close();
            //System.out.println("關閉了串口:"+serialPort.getName());
            serialPort = null;
        }
    }

    
}

  在一些基本方法寫完后,便得開始測試串口通信了

  於是就開始編寫測試代碼了

/**
     * 串口命令執行
     * @param order 命令
     * @param portName 端口名
     * @param baudRate 波特率
     * @return
     * @throws UnsupportedEncodingException
     */
    public synchronized  static String executeOrder(String order,String portName,int baudRate)  {
        String str="";
        if (serialPort==null) {
            openSerialPort(portName, baudRate);
        }
        //發送消息
        sendData(order);
               //接收消息
        String str=readData();
        
        return res;
    }
                

很遺憾上面代碼輸入命令,端口號等等后,返回結果一直是null, 突然間發現和以前寫讀寫io流還是有一定區別的。原因在於,io流是有文件在那可以隨時讀寫,而串口需要等待返回信息。

然后便開始改寫代碼。通過上網查詢,發現有一個端口監聽器,於是有了下面代碼

/**
     * 串口命令執行
     * @param order 命令
     * @param portName 端口名
     * @param baudRate 波特率
     * @return
     * @throws UnsupportedEncodingException
     */
    public synchronized  static String executeOrder(String order,String portName,int baudRate)  {
        String str="";
        if (serialPort==null) {
            openSerialPort(portName, baudRate);
        }
        //發送消息
        sendData(order);
        
        //設置監聽
        setListenerToSerialPort( new SerialPortEventListener(){
            @Override
            public void serialEvent(SerialPortEvent serialPortEvent) {
                if(serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {//數據通知
                    String str=readData();
                    res=returnCheck(order,str);
                }
            }
        });

        
        return res;
    }

/**
     * 給串口設置監聽
     * @param listener
     */
    public static void setListenerToSerialPort( SerialPortEventListener listener) {
        try {
            //給串口添加事件監聽
            serialPort.addEventListener(listener);
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        }
        serialPort.notifyOnDataAvailable(true);//串口有數據監聽
        serialPort.notifyOnBreakInterrupt(true);//中斷事件監聽

    }

經過再次執行,發現返回的依舊是null,於是在監聽器里打印串口消息輸出,發現是先返回 null,然后再打印串口消息。於是得出結論監聽器是異步的,但是我們需要返回值去判斷是否執行成功,

顯然這不符合要求。於是再做修改,需要把這異步返回值接收到。於是有了下面兩種方式:

第一種:放棄監聽器,使用循環去讀取串口返回信息,通過循環次數設置最大請求時間

/**
     * 串口命令執行
     * @param order 命令
     * @param portName 端口名
     * @param baudRate 波特率
     * @return
     * @throws UnsupportedEncodingException
     */
    public synchronized  static String executeOrder(String order,String portName,int baudRate)  {
        String str="";
        if (serialPort==null) {
            openSerialPort(portName, baudRate);
        }
        //發送消息
        sendData(order);
        //代替監聽
        for (int i = 0; i <1000; i++) {
            str=readData();
            str=returnCheck(order,str);
            if (str!=null){
                return str;
            }else {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }

第二種:使用監聽,通過循環判斷是否有串口消息返回,與第一種本質還是相同的

/**
     * 串口命令執行
     * @param order 命令
     * @param portName 端口名
     * @param baudRate 波特率
     * @return
     * @throws UnsupportedEncodingException
     */
    public synchronized  static String executeOrder(String order,String portName,int baudRate)  {
        String str="";
        if (serialPort==null) {
            openSerialPort(portName, baudRate);
        }
        //發送消息
        sendData(order);
        
        //設置監聽
        setListenerToSerialPort( new SerialPortEventListener(){
            @Override
            public void serialEvent(SerialPortEvent serialPortEvent) {
                if(serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {//數據通知
                    String str=readData();
                    res=returnCheck(order,str);
                }
            }
        });

        //監聽是異步請求,如果要獲取監聽里的內容做返回值,
        // 可以通過循環去延遲來獲取返回值,
        // 設置最大延遲防止死循環
        long startTime = System.currentTimeMillis();
        while (res==null){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long endTime = System.currentTimeMillis();
            if ((endTime-startTime)/1000.0>20){//最長20s返回
                return res;
            }
        }
        return res;
    }

到此串口消息通信就已解決了,下面附上完整代碼,包括一些使用到的進制轉換方法。

package com.along.outboundmanage.utill;

import gnu.io.*;

import javax.sound.midi.SoundbankResource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;

import static com.why.rxtx.utill.HexadecimalUtil.get16NumAdd0;
import static com.why.rxtx.utill.HexadecimalUtil.hexStringToByteArray;



/**
 * 使用rxtx連接串口工具類
 */
public class RXTXUtil {
    
    private static final String DEMONAME = "串口測試";

    /**
     * 檢測系統中可用的端口
     */
    private CommPortIdentifier portId;
    /**
     * 獲得系統可用的端口名稱列表
     */
    private static Enumeration<CommPortIdentifier> portList;
    /**
     * 輸入流
     */
    private static InputStream inputStream;
    /**
     * RS-232的串行口
     */
    private static SerialPort serialPort;
    /**
     * 返回結果
     */
    private static String res=null;
    /**
     * 串口命令執行
     * @param order 命令
     * @param portName 端口名
     * @param baudRate 波特率
     * @return
     * @throws UnsupportedEncodingException
     */
    public synchronized  static String executeOrder(String order,String portName,int baudRate)  {
        String str="";
        if (serialPort==null) {
            openSerialPort(portName, baudRate);
        }
        //發送消息
        sendData(order);
        //代替監聽
        /*for (int i = 0; i <1000; i++) {
            str=readData();
            
            if (str!=null){
                return str;
            }else {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }*/
        //設置監聽
        setListenerToSerialPort( new SerialPortEventListener(){
            @Override
            public void serialEvent(SerialPortEvent serialPortEvent) {
                if(serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {//數據通知
                    String str=readData();
                    res=returnCheck(order,str);
                }
            }
        });

        //監聽是異步請求,如果要獲取監聽里的內容做返回值,
        // 可以通過循環去延遲來獲取返回值,
        // 設置最大延遲防止死循環
        long startTime = System.currentTimeMillis();
        while (res==null){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long endTime = System.currentTimeMillis();
            if ((endTime-startTime)/1000.0>20){//最長20s返回
                return res;
            }
        }
        return res;
    }

    


    /**
     * 獲得系統可用的端口名稱列表
     * @return 可用端口名稱列表
     */
    @SuppressWarnings("unchecked")
    public static void getSystemPort(){
        List<String> systemPorts = new ArrayList<>();
        //獲得系統可用的端口
        portList = CommPortIdentifier.getPortIdentifiers();
        while(portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();//獲得端口的名字
            systemPorts.add(portName);
        }

    }

    /**
     * 開啟串口
     * @param serialPortName 串口名稱
     * @param baudRate 波特率
     * @return 串口對象
     */
    public static void openSerialPort(String serialPortName,int baudRate) {
        try {
            //通過端口名稱得到端口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortName);
            //打開端口,(自定義名字,打開超時時間)
            CommPort commPort = portIdentifier.open(serialPortName, 5000);
            //判斷是不是串口
            if (commPort instanceof SerialPort) {
                serialPort = (SerialPort) commPort;
                //設置串口參數(波特率,數據位8,停止位1,校驗位無)
                serialPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                //    System.out.println("開啟串口成功,串口名稱:"+serialPortName);
            }else {
                //是其他類型的端口
                throw new NoSuchPortException();
            }
        } catch (NoSuchPortException e) {
            e.printStackTrace();
        } catch (PortInUseException e) {
            e.printStackTrace();
        } catch (UnsupportedCommOperationException e) {
            e.printStackTrace();
        }

    }

    /**
     * 向串口發送數據
     * @param order 發送的命令
     */
    public static void sendData( String order) {
        //16進制表示的字符串轉換為字節數組
        byte[] data =hexStringToByteArray(order);
        OutputStream os = null;
        try {
            os = serialPort.getOutputStream();//獲得串口的輸出流
            os.write(data);
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉流操作
            try {
                if (os != null) {
                    os.close();
                    os = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 從串口讀取數據
     * @return 讀取的數據
     */
    public static String  readData() {
        //保存串口返回信息
        StringBuffer res=new StringBuffer(40);
        InputStream is = null;
        byte[] bytes = null;
        try {
            is = serialPort.getInputStream();//獲得串口的輸入流
            int bufflenth = is.available();//獲得數據長度
            while (bufflenth != 0) {
                bytes = new byte[bufflenth];//初始化byte數組
                is.read(bytes);
                bufflenth = is.available();
            }
            if(bytes!=null) {
                for (int i = 0; i < bytes.length; i++) {
                    //轉換成16進制數(FF)
                    res.append(get16NumAdd0((bytes[i]&0xff)+"",2));
                }
            }
            System.out.println("res: "+res.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                    is = null;
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

        return res.toString();
    }

    /**
     * 關閉串口
     *
     */
    public static void closeSerialPort() {
        if(serialPort != null) {
            serialPort.close();
            //System.out.println("關閉了串口:"+serialPort.getName());
            serialPort = null;
        }
    }

    /**
     * 給串口設置監聽
     * @param listener
     */
    public static void setListenerToSerialPort( SerialPortEventListener listener) {
        try {
            //給串口添加事件監聽
            serialPort.addEventListener(listener);
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        }
        serialPort.notifyOnDataAvailable(true);//串口有數據監聽
        serialPort.notifyOnBreakInterrupt(true);//中斷事件監聽

    }
}
import org.thymeleaf.util.StringUtils;


/**
 * 進制換
 * */
public class HexadecimalUtil {
    

    /**
     * 十六進制轉十進制
     *
     * @param num
     * @return
     */
    public static Integer get10HexNum(String num) {
        if (num.contains("0X")) {
            num = num.replace("0X", "");
        }
        return Integer.parseInt(num.substring(0), 16);
    }

    /**
     * 十進制轉十六進制
     *
     * @param num
     * @return
     */
    public static String get16Num(Object num) {

        return Integer.toHexString(Integer.parseInt(num + ""));
    }

    /**
     * 十進制轉十六進制,設置長度,不足補0
     *
     * @param num
     * @return
     */
    public static String get16NumAdd0(String num, int len) {
        String str = Integer.toHexString(Integer.parseInt(num)).toUpperCase();
        String res = "";
        if (len >= str.length()) {
            res = StringUtils.repeat("0", (len - str.length())) + str;
        } else {
            return str;
        }
        return res;
    }



    //num & 0xff
    public static int low8(Object num) {
        return Integer.parseInt(num + "") & 0xff;
    }

    //獲取高四位
    public static int getHeight4(byte data) {
        int height;
        height = ((data & 0xf0) >> 4);
        return height;
    }

    /**
     * 16進制表示的字符串轉換為字節數組
     *
     * @param hexString 16進制表示的字符串
     * @return byte[] 字節數組
     */
    public static byte[] hexStringToByteArray(String hexString) {
        hexString = hexString.replaceAll(" ", "");
        int len = hexString.length();
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            // 兩位一組,表示一個字節,把這樣表示的16進制字符串,還原成一個字節
            bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
                    .digit(hexString.charAt(i + 1), 16));
        }
        return bytes;
    }
}

 

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM