Modbus 协议快速入门

Modbus 协议快速入门

1.什么是 Modbus 协议

什么是协议:是一种约定或规则或规则,它在计算机网络和通信领域起着至关重要的作用。具体来说,协议是网络中(或一般业务中)进行数据交换和解释信息时所要遵守的一套规则和约定,或者说是通信双方共同遵守的一组规则或标准。这些规则或标准详细定义了信息的格式、传输的顺序、控制信息以及同步机制等各个方面。

什么是modbus协议:Modbus协议是一种串行通信协议,由Modicon公司(现为施耐德电气Schneider Electric)于1979年发表,旨在实现可编辑逻辑控制器(PLC)之间的通信。如今,它已经成为工业领域通信协议的业界标准,并且是工业电子设备之间常用的连接方式。Modbus是主从方式通信,也就是说,不能同步进行通信,总线上每次只有一个数据进行传输,既主机发送,从机应答,主机不发送,总线上就没有数据通信。一个主线上只能有一个主句,可以有若干个从机。

什么是串行通信协议:串行通信是一种计算机通信方式,它在主机与外设以及主机之间的数据传输中起着重要作用。串行通信是指数据按位依次传输的通信方式,每位数据占据固定的时间长度,并使用少数几条通信线路完成系统间的信息交换。在串行通信中,数据被分解为一系列单独的比特(位),并按顺序通过传输线进行传输。每个比特在传输线上占用固定的时间长度,这样接收端就可以按照相同的时序接收并重组这些数据位,从而回复出原始数据

2.Modbus有什么用

Modbus协议广泛应用于工业控制和自动化领域,可以连接各种设备和控制器,用于实现数据交换、监控和控制。具体包括:

  1. 工业自动化控制:Modbus被广泛应用于工业自动化控制系统中,用于连接PLC、传感器、执行器等设备,实现监控和控制
  2. 智能家居:Modbus协议可以应用于智能家居系统中,用于连接各种传感器和执行器,实现远程控制和检测
  3. 能源监控:Modbus协议可以用于能源监控系统,连接电表、燃气表、水表等设备,实现能源数据的采集和分析。
  4. 环境检测:Modbus协议可以应用于环境检监测系统中,连接各种传感器和仪器,监测环境参数如温度、湿度、气压等。
  5. 智能交通:Modbus可以应用于智能交通系统中,用于连接交通控制设备、车辆检测器等,实现交通信号的控制和管理

总的来说,就是约定了一套设备之间数据交互的规则,用于各种设备之间进行通信的。

3.Modbus内容

3.1 Modbus概述

modbus分为以下三种协议:

  • Modbus-RTU
  • Modbus-TCP
  • Modbus-ASCII

以上三种协议,比较常用的 Modbus-RTU 协议,其次是 Modbus-TCP 协议,一个设备只会有一种协议。

Modbus是主从方式通信

什么是帧:Modbus每发送一次数据就是一个数据帧,每个数据帧都必须符合modbus的帧结构。

3.2 Modbus-RTU

设备必须要有RTU协议!这是Modbus协议上规定的,且默认模式必须是 RTU,ASCLL作为选项。

也就是说,大部分时候我们都是使用 Modbus-RTU协议进行通信

3.2.1 帧格式

帧结构 = 地址 + 功能码 + 数据 + 校验

  • 地址(设备编号):占用一个字节,范围是 0-255,其中有效范围是 1-247,其他有特殊用途,比如 255 是广播地址(广播就是应答所有地址,正常的需要两个设备的地址一样才能进行查询和回复)
  • 功能码:占一个字节,功能码的意义就是,知道这个指令是干啥的,就是告诉从机你要执行什么操作(常用 0x03,0x06和0x10功能码)
  • 数据:根据功能码的不同,数据会有不同的结构,具体详见下面的分析
  • 校验:为了保证数据不错误,需传输校验码进行校验,如果校验无误则代表传输的数据并没有丢失。校验的算法为CRC冗余校验
