0


ModBus通讯协议(Java代码实例)

一、背景

  1. 什么是Modbus Modbus是在1970年末为可编程逻辑控制器通信开发的,Modbus是一种串行通信协议,目的是用于与PLC设备进行串口通讯,在需要对PLC设备进行数据通讯的时候进行使用。
  2. 为什么要使用Modbus 为什么要使用Modbus协议,因为Modbus协议是modicon公司于1979年为使用PLC通讯发表的,Modbus已经成为工业领域通信协议的业界标准。

二、描述

在工作项目中遇到了需要用的ModBus协议的情况,所以这里记录一下我对ModBus协议的一些理解,首先这里采用实际例子进行解释,自我感觉只有代入实际项目才理解的更快。
1、使用场景:用于与PLC设备进行数据通讯
2、设备名称: IM1281B电能计量模块,也可用JSY-MK-149 电能测量仪
3、硬件通讯协议:RS-485
4、软件通讯协议:Modbus-RTU协议

三、Modbus协议实例

1. 示例

首先Modbus协议一共包括ASCII、RTU、TCP等,此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。
下图是RTU方式的传输格式:

发送数据:01 03 00 48 00 08 C4 1A (抄0048H到004FH共8寄存器)

img

返回数据:01 03 20 00 23 18 60 00 00 05 9D 00 02 FD A0 00 00 00 1E 00 00 02 59 00 00 00 1D 00 00 0C 1C 00 00 13 88 40 3C
解析后得到的结果是:

img

注:命令的下发可使用串口通讯工具或者使用透传的方式进行命令的下发。

2. 发送与返回的数据解析

1.发送解析

上面已经是一个正常的Modbus-RTU通讯应答,现在开始分析这个发送和接受数据是如何进行的。第一步:
根据厂商提供的文档,找到我们需要采集的数据都在哪些寄存器中,如下图所示,我们要的电量数据是存储在寄存器地址 0048H - 004FH这个范围里的,一共8个寄存器。

img

第二步:
开始拼接发送数据,首先我们要知道,
1 Modbus的模式是主从模式,主机发送命令给从机,从机不能主动发送数据给主机。
2 每个从机设备都是自己的设备地址号,这个设备地址号是用于传输时来寻找设备用的,默认地址是1,可以进行修改地址号以区分不同的设备

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5AraxoeH-1680103701984)(https:upload-images.jianshu.io/upload_images/2650372-a7e2cd3a454c2a64.png?imageMogr2/auto-orient/strip|imageView2/2/w/581/format/webp)]

看上图,首先这段命令是一个查询命令,结构是:

//这边使用的是Netty进行命令下发publicvoidchannelActive(ChannelHandlerContext ctx)throwsException{//请求当前设备的设备IDbyte[] deviceIdResult =newbyte[]{0x00,0x03,0x00,0x04,0x00,0x01,(byte)0xC4,0x1A};
    ctx.writeAndFlush(Unpooled.wrappedBuffer(deviceIdResult)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
    ctx.flush();}
2.返回结果解析

01 03 20 00 23 18 60 00 00 05 9D 00 02 FD A0 00 00 00 1E 00 00 02 59 00 00 00 1D 00 00 0C 1C 00 00 13 88 40 3C

img

上图是从机返回的数据,数据段中的数据太多就省略了。

  1. 地址码同上
  2. 功能码同上
  3. 数据段的字节数量:这个是数据段的总字节个数
  4. 数据段:数据段中的数据就是我们想要采集的数据了,只要把这个数据段数据解析后就能得到我们想要的电压,电流等数据了,这边举例解析一个字段,数据的里取4个字节进行解析,取几个字节具体根据厂家文档说明。 从返回数据中取出 00 23 18 60 这四个字节,调用16进制转10进制方法对字节进行转换,得到结果后根据文档要求剩以0.0001得到电压的值,就按这个顺序解析其他的值就可以了,代码如下。
float valtage =(float)hex2decimal("00231860");
System.out.println("电压"+ valtage *0.0001);
结果:电压230.0// 16进制转换10进制publicstaticinthex2decimal(String hex){return Integer.parseInt(hex,16);}
  1. CRC校验:同上

3、校验代码

1. 计算CRC校验码,把校验码之前的数据放入进去,数值前加0x

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

        crc =((ucCRCHi &0x00ff)<<8)|(ucCRCLo &0x00ff)&0xffff;//高低位互换,输出符合相关工具对Modbus CRC16的运算
        crc =((crc &0xFF00)>>8)|((crc &0x00FF)<<8);return String.format("%04X", crc);}

