java實現上位機與下位機串口通信

串口通信是在工程應用中很常見。在上位機與下位機通訊過程中常通過有線的串口進行通信,在低速傳輸模式下串口通信得到廣泛使用。在說個之前先來簡單解釋一下上位機與下位機的概念。
上位機與下位機
      通常上位機指的是PC,下位機指的是單片機或者帶微處理器的系統。下位機一般是將模擬信號經過AD採集將模擬量轉換爲數字量,下位機再經過數字信號處理以後將數字信號通過串口發送到上位機,相反上位機可以給下位機發送一些指令或者信息。常見的通信串口包括RS232、RS485、RS422等。這些串口只是在電平特性有所不同,在上位機與下位機進行數據通信時可以不考慮電平特性,而且現在在硬件上有各種轉接接口,使用起來也很方便。
      當然在通常做簡單的串口UART實驗時我們可以使用各種各樣的串口助手小軟件,但是這些串口小工具有時候並不能很好滿足需求,那就嘗試着自己寫一套屬於自己的串口助手?接下來說說如何使用java實現上位機與下位機之間的RS485串口通信。
step 1: 下載支持java串口通信的jar包,這裏給出下載地址:
http://files.cnblogs.com/files/Dreamer-1/mfz-rxtx-2.2-20081207-win-x86.zip(32bit 下載地址)
http://files.cnblogs.com/files/Dreamer-1/mfz-rxtx-2.2-20081207-win-x64.zip (64位下載地址)
    RXTXcomm.jar需要導入到java工程裏面去。另外就是需要將rxtxParallel.dll與rxtxSerial.dll複製在安轉JDK的bin文件下和jre的bin文件夾下面,這樣才能保證能夠正常使用這個jar包。以下是將兩個dll文件複製的位置:

C:\Java\jdk1.8.0_25\bin\
C:\Java\jdk1.8.0_25\jre\bin\

 
step 3:RXTXComm Api如何使用
      接下來就是使用該導入jar包進行編碼實現串口通信的功能了。在編碼之前先來理一理串口通信的主要環節,主要分爲以下幾點:
1)計算機首先需要進行硬件check,查找是否有可用的COM端口,並對該對端口進行簡要判斷,包括這些端口是否是串口,是否正在使用。以下是部分主要代碼:

UARTParameterSetup  串口參數設置類

 

package com.com.cai.rssibawu;

import gnu.io.*;

import java.util.ArrayList;
import java.util.Enumeration;

/*定義一個串口數據包的類並做一些簡單的操作
 * 主要是串口通信的一些異常檢測以及提示
 * 對通信串口的一些基本參數的設置
 * 通常將外部引用的jar包存在工程下lib文件下
 * 該類的幾個方法都是對串口的檢測與設置,不需要經常修改 屬於一個類的操作因此使用static類方法
 * 設置繼承權限,不希望被擴展類繼承,因此類的修飾符爲:final
 */