3.2.2 0x03查询寄存器功能码

如果有硬件条件,使用支持modbus协议的设备,通过串口连接主机以及设备,同时打开串口调试工具按下面的进行操作

主机发送:02 03 80 00 00 02 ED F8
从机返回:02 03 04 00 00 20 09 10 F5 

如果通过发送上面的数据,从机返回对应数据,则代表我们成功通过串口实现了受用 modbus 协议进行通信

来分析上面的报文

发送  02 03 80 00 00 02 ED F8
02(从机地址) +  03(功能码) + 8000(起始寄存器地址) + 0002(查询的寄存器个数) + ED F8(CRC校验)
1 byte        +  1 byte       + 2 byte              + 2 byte                 + 2 byte  (总长度固定为8byte)

02:从机地址,这里是发给设备编号为02的从机,占1byte

03:功能码,0x03代表查询寄存器功能,占1byte

8000:要查询的起始寄存器地址,这里寄存器地址为 0x8000,占2byte

0002:要查询几个寄存器,这里查询2(0x0002)个寄存器,占2byte

ED F8:CRC校验码,通过CRC算法算的,占2byte

这串报文简单化来讲就是,我向02设备发送报文(02),告诉02设备我要查询寄存器(03功能码),寄存器起始地址是0x8000(根据实际需要修改寄存器地址),我要查询 2(0002)个寄存器,为了保证我发的这个报文是没有被人动过的,我告诉02设备,我这个报文的密码是 ED F8(CRC校验码),02设备你收到我发给你的报文后,看下这个密码能不能打开报文(从机设备会将除校验码外的其他报文用CRC算法进行计算,并比对报文中的CRC校验码,一致则代表报文是完整的),打不开的话那就是我这边给错密码了,或者是中间丢失了一些东西(报文不完整)

回复  02 03 04 00 00 20 09 10 F5 
02(从机地址) +  03(功能码)+ 04(数据长度) + 0000(第一个数据) + 2009(第二个数据) + 10 F5(CRC校验)
1 byte        +  1 byte      +1byte           + 2 byte           + 2 byte            + 2 byte (总长度固定为9byte)

02:从机地址,这里是返回的设备编号为02的从机,占1byte

03:功能码,0x03代表查询寄存器功能,占1byte

04:返回的数据长度,数值为 2x查询的寄存器个数,占1byte

0000:查询0x8000寄存器数据,占2byte

2009:查询0x8001寄存器数据,占2byte

10 F5:CRC校验码,通过CRC算法算的,占2byte

注:功能码和CRC校验中间的为查询后返回的数据,长度为 2byte x 查询的寄存器个数,每2byte作为一个返回的值

这串报文简单化来讲就是,02设备说我是02设备(02),刚刚执行的是查询寄存器的操作(03),总共返回的数据长度为为4byte(04),返回的数据为 0x0000 和 0x2009(0000 2009),主机你收到我的报文后用 10 F5 作为密码进行打开(CRC校验码)

也就是说,

主机发送的报文就是: 找谁(从机地址) + 要干嘛(功能码) + 具体要干的事情细节(数据:寄存器起始地址 + 查询的寄存器个数)+ 验证

从机发送的报文就是: 我是谁(从机地址) + 刚刚干了什么事情(功能码) + 干了几件事情(查询的寄存器个数) + 每件事情的结果(数据:寄存器里面的值)+ 验证

3.2.3 0x06修改寄存器功能码

报文如下

主机发送:02 06 A8 0A 00 01 48 5B
从机返回:02 06 A8 0A 00 01 48 5B

对上面报文进行分析

发送 02 06 A8 0A 00 01 48 5B
02(从机地址) +  06(功能码) + A80A(修改的寄存器地址) + 0001(修改后的数据) + 48 5B(CRC校验)
1 byte        +  1 byte       + 2 byte              + 2 byte                 + 2 byte  (总长度固定为8byte)

