一文搞懂物聯網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

原文鏈接

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