public class UARTParameterSetup {
    /*類方法 不可改變 不接受繼承
     * 掃描獲取可用的串口
     * 將可用串口添加至list並保存至list
     */
    public static final ArrayList<String> uartPortUseAblefind() {
        //獲取當前所有可用串口
        //由CommPortIdentifier類提供方法
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        ArrayList<String> portNameList = new ArrayList();
        //添加並返回ArrayList
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();
            portNameList.add(portName);
        }
        return portNameList;
    }

    /*
     * 串口常見設置
     * 1)打開串口
     * 2)設置波特率 根據單板機的需求可以設置爲57600 ...
     * 3)判斷端口設備是否爲串口設備
     * 4)端口是否佔用
     * 5)對以上條件進行check以後返回一個串口設置對象new UARTParameterSetup()
     * 6)return:返回一個SerialPort一個實例對象,若判定該com口是串口則進行參數配置
     *   若不是則返回SerialPort對象爲null
     */
    public static final SerialPort portParameterOpen(String portName, int baudrate) {
        SerialPort serialPort = null;
        try {  //通過端口名識別串口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
            //打開端口並設置端口名字 serialPort和超時時間 2000ms
            CommPort commPort = portIdentifier.open(portName, 1000);
            //進一步判斷comm端口是否是串口 instanceof
            if (commPort instanceof SerialPort) {
                System.out.println("該COM端口是串口!");
                //進一步強制類型轉換
                serialPort = (SerialPort) commPort;
                //設置baudrate 此處需要注意:波特率只能允許是int型 對於57600足夠
                //8位數據位
                //1位停止位
                //無奇偶校驗
                serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                //串口配製完成 log
                System.out.println("串口參數設置已完成,波特率爲" + baudrate + ",數據位8bits,停止位1位,無奇偶校驗");
            }
            //不是串口
            else {
                System.out.println("該com端口不是串口,請檢查設備!");
                //將com端口設置爲null 默認是null不需要操作
            }

        } catch (NoSuchPortException e) {
            e.printStackTrace();
        } catch (PortInUseException e) {
            e.printStackTrace();
        } catch (UnsupportedCommOperationException e) {
            e.printStackTrace();
        }

        return serialPort;
    }

    /*
     * 關閉串口
     * 串口關閉以及檢測的COM端口非串口的標誌是返回一個SerialPort是null
     * 關閉串口後使用null進行重置
     */
    public static void closePort(SerialPort serialPort) {
        if (serialPort != null) {
            serialPort.close();
            serialPort = null;
            System.out.println("串口已關閉!");
        }
    }
}

以下是測試類的測試實例:

 

ArrayList<String> arraylist=UARTParameterSetup.uartPortUseAblefind();
        int useAbleLen=arraylist.size();
        if(useAbleLen==0)
        {
            System.out.println("沒有找到可用的串口端口,請check設備!");
        }
        else
        {   
            System.out.println("已查詢到該計算機上有以下端口可以使用:");
            for(int index=0;index<arraylist.size();index++)
            {
                System.out.println("該COM端口名稱:"+arraylist.get(index));
                //測試串口配置的相關方法
            } 
        }   

2)通過計算機對串口的自檢後,可以對串口參數進行簡單的配置。常見的配置可以從常見的串口助手中得到啓發。以下是一個串口助手的人機交互界面。

 

 

以下是對串口設置主要代碼:

UARTParameterSetup  串口參數設置類

package com.com.cai.rssibawu;

import gnu.io.*;

import java.util.ArrayList;
import java.util.Enumeration;

/*定義一個串口數據包的類並做一些簡單的操作
 * 主要是串口通信的一些異常檢測以及提示
 * 對通信串口的一些基本參數的設置
 * 通常將外部引用的jar包存在工程下lib文件下
 * 該類的幾個方法都是對串口的檢測與設置,不需要經常修改 屬於一個類的操作因此使用static類方法
 * 設置繼承權限,不希望被擴展類繼承,因此類的修飾符爲:final
 */