02:从机地址,这里是发给设备编号为02的从机,占1byte

06:功能码,0x06代表修改寄存器功能,占1byte

A80A:要修改的寄存器地址,这里寄存器地址为 0xA80A,占2byte

0001:修改后的值,这里为 0x0001,占2byte

48 5B:CRC校验码,通过CRC算法算的,占2byte

0x06发送的报文和0x03发送的报文很像,区别就是0x03修改寄存器的个数咋 0x06 就是修改后的数据,可以进行联想记忆。

这串报文简单化来讲就是,主机向02设备发送报文(02),告诉02设备我要修改寄存器(06),修改的寄存器地址为 0xA80A (A80A ),修改后的数据为 0x0001(0001),打开这个报文的密码是 48 5B(CRC校验码)

回复 02 06 A8 0A 00 01 48 5B
02(从机地址) +  06(功能码) + A80A(修改的寄存器地址) + 0001(修改后的数据) + 48 5B(CRC校验)
1 byte        +  1 byte      +2byte                   +2byte               + 2 byte  (总长度固定为8byte)

这里可以看到,0x06修改命令返回的报文和发送的报文是一致的,但他们代表的意义是不同的,当然,除了一些特殊的从机地址,可以按照返回的报文和发送的报文一致来判断返回报文是否正确

02:从机地址,这里是返回的设备编号为02的从机,占1byte

06:功能码,0x06代表修改寄存器功能,占1byte

8000:已经修改的寄存器地址,这里寄存器地址为 0xA80A,占2byte

0001:修改完成后该寄存器的值,这里为 0x0001,占2byte

48 5B:CRC校验码,通过CRC算法算的,占2byte

这串报文简单化来讲就是,02设备向主机发送报文,告诉主机我是02设备(02),刚刚执行了修改寄存器(0x06),修改后的寄存器地址为 0xA80A (A80A ),修改后的数据为 0x0001(0001),打开这个报文的密码是 48 5B(CRC校验码)

3.2.3 0x10批量修改寄存器功能码

在批量修改或者修改32位寄存器(32位寄存器在存储时占了两个16位的寄存器)时使用这个命令

报文如下

主机发送:02 10 A8 06 00 02 04 00 0F 00 03 93 04
从机返回:02 10 A8 06 00 02 81 9A

对上面报文进行分析

发送:02 10 A8 06 00 02 04 00 0F 00 03 93 04
02(从机地址)+ 10(功能码)+ A806(寄存器起始地址)+ 0002(寄存器个数)+ 04(数据长度)+ 000F(数据1)+ 0003(数据2)+ 9304(CRC校验码)
1byte        + 1byte      + 2byte               + 2byte            + 2byte        + (2byte * 查询的寄存器个数) + 2byte 

02:从机地址,这里是发给设备编号为02的从机,占1byte

10:功能码,0x10代表批量修改寄存器功能,占1byte

A806:要修改的寄存器起始地址,这里寄存器起始地址为 0xA806,占2byte

0002:修改的寄存器个数,这里为 0x0002,占2byte

04:修改的数据长度,数值为 2x修改的寄存器个数,这里为 0x04,占1byte

000F:修改的第一个寄存器的值,这里为 0x000F,占2byte

0003:修改的第二个寄存器的值,这里为000F,占2byte

93 04:CRC校验码,通过CRC算法算的,占2byte

这个报文和发送0x03查询的报文有点相似,就是在查询寄存器个数后面增加了要发送的数据的长度和具体数据,可以联想来记忆

这串报文简单化来讲就是,主机向02设备发送报文(02),告诉02设备我要批量修改寄存器(10功能码),被修改的寄存器起始地址是0xA806(A806),我要修改 2(0002)个寄存器,我接下来要修改的值长度位 0x04(04),第一个寄存器的值要改为 0x000F(000F),第二个寄存器的值要改为 0x0003(0003),为了保证我发的这个报文是没有被人动过的,我告诉02设备,我这个报文的密码是 93 04(CRC校验码),

