一文搞懂物聯網Modbus通訊協議

語言: CN / TW / HK

簡介: 一般來說,常見的物聯網通訊協議眾多,如藍芽、Zigbee、WiFi、ModBus、PROFINET、EtherCAT、蜂窩等。而在眾多的物聯網通訊協議中,Modbus是當前非常流行的一種通訊協議。它一種序列通訊協議,是Modicon公司於1979年為使用可程式設計邏輯控制器(PLC)通訊而制定的,可以說,它已經成為工業領域通訊協議的業界標準。

1 概述

隨著IT技術的快速發展,當前已經步入了智慧化時代,其中的物聯網技術將在未來佔據越來越重要的地位。根據百度百科的定義,物聯網(Internet of things,簡稱IOT )即“萬物相連的網際網路”,是網際網路基礎上的延伸和擴充套件的網路,物聯網將各種資訊有機的結合起來,實現任何時間、任何地點,人、機、物的互聯互通。物聯網從技術上來說,很重要的核心是通訊協議,即如何按約定的通訊協議,把機、物和人與網際網路相連線,進行資訊通訊,以實現對人、機和物的智慧化識別、定位、跟蹤、監控和管理的一種網路。

一般來說,常見的物聯網通訊協議眾多,如藍芽、Zigbee、WiFi、ModBus、PROFINET、EtherCAT、蜂窩等。而在眾多的物聯網通訊協議中,Modbus是當前非常流行的一種通訊協議。它一種序列通訊協議,是Modicon公司於1979年為使用可程式設計邏輯控制器(PLC)通訊而制定的,可以說,它已經成為工業領域通訊協議的業界標準。其優勢如下:

  • 免費無版稅限制
  • 容易部署
  • 靈活限制少

2ModBus協議概述

Modbus通訊協議使用請求-應答機制在主(Master)(客戶端Client)和從(Slave)(伺服器Server)之間交換資訊。Client-Server原理是通訊協議的模型,其中一個主裝置控制多個從裝置。這裡需要注意的是:Modbus通訊協議當中的Master對應Client,而Slave對應Server。Modbus通訊協議的官網為www.modbus.org。目前官網組織已經建議將Master-Slave替換為Client-Server。從協議型別上可以分為:Modbus-RTU(ASCII)、Modbus-TCP和Modbus-Plus。本文主要介紹Modbus-RTU(ASCII)的通訊協議原理。標準的Modbus協議物理層介面有RS232、RS422、RS485和乙太網介面。

通訊示意圖如下:

一般來說,Modbus通訊協議原理具備如下的特徵:

  • 一次只有一個主機(Master)連線到網路
  • 只有主裝置(Master)可以啟動通訊並向從裝置(Slave)傳送請求
  • 主裝置(Master)可以使用其特定地址單獨定址每個從裝置(Slave),也可以使用地址0(廣播)同時定址所有從裝置(Slave)
  • 從裝置(Slave)只能向主裝置(Master)傳送回覆
  • 從裝置(Slave)無法啟動與主裝置(Master)或其他從裝置(Slave)的通訊

Modbus協議可使用2種通訊模式交換資訊:

  • 單播模式
  • 廣播模式

不管是請求報文還是答覆報文,資料結構如下:

即報文(幀資料)由4部分構成:地址(Slave Number)+功能碼(Function Codes)+資料(Data)+校驗(Check) 。其中的地址代表從裝置的ID地址,作為定址的資訊。功能碼錶示當前的請求執行具體什麼操作,比如讀還是寫。資料代表需要通訊的業務資料,可以根據實際情況來確定。最後一個校驗則是驗證資料是否有誤。其中的功能碼說明如下:

比如功能碼為03代表讀取當前暫存器內一個或多個二進位制值,而06代表將二進位制值寫入單一暫存器。為了模擬Modbus通訊協議過程,這裡可以藉助模擬軟體:

  • Modbus Poll(Master)
  • Modbus Slave

具體的安裝過程這裡不再贅述。首先這裡需要模擬一個物聯網感測器裝置,這裡用Modbus Slave來定義,首先開啟此軟體,並定義一個ID為1的裝置:

此功能碼為03。另外,設定連線引數,示例介面如下:

下面再用Modbus Poll軟體來模擬主機,來獲取從裝置的資料。首先定義一個讀寫報文。

然後再定義一個連線資訊:

注意:兩個COM口要使用不同的名稱。

成功建立通訊後,通訊的報文格式如下:

Tx代表請求報文,而Rx代表答覆報文。

3ModBus Java實現