public class UARTParameterSetup {
    /*類方法 不可改變 不接受繼承
     * 掃描獲取可用的串口
     * 將可用串口添加至list並保存至list
     */
    public static final ArrayList<String> uartPortUseAblefind() {
        //獲取當前所有可用串口
        //由CommPortIdentifier類提供方法
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        ArrayList<String> portNameList = new ArrayList();
        //添加並返回ArrayList
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();
            portNameList.add(portName);
        }
        return portNameList;
    }

    /*
     * 串口常見設置
     * 1)打開串口
     * 2)設置波特率 根據單板機的需求可以設置爲57600 ...
     * 3)判斷端口設備是否爲串口設備
     * 4)端口是否佔用
     * 5)對以上條件進行check以後返回一個串口設置對象new UARTParameterSetup()
     * 6)return:返回一個SerialPort一個實例對象,若判定該com口是串口則進行參數配置
     *   若不是則返回SerialPort對象爲null
     */
    public static final SerialPort portParameterOpen(String portName, int baudrate) {
        SerialPort serialPort = null;
        try {  //通過端口名識別串口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
            //打開端口並設置端口名字 serialPort和超時時間 2000ms
            CommPort commPort = portIdentifier.open(portName, 1000);
            //進一步判斷comm端口是否是串口 instanceof
            if (commPort instanceof SerialPort) {
                System.out.println("該COM端口是串口!");
                //進一步強制類型轉換
                serialPort = (SerialPort) commPort;
                //設置baudrate 此處需要注意:波特率只能允許是int型 對於57600足夠
                //8位數據位
                //1位停止位
                //無奇偶校驗
                serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                //串口配製完成 log
                System.out.println("串口參數設置已完成,波特率爲" + baudrate + ",數據位8bits,停止位1位,無奇偶校驗");
            }
            //不是串口
            else {
                System.out.println("該com端口不是串口,請檢查設備!");
                //將com端口設置爲null 默認是null不需要操作
            }

        } catch (NoSuchPortException e) {
            e.printStackTrace();
        } catch (PortInUseException e) {
            e.printStackTrace();
        } catch (UnsupportedCommOperationException e) {
            e.printStackTrace();
        }

        return serialPort;
    }

    /*
     * 關閉串口
     * 串口關閉以及檢測的COM端口非串口的標誌是返回一個SerialPort是null
     * 關閉串口後使用null進行重置
     */
    public static void closePort(SerialPort serialPort) {
        if (serialPort != null) {
            serialPort.close();
            serialPort = null;
            System.out.println("串口已關閉!");
        }
    }
}

      以上代碼就是返回一個對象,同時也返回了對象屬性,因爲對象在java裏面是屬於傳值引用。對以上需要說明的是:在實驗時需要連接串口才能讓計算機檢測到才能讓程序工作,這裏使用的是RS485轉接線:

 

 

3)通過以上兩個步驟後基本對串口的設置也完成了,對於串口類型的確認例如:RS232/RS485/RS422等,可以作爲進一步確認的條件。RS485可以在gnu.io中找到。

 


      接下來就是上位機與下位機之間的雙向通信的功能實現了。該部分主要是利用java的輸入輸出流來實現。以下是主要代碼:

DataTransimit 串口數據傳輸類

package com.com.cai.rssibawu;

import gnu.io.SerialPort;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.TooManyListenersException;

/*
 * 串口數據發送以及數據傳輸作爲一個類
 * 該類做主要實現對數據包的傳輸至下單板機
 */
public class DataTransimit {