返回:02 10 A8 06 00 02 81 9A
02(从机地址)+ 10(功能码)+ A806(寄存器起始地址)+ 0002(寄存器个数)+ 819A(CRC校验码)
1byte        + 1byte       + 2byte              +2byte             + 2byte  (总长度固定为8byte)

02:从机地址,这里是发给设备编号为02的从机,占1byte

10:功能码,0x10代表批量修改寄存器功能,占1byte

A806:已经修改的寄存器起始地址,这里寄存器起始地址为 0xA806,占2byte

0002:修改好了的寄存器个数,这里为 0x0002,占2byte

81 9A:CRC校验码,通过CRC算法算的,占2byte

这串报文简单化来讲就是,02设备向主机发送报文,告诉主机我是02设备(02),刚刚执行了批量修改寄存器(0x10),修改后的寄存器地址为 0xA806 (A806 ),修改好的寄存器个数为 0x0002(0002),打开这个报文的密码是 81 9A(CRC校验码)

注:32位的寄存器需要用 0x10功能码进行批量修改,不能用0x6修改单个寄存器的功能码修改

3.3 Modbus-TCP

Modbus-TCP 报文帧的格式和 Modbus-RTU是差不多的,区别就在于 Modbus-TCP 采用的是TCP进行连接,而非串口,报文帧的头部比Modbus-RTU要多了6byte的数据, Modbus-TCP不需要做CRC冗余校验。

Modbus-TCP 的报文头部起始为 :

  • 事物处理标识符:长度2byte,可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文

  • 协议标识符:长度2byte, 0x0000 代表 Modbus-TCP协议

  • 长度:长度2byte,表示接下来的字节长度,单位字节

报文头部,Nodbus-TCP的报文帧在Modbus-RTU 的基础上,在增加了上面 6 byte 的数据

报文尾部,Nodbus-TCP不需要做CRC冗余校验

以 0x03 查询功能码为例

Modbus-RTU发送:02 10 A8 06 00 02 04 00 0F 00 03 93 04
Modbus-TCP发送:00 01 00 00 00 0B 02 10 A8 06 00 02 04 00 0F 00 03 

00 01 00 00 00 0B

00 01:事物处理标识符

00 00:Modbus-TCP协议

00 0B:接下来的数据长度为 11(0x000B) 个

02 10 A8 06 00 02 04 00 0F 00 03:与前面RTU的报文格式差不多,只是少了CRC冗余校验码

3.4 Modbus-ACSSII

一般只需要了解RTU协议,因为Modbus协议的设备都必须有 Modbus-RTU协议,至于ACSLL协议,做个大概了解即可

3.4.1 帧形式

对于RTU协议,比如RTU协议发送一个字节:0x12;ASCLL协议则需要发送2个字节:1个字节代表ASCLL码1,一个代表ASCLL码2,既0x31和0x32才能代表0x12.所以,ASCLL协议的效率比较低。但是,ASCLL更符合串口打印查看,因为串口发送的数据一般都是文本模式(ASCLL)。

但是因为RTU一个字节ASCLL需要两个字节来表示,所以ASCLL发送的数据量是RTU的两倍,ASCLL的效率更低

那么ASCLL码效率更低,数据发送量大为啥还采用这种方式呢?

因为假如你要发送数据0x03,采用RTU方式(16进制发送),计算机终端设备接收到0x03后是不可以显示的,就是不能把0x03打印出来。因为可见字符的ASCLL码是从32-126,不是这个范围以外的显示屏上都看不到,会出现乱码,如果是串口助手的话就会显示口口口口。如果采用ASCLL方式(文本模式发送),就不会出现不可显示和乱码的情况,因为文本模式发送0x03,就是发送ASCLL码0和ASCLL码3.也就是0x30和0x33,是可以正常显示在计算机终端的。所以ASCLL效率虽然低,但方便调试显示。