下面介紹一下如何用Java來實現一個Modbus TCP通訊。這裡Java框架採用Spring Boot,首先需要引入Modbus4j庫。Maven依賴庫的pom.xml定義如下:

 <dependency>
    <groupId>com.infiniteautomation</groupId>
    <artifactId>modbus4j</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>org.rxtx</groupId>
    <artifactId>rxtx</artifactId>
    <version>2.1.7</version>
</dependency>

其中的modbus4j庫可能在Maven中無法正常下載,可以手動下載後放於專案中,並新增到專案庫中。如下圖所示:

注意:首次實用串列埠時,需要進行安裝,否則會報 no rxtxSerial in java.library.path的錯誤。

訪問http://fizzed.com/oss/rxtx-for-java 下載對應作業系統的庫檔案,解壓後安裝如下指導進行拷貝後安裝。

For a JDK installation:
Copy RXTXcomm.jar ---> <JAVA_HOME>\jre\lib\ext
Copy rxtxSerial.dll ---> <JAVA_HOME>\jre\bin
Copy rxtxParallel.dll ---> <JAVA_HOME>\jre\bin

另外,需要注意,這裡還需要串列埠支援,這裡可以用虛擬串列埠軟體來解決。

下面給出Java核心程式碼片段。

    public static SerialPort open(String portName, Integer baudRate, Integer dataBits,
                                  Integer stopBits, Integer parity) {
        SerialPort result = null;
        try {

            CommPortIdentifier identifier = CommPortIdentifier.getPortIdentifier(portName);
            // 開啟埠
            CommPort commPort = identifier.open(portName, 2000);
            // 判斷是不是串列埠
            if (commPort instanceof SerialPort) {
                result = (SerialPort) commPort;
                // 設定串列埠的引數
                result.setSerialPortParams(baudRate, dataBits, stopBits, parity);
                log.info("開啟串列埠{}成功", portName);
            }else{
                log.info("{}不是串列埠", portName);
            }
        } catch (Exception e) {
            log.error("開啟串列埠{}錯誤", portName, e);
        }
        return result;
    }

首先需要啟動Modbus RTU Slave程式,核心程式碼片段如下:

    public static void createRtuSlave(){
        // 串列埠是COM3,波特率是9600
        SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl("COM3", 9600,
                SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, 0, 0);
        ModbusFactory modbusFactory = new ModbusFactory();
        建立RTU Slave
        final ModbusSlaveSet slave = modbusFactory.createRtuSlave(wrapper);
        // 暫存器裡可以設定線圈狀態、離散輸入狀態、保持暫存器和輸入暫存器
        //從站裝置ID是1
        BasicProcessImage processImage = new BasicProcessImage(1);
        processImage.setInvalidAddressValue(Short.MIN_VALUE);
        slave.addProcessImage(processImage);

        // 新增監聽器,監聽slave線圈狀態和保持暫存器的寫入
        processImage.addListener(new MyProcessImageListener());
        //設定資料
        setCoil(processImage);
        setInput(processImage);
        setHoldingRegister(processImage);
        setInputRegister(processImage);

        // 開啟執行緒啟動
        new Thread(() -> {
            try {
                slave.start();
            }
            catch (ModbusInitException e) {
                e.printStackTrace();
            }
        }).start();
    }

而Modbus RTU Master程式,核心程式碼片段如下:

    private static void createRtuMaster() throws Exception{
        //串列埠是COM4,波特率是9600
        SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl("COM4", 9600,
                SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, 0, 0);
        ModbusFactory modbusFactory = new ModbusFactory();
        //RTU Master
        ModbusMaster master = modbusFactory.createRtuMaster(wrapper);
        master.init();
        // 從站裝置ID是1
        int slaveId = 1;
        // 讀取保持暫存器
        readHoldingRegisters(master, slaveId, 0, 3);
        // 將地址為0的保持暫存器資料修改為0
        writeRegister(master, slaveId, 0, 0);
        // 再讀取保持暫存器
        readHoldingRegisters(master, slaveId, 0, 3);
    }

啟動後輸出如下所示:

//Slave
[Thread-1] INFO cn.wu.demo.modbus4j.util.SerialPortUtils - 開啟串列埠COM3成功
保持暫存器地址=0,舊值=8,新值=0
//Master  //////////////////////////////////////////////////////////////////////  
[main] INFO cn.wu.demo.modbus4j.util.SerialPortUtils - 開啟串列埠COM4成功
讀取保持暫存器=[8, 56, 0]
寫保持暫存器成功
讀取保持暫存器=[0, 56, 0]

參考開源專案:https://github.com/wu-boy/modbus4j

原文連結

本文為阿里雲原創內容,未經允許不得轉載。