2. CS校验码(如:标准188协议校验方式)

publicbytegetCS(byte[] Abyte){byte result;try{int num =0;for(int i =0; i < Abyte.length; i++){
                    num =(num + Abyte[i])%256;}
                result =(byte) num;}catch(Exception e){
                result =0;}return result;}

2. CRC16校验

/// <summary>/// CRC16校验函数 (一)/// </summary>/// <param name="x"></param>/// <returns></returns>privatestaticbyte[]CRC16(byte[] x){byte[] temdata =newbyte[2];int len = x.Length;UInt16 crc =0;byte da;int i =0;UInt16[] yu ={0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef};while(len--!=0){

                da =(byte)(((byte)(crc /256))/16);
                crc <<=4;
                crc ^= yu[da ^ x[i]/16];
                da =(byte)(((byte)(crc /256))/16);
                crc <<=4;
                crc ^= yu[da ^ x[i]&0x0f];
                i++;}
            temdata[1]=(byte)(crc &0xFF);
            temdata[0]=(byte)(crc >>8);return temdata;}/// <summary>/// CRC16校验类 (二)/// </summary>publicstaticclassCrc16{constushort polynomial =0xA001;staticreadonlyushort[] table =newushort[256];/// <summary>/// 获得校验字符串/// </summary>/// <param name="bytes"></param>/// <returns></returns>publicstaticstringComputeChecksum(byte[] bytes){ushort crc =0;for(int i =0; i < bytes.Length;++i){byte index =(byte)(crc ^ bytes[i]);
                    crc =(ushort)((crc >>8)^ table[index]);}return crc.ToString("X4");}staticCrc16(){ushortvalue;ushort temp;for(ushort i =0; i < table.Length;++i){value=0;
                    temp = i;for(byte j =0; j <8;++j){if(((value^ temp)&0x0001)!=0){value=(ushort)((value>>1)^ polynomial);}else{value>>=1;}
                        temp >>=1;}
                    table[i]=value;}}}

3. CRC32校验