从上图可以看出:

  1. 比TRU多了起始段 : ,多个结束符 CR,LF
  2. 地址和功能都变成了2个字节
  3. 数据部分更加繁琐,但更符合人们的查看

3.5 CRC冗余校验

主机或子机可用校验码进行判别接收信息是否出错。有时,由于电子噪声或其他一些干扰,信息在传输过程中会发生细微的变化,错误校验码保证了主机或子机对在传送过程中出错的信息不起作用。这样增加了系统的安全和效率。错误校验码采用CRC-16校验方法。

二字节的错误校验码,低字节在前,高字节在后。

    /**
     * @description: 计算 CRC值
     * @author WXP
     * @date: 2024/10/14 13:46
     */
    public static int calculateCRC(byte[] message, int length) {
   
   
        int crc = 0xFFFF;
        for (int i = 0; i < length; i++) {
   
   
            crc ^= (message[i] & 0xFF);
            for (int j = 0; j < 8; j++) {
   
   
                if ((crc & 1) == 1) {
   
   
                    crc = (crc >> 1) ^ 0xA001;
                } else {
   
   
                    crc = crc >> 1;
                }
            }
        }
        return crc;
    }

4. 如何通过代码实现 Modbus协议

底层代码实现比较需要注意的就是高低位的转换,下面是高低位的提取代码

	int hex = 0x5A6B	
	byte high = (byte) (hex >> 8);// 高位 高位右移8位得出高位
    byte low = (byte) (hex & 0xFF);// 低位 0x00FF,高位和 00做&操作留下低位

4.1 Modbus-RTU

需先导入串口通信的包 jSerialComm

<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.9.2</version>
</dependency>
package org.xp;

import com.fazecast.jSerialComm.SerialPort;

import java.util.ArrayList;
import java.util.Arrays;

public class ModbusRTURequestBuilder {
   
   

    private static final int slaveAddress = 2;
    private static final int functionCode = 3;
    private static final int updateSingleFunctionCode = 0x0006;
    private static final int updateMulFunctionCode = 0x0010;
    private static final int startAddress = 0x8000;
    private static final int registerCount = 2;
    private static final int updateData = 0x0010;
    private static final int updateMulData = 0x02;

    public static void main(String[] args) {
   
   
        // 设置通信端口、波特率、数据大小、校验位、停止位
        SerialPort serialPort = SerialPort.getCommPort("COM3");
        serialPort.setBaudRate(9600);
        serialPort.setNumDatabytes(8);
        serialPort.setParity(SerialPort.NO_PARITY);
        serialPort.setNumStopbytes(1);

        /**
         *       03 查询功能
         */
        // 打开串口,传输数据
        if (serialPort.openPort()) {
   
   
            byte[] request = ModbusRTURequestBuilder.buildModbusRTURequest(slaveAddress, functionCode, startAddress, registerCount);
            System.out.println("发送的报文:" + Arrays.toString(request));
            serialPort.writeBytes(request, request.length);
        }

        // 睡眠 100 毫秒,等待从机响应
        try {
   
   
            Thread.sleep(100); // 如果接受到的报文不完整,则增加睡眠时间
        } catch (Exception e) {
   
   
            e.printStackTrace();
        }

        // 接收从机响应,处理返回数据
        byte[] bytes = new byte[5 + registerCount * 2];
        serialPort.readBytes(bytes, bytes.length);
        System.out.println("接收的报文:" + Arrays.toString(bytes));

        // 校验冗余码
        int crc = ModbusRTURequestBuilder.calculateCRC(bytes, bytes.length - 2);

        byte high = (byte) (crc & 0xFF);// 高位
        byte low = (byte) (crc >> 8);// 低位
        System.out.println("冗余码" + high + "," + low)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值