    /*
     * 上位機往單板機通過串口發送數據
     * 串口對象 seriesPort
     * 數據幀:dataPackage
     * 發送的標誌:數據未發送成功拋出一個異常
     */
    public static void uartSendDatatoSerialPort(SerialPort serialPort, byte[] dataPackage) {
        OutputStream out = null;
        try {
            out = serialPort.getOutputStream();
            out.write(dataPackage);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關閉輸出流
            if (out != null) {
                try {
                    out.close();
                    out = null;
                    System.out.println("數據已發送完畢!");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /*
     * 上位機接收數據
     * 串口對象seriesPort
     * 接收數據buffer
     * 返回一個byte數組
     */
    public static byte[] uartReceiveDatafromSingleChipMachine(SerialPort serialPort) {
        byte[] receiveDataPackage = null;
        InputStream in = null;
        try {
            in = serialPort.getInputStream();
            //獲取data buffer數據長度
            int bufferLength = in.available();
            while (bufferLength != 0) {
                receiveDataPackage = new byte[bufferLength];
                in.read(receiveDataPackage);
                bufferLength = in.available();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return receiveDataPackage;
    }

    /*
     * 監聽器
     * 數據接收與通信時喚醒線程相關
     * 需要根據線程進行補充
     */
    public static void listener(SerialPort port, SerialPortEventListener listener) {
        //串口添加監聽器
        try {
            port.addEventListener(listener);
        } catch (TooManyListenersException e) {
            e.printStackTrace();
        }
        //設置當前有效
    }
}

      通過以上關於Uart兩個基本類實現對底層Uart的功能封裝,其中一個類主要負責Uart串口自檢和基本設置,另外一個類主要has數據傳輸的兩個方法。接下來以一個實例說一說通過RS485串口通信將系統當前時間發送至單板機系統。
step 4:實現實時系統時間的數據包傳輸至下位機
      這一步可以分爲以下兩個步驟:首先實現獲取系統時間,將時間進行封裝成幀;另外就是通過RS485串口將時間數據包發送至單板機系統進行解析。
1)系統時間的獲取
      根據java面對對象設計思想,這裏將有關係統時間的方法歸爲一類。
以下是獲取當前系統時間代碼:

SystemDateTimeGet  獲取當前時間 組裝數據

package com.com.cai.rssibawu;

import java.util.Calendar;

/*
 * 獲取系統當前時間
 */
public class SystemDateTimeGet {
    //類變量 時間的校驗和
    static int timeCheckSum = 0;

    public static String getCurrentDateTime() {
        //單例模式
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);//獲取年份
        int month = calendar.get(Calendar.MONTH);//獲取月份
        int day = calendar.get(Calendar.DATE);//獲取日期
        int minute = calendar.get(Calendar.MINUTE);//分
        int hour = calendar.get(Calendar.HOUR);//小時
        int second = calendar.get(Calendar.SECOND);//秒
        if (hour >= 12) {
            hour = hour + 12;
        }
        String curerentDateTime = year + " " + (month + 1) + " " + day + " " + hour + " " + minute + " " + second + " ";
        timeCheckSum = year + (month + 1) + day + (hour + 12) + minute + second;
        return curerentDateTime;
    }

    /*
     * 將以上時間字符串進行隔開用byte[]保存
     */
    public static byte[] dateTimeBytesGet(String currenDateTime) {
        //對當前時間參數進行格式判斷
        //對格式進行判斷
        int rawDataSize = 6;
        byte[] dateTimeBytes = new byte[rawDataSize + 1];
        String[] currentDateTimeSplit = currenDateTime.split(" ");
        if (currentDateTimeSplit.length == rawDataSize) {
            //時間數據格式正確
            //eg 2016 12 23 22 18 26
            //使用byte[]進行存儲時需要 -128~+127
            //對於年份使用兩個byte存儲
            for (int dataIndex = 0; dataIndex < rawDataSize; dataIndex++) {
                int dateTemp = Integer.parseInt(currentDateTimeSplit[dataIndex]);
                if (dataIndex == 0) {
                    byte H8bits = (byte) ((dateTemp) >> 8);
                    byte L8bits = (byte) ((dateTemp) & 0xff);
                    dateTimeBytes[dataIndex] = H8bits;
                    dateTimeBytes[dataIndex + 1] = L8bits;
                }
                dateTimeBytes[dataIndex + 1] = (byte) dateTemp;
            }
        } else {
            System.out.println("當前時間獲取出現異常數據");
            System.exit(-1);
            dateTimeBytes = null;
        }
        return dateTimeBytes;
    }

    /*
     * 對時間格式進行解析並還原原來的時間格式
     * 對數據進行還原
     * 僅限於debug使用
     */
    public static String dateTimeBytesfromTostring(byte[] currentDateTime) {
        String string = "";
        if (currentDateTime.length == 7) {
            string = ((currentDateTime[0] << 8) + bytetoUnsigendInt(currentDateTime[1])) + " " + currentDateTime[2] + " " +
                    currentDateTime[3] + " " + currentDateTime[4] + " " + currentDateTime[5] + " " +
                    currentDateTime[6];
        }

        return string;
    }

    /*
     * 將byte轉化爲字符串
     * 將有符號byte轉化爲無符號數字
     * debug使用
     */
    public static int bytetoUnsigendInt(byte aByte) {

        String s = String.valueOf(aByte);
        System.out.println(s);
        //System.out.println(s);
        int bytetoUnsigendInt = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != '0') {
                bytetoUnsigendInt += 1 << (7 - i);
            }
        }
        return bytetoUnsigendInt;
    }

    /*
     * 將數組封裝成幀
     * 每一個數據幀由以下幾個部分組成
     * 1)數據包頭部 head 0X2F
     * 2)數據包命令 CMD  0X5A
     * 3)數據個數     length of data 7
     * 4)校驗和         H8/L8 byte of  check sum(高字節在前 低字節在後)
     * 5)數據結尾標誌 tail OX30
     * 6)可採用線程進行獲取當前時間
     */
    public static byte[] makeCurrentDateTimefromStringtoFramePackage(byte[] dateTimeBytes) {
        //在時間byte[]前後添加一些package校驗信息
        int dataLength = 13;
        byte[] terimalTimePackage = new byte[dataLength];
        //裝填信息
        //時間數據包之前的信息
        terimalTimePackage[0] = 0x2F;
        terimalTimePackage[1] = 0X5A;
        terimalTimePackage[2] = 7;
        //計算校驗和
        //轉化爲無符號進行校驗
        for (int dataIndex = 0; dataIndex < dateTimeBytes.length; dataIndex++) {
            terimalTimePackage[dataIndex + 3] = dateTimeBytes[dataIndex];
        }
        //將校驗和分爲高低字節
        byte sumH8bits = (byte) ((timeCheckSum) >> 8);
        byte sumL8bits = (byte) ((timeCheckSum) & 0xff);
        terimalTimePackage[10] = sumH8bits;//高字節在前
        terimalTimePackage[11] = sumL8bits;//低字節在後
        //數據包結尾
        terimalTimePackage[12] = 0X30;
        return terimalTimePackage;
    }
}

      java 提供了calender類,該類提供了一些與時間有關方法。至於Calendar.getInstance()使用單例模式獲取一個Calendar實例對象,單例模式就是一個類在任何時候只允許有一個實例化對象。獲取系統時間除了使用Calendar還可以使用Date類,通過創建對象也可以實現系統當前時間的獲取。timeCheckSum作爲時間數據的校驗和發送至單板機作爲自定義協議的一部分。
      由於發送的數據包通常是以字節(byte)爲單位進行發送和傳輸的,因此需要將int型的時間轉換爲byte使用byte[]進行存儲,作爲一個數據包發送。

 

    /*
     * 將以上時間字符串進行隔開用byte[]保存
     */
    public static byte[] dateTimeBytesGet(String currenDateTime)
    {
        //對當前時間參數進行格式判斷
        //對格式進行判斷
        int rawDataSize=6;
        byte[] dateTimeBytes=new byte[rawDataSize+1];
        String[] currentDateTimeSplit=currenDateTime.split(" ");
        if(currentDateTimeSplit.length==rawDataSize)
        {
            //時間數據格式正確
            //eg 2016 12 23 22 18 26
            //使用byte[]進行存儲時需要 -128~+127
            //對於年份使用兩個byte存儲
            for(int dataIndex=0;dataIndex<rawDataSize;dataIndex++)
            {
                int dateTemp=Integer.parseInt(currentDateTimeSplit[dataIndex]);
                if(dataIndex==0)
                {
                    byte H8bits=(byte)((dateTemp)>>8);
                    byte L8bits=(byte)((dateTemp)&0xff);
                    dateTimeBytes[dataIndex]= H8bits;
                    dateTimeBytes[dataIndex+1]= L8bits;
                }
                dateTimeBytes[dataIndex+1]=(byte)dateTemp;
            }
        }else
        {
            System.out.println("當前時間獲取出現異常數據");
            System.exit(-1);
            dateTimeBytes=null;
        }
        return dateTimeBytes;
    }

      以上數據可以使用7個byte對時間數據進行存儲,因爲年份需要使用兩個字節來存儲,格式爲高字節在前,低字節在後,之後依次存放。
      將時間數據存放在byte[]數組以後接下來就是添加自己的協議部分了。該部分具有較大的隨意性,因爲該協議可以根據不同的風格有不同的形式。爲了簡單起見,只需要在時間數據byte[]之前添加head、CMD、時間數據長度length這三個字節進行補充,時間數據byte[]後面依次添加校驗和的高低字節以及tail指令即可。以上基本實現了一個簡單的時間數據package。以下是本模塊的代碼:

 

    /*
    * 將數組封裝成幀
    * 每一個數據幀由以下幾個部分組成
    * 1)數據包頭部 head 0X2F
    * 2)數據包命令 CMD  0X5A
    * 3)數據個數     length of data 7
    * 4)校驗和         H8/L8 byte of  check sum(高字節在前 低字節在後)
    * 5)數據結尾標誌 tail OX30
    * 6)可採用線程進行獲取當前時間
    */
    public static byte[] makeCurrentDateTimefromStringtoFramePackage(byte[] dateTimeBytes)
    {
        //在時間byte[]前後添加一些package校驗信息
        int dataLength=13;
        byte[] terimalTimePackage=new byte[dataLength];
        //裝填信息
        //時間數據包之前的信息
        terimalTimePackage[0]=0x2F;
        terimalTimePackage[1]=0X5A;
        terimalTimePackage[2]=7;
        //計算校驗和
        //轉化爲無符號進行校驗
        for(int dataIndex=0;dataIndex<dateTimeBytes.length;dataIndex++)
        {
            terimalTimePackage[dataIndex+3]=dateTimeBytes[dataIndex];
        }
        //將校驗和分爲高低字節
        byte sumH8bits=(byte)((timeCheckSum)>>8);
        byte sumL8bits=(byte)((timeCheckSum)&0xff);
        terimalTimePackage[10]=sumH8bits;//高字節在前
        terimalTimePackage[11]=sumL8bits;//低字節在後
        //數據包結尾
        terimalTimePackage[12]=0X30;
        return terimalTimePackage;
    }

      下面給出了將時間數據byte數組進行解析的debug代碼,一方面是確定上位機本部分模塊的程序可靠性,另外也可以直接移植到下位機對數據包的解析之中。在下位機解析過程中需要注意一點:因爲在java中8大基本類型都是帶符號,年份時間和時間校驗和拆分爲高低字節時,低字節是二進制無符號的,但是計算機卻是按照有符號數(補碼方式)進行讀取,例如在2016年轉換爲二進制數爲:11111100000,那麼高字節爲00000111,低字節爲11100000。計算機讀取爲:高字節爲7,低字節爲-32。其實由兩個byte真實還原的過程應爲:7<<8+(低字節二進制數字)=7*256+224=2016,因此在debug解析時間數據包時需要將有符號數字轉換爲無符號數字。

 

     /*
     * 對時間格式進行解析並還原原來的時間格式
     * 對數據進行還原
     * 僅限於debug使用
     */
    public static String dateTimeBytesfromTostring(byte[] currentDateTime)
    {
        String string="";
        if(currentDateTime.length==7)
        {
          string=((currentDateTime[0]<<8)+bytetoUnsigendInt(currentDateTime[1]))+" "+currentDateTime[2]+" "+
          currentDateTime[3]+" "+currentDateTime[4]+" "+currentDateTime[5]+" "+
          currentDateTime[6];
        }

        return string;
    }
    
    /*
     * 將byte轉化爲字符串
     * 將有符號byte轉化爲無符號數字
     * debug使用
     */
    public  static int bytetoUnsigendInt(byte aByte)
    {
        String s=String.valueOf(aByte);
        //System.out.println(s);
        int bytetoUnsigendInt=0;
        for(int i=0;i<s.length();i++)
        {
            if(s.charAt(i)!='0')
            {
                bytetoUnsigendInt+=1<<(7-i);
            }
        }
        return bytetoUnsigendInt;
    }

2)將最後的時間數據包通過RS485串口發送至下位機
      結合前面的串口程序就可以使用串口發送程序了。在程序debug的前期可以在程序的關鍵位置輸出日誌就是打印log的方法可以提高程序調試的效率。以下是主類的測試代碼:

DateTimeTransimitUseUARTMain  測試類 向串口發送數據

package com.com.cai.rssibawu;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/*
 * 主要是實現對系統時間的數據包封裝通過windows UART傳送給單板機進行時間校準
 * 本程序主要是實現上位機程序,實現對時間數據包的數據的格式的定義與解釋
 * 根據定義的時間packageg格式在單板機上進行解析

 * 逐步實現以下
 * step 1:加載UART 通信數據包
 * step 2:單列模式獲取系統時間(如何獲取?方案)
 * step 3:開啓線程,該線程主要負責取時間後將時間數據包進行發送至串口
 * step 4:簡單的測試代碼(可以使用線程監控串口buffer數據並顯示,嘗試解析並打印日誌)
 * step 5:實現界面的封裝
 * step 6:添加UART接收程序
 * step 7:對一些波形進行簡單顯示以及對數據進行存儲
 * step 8:mysql數據進行存儲 JDBC
 * step 9:進一步實現界面的封裝
 * step 10:對重要信息的可視化
 */
public class DateTimeTransimitUseUARTMain {
    public static void main(String[] args) {
        //主mian類
        byte[] dataFrame = {0x00, 0x5A, 0x64, 0x56, 0x43, 0x6F, 0x78};
        ArrayList<String> arraylist = UARTParameterSetup.uartPortUseAblefind();
        int useAbleLen = arraylist.size();
        if (useAbleLen == 0) {
            System.out.println("沒有找到可用的串口端口,請check設備!");
        } else {
            System.out.println("已查詢到該計算機上有以下端口可以使用:");
            for (int index = 0; index < arraylist.size(); index++) {
                System.out.println("該COM端口名稱:" + arraylist.get(index));
                //測試串口配置的相關方法
            }
            //取出第一個COM端口進行測試
            SerialPort serialPort = UARTParameterSetup.portParameterOpen(arraylist.get(0), 57600);
            //退出程序 後續不需要監測 因爲transimit一直需要保證連接狀態
            //System.exit(0);
            DataTransimit.uartSendDatatoSerialPort(serialPort, dataFrame);
            String currentDateTime = SystemDateTimeGet.getCurrentDateTime();
            System.out.println(currentDateTime);
            byte[] bytes = SystemDateTimeGet.dateTimeBytesGet(currentDateTime);
            //System.out.println(Arrays.toString(bytes));
            String str = SystemDateTimeGet.dateTimeBytesfromTostring(bytes);
            System.out.println(str);
            //System.out.println(SystemDateTimeGet.bytetoUnsigendInt((byte) -32));
            byte[] terimalTimeByte = SystemDateTimeGet.makeCurrentDateTimefromStringtoFramePackage(bytes);
            System.out.println(Arrays.toString(terimalTimeByte));
            DataTransimit.uartSendDatatoSerialPort(serialPort, terimalTimeByte);
            //關閉串口
            UARTParameterSetup.closePort(serialPort);
        }
    }
}



以下是測試結果:
      當沒有串口設備接入計算機時控制檯打印一條信息:

 

沒有找到可用的串口端口,請check設備!

