Modbus通信协议和Java实现

Modbus是一种广泛应用的串行通信协议,由Modicon公司于1979年发布,现为工业设备间通信的业界标准。它允许最多240个设备在一个网络上通信,支持RTU、ASCII和TCP三种模式。RTU模式中,数据通过线圈和寄存器存储,地址分为标准和扩展。协议报文包含从站地址、功能码、数据和CRC校验。例如,读取输出寄存器的报文格式为:从站地址+功能码+起始地址+长度+CRC校验。CRC校验通过预定义的查找表计算。该协议广泛用于SCADA系统中连接监控计算机和RTU。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Modbus通信协议

介绍

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。

  1. 公开发表并且无版权要求
  2. 易于部署和维护
  3. 对供应商来说,修改移动本地的比特或字节没有很多限制

Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信,举个例子,一个测量温度和湿度的装置,并且将结果发送给计算机。在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

Modbus协议是一种应用层的报文传输协议
同一个父亲,儿子有三种形式

  • RTU
  • ASCII
  • TCP

RTU

目的:通信,本质是读写

存储区

存储区:输入线圈、输出线圈、输入寄存器、输出寄存器
线圈和寄存器表示最小单位,存布尔用线圈 1位 0|1,存数据用寄存器 16位
1位等于1bit
16位代表二进制的16个位,无符号 最小为0, 最大为 FFFF。有符号 最小为-8000 最大为7FFF

比如
输出线圈 0
输入线圈 1
输出寄存器 4
输入寄存器 3

存储区范围:
**标准地址 5位 ** XXXXX 第一位表示哪个存储区
Y XXXX

输出线圈 00001-09999
输入线圈 10001-19999
输出寄存器 40001-49999
输入寄存器 30001-39999
通过第一位就知道是哪个存储区,后面表示第多少个寄存器或者线圈

**扩展地址 6位 **
Y XXXXX

输出线圈 000001-065535
输入线圈 100001-165536
输出寄存器 400001-465536
输入寄存器 300001-365536

最多到65536

通信

批注:
输入端,也就是电源进去的一端
输出端,也就是电源出去的一端
读和写

读输出线圈 01
读输入线圈 02
读输出寄存器 03
读输入寄存器 04

写单个输出线圈 05
写单个输出寄存器 06

写多个输出线圈 15
写多个输出寄存器 16

6种动作-功能-功能码

协议

ModbusRTU/ASCII

规定报文格式:
从站地址(设备编号)(1byte)+ 功能码(1byte) + 数据(N byte) + CRC校验 (2byte)

对于读取来说:
从站地址(设备编号) —区分设备(多个人,先喊名字再说话)找谁
功能码 — 干嘛
数据 — 具体干嘛的细节
校验 —验证

对于写入来说:
从站地址(设备编号) —区分设备(多个人,先喊名字再说话)找谁
功能码 — 干嘛
数据 — 具体干嘛的细节(细节更多,多了写入的具体数值)
校验 —验证

举个栗子:
01 03 00 00 00 02 C4 0B (发送)
01 03 04 01 46 01 3B 5A 59 (回)

01 站地址
03 读输出寄存器
00 00 起始寄存器
00 02 寄存器长度
C4 0B 校验码
从站地址1,读输出寄存器,从0开始,读2个

01 站地址
03 读输出寄存器
04 字节计数
01 46 01 3B 具体4个字节
0146 16进制转换成10进制的结果 326 说明书上会说 0表示湿度 得到后除以10就行,也就是32.6
013B 315温度也就是31.5

5A 59 校验码

校验码代码

 private static readonly byte[] aucCRCHi = {
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40
         };
        private static readonly byte[] aucCRCLo = {
             0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
             0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
             0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
             0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
             0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
             0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
             0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
             0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
             0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
             0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
             0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
             0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
             0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
             0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
             0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
             0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
             0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
             0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
             0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
             0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
             0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
             0x41, 0x81, 0x80, 0x40
         };

        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="pucFrame">字节数组</param>
        /// <param name="usLen">验证长度</param>
        /// <returns>2个字节</returns>
        public  static  byte[] CalculateCRC(byte[] pucFrame, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };
            ushort iIndex;
            while (usLen-- > 0)
            {
                iIndex = (ushort)(res[0] ^ pucFrame[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
                res[1] = aucCRCLo[iIndex];
            }
            return res;
        }

栗子

在这里插入图片描述

在这里插入图片描述

说明书的地址

温度的Modbus地址是多少?40001
湿度?40002
绝对地址

通讯报文的地址,是相对地址。不需要给绝对值了,会出现重复和冲突。从0开始
对于输出寄存器来说 0对应-40001
对于输入寄存器 0对应-30001

交流的时候:
温度数据给我,地址是多少?
直接说40001,既告诉了哪个区,有说了多少个

要知道10进制还是16进制过来

谁要数据,谁解析
主从的,发的是请求,会有回应

实现

Java实现ModbusTCP通信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值