privatestaticUInt32[] crcTable ={0x0,0x77073096,0xee0e612c,0x990951ba,0x76dc419,0x706af48f,0xe963a535,0x9e6495a3,0xedb8832,0x79dcb8a4,0xe0d5e91e,0x97d2d988,0x9b64c2b,0x7eb17cbd,0xe7b82d07,0x90bf1d91,0x1db71064,0x6ab020f2,0xf3b97148,0x84be41de,0x1adad47d,0x6ddde4eb,0xf4d4b551,0x83d385c7,0x136c9856,0x646ba8c0,0xfd62f97a,0x8a65c9ec,0x14015c4f,0x63066cd9,0xfa0f3d63,0x8d080df5,0x3b6e20c8,0x4c69105e,0xd56041e4,0xa2677172,0x3c03e4d1,0x4b04d447,0xd20d85fd,0xa50ab56b,0x35b5a8fa,0x42b2986c,0xdbbbc9d6,0xacbcf940,0x32d86ce3,0x45df5c75,0xdcd60dcf,0xabd13d59,0x26d930ac,0x51de003a,0xc8d75180,0xbfd06116,0x21b4f4b5,0x56b3c423,0xcfba9599,0xb8bda50f,0x2802b89e,0x5f058808,0xc60cd9b2,0xb10be924,0x2f6f7c87,0x58684c11,0xc1611dab,0xb6662d3d,0x76dc4190,0x1db7106,0x98d220bc,0xefd5102a,0x71b18589,0x6b6b51f,0x9fbfe4a5,0xe8b8d433,0x7807c9a2,0xf00f934,0x9609a88e,0xe10e9818,0x7f6a0dbb,0x86d3d2d,0x91646c97,0xe6635c01,0x6b6b51f4,0x1c6c6162,0x856530d8,0xf262004e,0x6c0695ed,0x1b01a57b,0x8208f4c1,0xf50fc457,0x65b0d9c6,0x12b7e950,0x8bbeb8ea,0xfcb9887c,0x62dd1ddf,0x15da2d49,0x8cd37cf3,0xfbd44c65,0x4db26158,0x3ab551ce,0xa3bc0074,0xd4bb30e2,0x4adfa541,0x3dd895d7,0xa4d1c46d,0xd3d6f4fb,0x4369e96a,0x346ed9fc,0xad678846,0xda60b8d0,0x44042d73,0x33031de5,0xaa0a4c5f,0xdd0d7cc9,0x5005713c,0x270241aa,0xbe0b1010,0xc90c2086,0x5768b525,0x206f85b3,0xb966d409,0xce61e49f,0x5edef90e,0x29d9c998,0xb0d09822,0xc7d7a8b4,0x59b33d17,0x2eb40d81,0xb7bd5c3b,0xc0ba6cad,0xedb88320,0x9abfb3b6,0x3b6e20c,0x74b1d29a,0xead54739,0x9dd277af,0x4db2615,0x73dc1683,0xe3630b12,0x94643b84,0xd6d6a3e,0x7a6a5aa8,0xe40ecf0b,0x9309ff9d,0xa00ae27,0x7d079eb1,0xf00f9344,0x8708a3d2,0x1e01f268,0x6906c2fe,0xf762575d,0x806567cb,0x196c3671,0x6e6b06e7,0xfed41b76,0x89d32be0,0x10da7a5a,0x67dd4acc,0xf9b9df6f,0x8ebeeff9,0x17b7be43,0x60b08ed5,0xd6d6a3e8,0xa1d1937e,0x38d8c2c4,0x4fdff252,0xd1bb67f1,0xa6bc5767,0x3fb506dd,0x48b2364b,0xd80d2bda,0xaf0a1b4c,0x36034af6,0x41047a60,0xdf60efc3,0xa867df55,0x316e8eef,0x4669be79,0xcb61b38c,0xbc66831a,0x256fd2a0,0x5268e236,0xcc0c7795,0xbb0b4703,0x220216b9,0x5505262f,0xc5ba3bbe,0xb2bd0b28,0x2bb45a92,0x5cb36a04,0xc2d7ffa7,0xb5d0cf31,0x2cd99e8b,0x5bdeae1d,0x9b64c2b0,0xec63f226,0x756aa39c,0x26d930a,0x9c0906a9,0xeb0e363f,0x72076785,0x5005713,0x95bf4a82,0xe2b87a14,0x7bb12bae,0xcb61b38,0x92d28e9b,0xe5d5be0d,0x7cdcefb7,0xbdbdf21,0x86d3d2d4,0xf1d4e242,0x68ddb3f8,0x1fda836e,0x81be16cd,0xf6b9265b,0x6fb077e1,0x18b74777,0x88085ae6,0xff0f6a70,0x66063bca,0x11010b5c,0x8f659eff,0xf862ae69,0x616bffd3,0x166ccf45,0xa00ae278,0xd70dd2ee,0x4e048354,0x3903b3c2,0xa7672661,0xd06016f7,0x4969474d,0x3e6e77db,0xaed16a4a,0xd9d65adc,0x40df0b66,0x37d83bf0,0xa9bcae53,0xdebb9ec5,0x47b2cf7f,0x30b5ffe9,0xbdbdf21c,0xcabac28a,0x53b39330,0x24b4a3a6,0xbad03605,0xcdd70693,0x54de5729,0x23d967bf,0xb3667a2e,0xc4614ab8,0x5d681b02,0x2a6f2b94,0xb40bbe37,0xc30c8ea1,0x5a05df1b,0x2d02ef8d,};/// <summary>/// 获取CRC32校验字符串/// </summary>/// <param name="bytes"></param>/// <returns>4字节16进制码</returns>publicstaticstringGetCRC32(byte[] bytes){int iCount = bytes.Length;UInt32 crc =0xFFFFFFFF;for(int i =0; i < iCount; i++){
                crc =((crc >>8)&0x00FFFFFF)^ crcTable[(crc ^ bytes[i])&0xFF];}UInt32 temp = crc ^0xFFFFFFFF;return temp.ToString("X8");}

4. BCC异或校验

/// <summary>/// BCC异或取反校验/// </summary>/// <param name="data"></param>/// <returns></returns>publicstaticstringgetBCC(byte[] data){String ret ="";byte[] BCC =newbyte[1];for(int i =0; i < data.Length; i++){
                BCC[0]=(byte)(BCC[0]^ data[i]);}String hex =((~ BCC[0])&0xFF).ToString("X");//取反操作if(hex.Length ==1){
                hex ='0'+ hex;}
            ret += hex.ToUpper();return ret;}

四、学习链接

MODBUS通讯视频教程

常见几种校验方法(CS和校验、CRC16、CRC32、BCC异或校验)

标签: 网络 Java ModBus

本文转载自: https://blog.csdn.net/qq_43842093/article/details/129848024
版权归原作者 Archie_java 所有, 如有侵权,请联系我们删除。

“ModBus通讯协议(Java代码实例)”的评论:

还没有评论