      當RS485設備接入計算機時,控制檯打印消息如下:


已查詢到該計算機上有以下端口可以使用:
該COM端口名稱:COM5
該COM端口是串口!
串口參數設置已完成,波特率爲57600,數據位8bits,停止位1位,無奇偶校驗
數據已發送完畢!
2020 1 9 2 13 21 
-28
2016 1 9 2 13 21
[47, 90, 7, 7, -28, 1, 9, 2, 13, 21, 8, 30, 48]
數據已發送完畢!
串口已關閉!

Process finished with exit code 0

 


      通過以上幾個步驟基本實現了上位機與下位機串口通信的功能,接下來還可以對程序進行改進:
1 )添加界面,可以類比串口助手界面根據自身需要設計獨具風格的人機交互界面,可以使用java等編寫獨具風格的界面。對於數據校正部分,以上只給出了一種較簡單的方式,在實際操作時可以採用與場景合適的數據校驗方式。
2 )在程序中添加線程,在以上程序中對於系統時間的獲取可以通過線程的方式進行獲取,這樣上位機就可以一直往下位機發送數據包,而不是僅僅發一次。
3 )對於上位機數據接收,除了以上最基本的接收功能外,還可以使用JDBC與mysql等數據進行存儲,並繪畫數據曲線實現特性分析等更多功能。
   
 

發佈了231 篇原創文章 · 獲贊 46 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章