c# modbus的TCP数据协议

参考:C#实现MODBUS TCP 通信 第二章 (程序内实现) - 『编程语言区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

modbus通讯协议详解 - 孤情剑客 - 博客园 (cnblogs.com)

只要了解这个modubs的数据格式

常用的命令

功能码(16进制)    功能说明
0x01                          读取输出线圈              1
0x02                          读取输入线圈              2
0x03                          读取保持寄存器            3
0x04                          读取输入寄存器            4
0x05                          写入单个线圈              5
0x06                          写入单个寄存器            6
0x0F                          写入多个线圈              15
0x10                          写入多个寄存器            16

解释

线圈寄存器:实际上就可以类比为开关量(继电器状态),每一个bit对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01  0x05  0x0f   
离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02   
保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。一般对应参数设置,比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10   
输入寄存器:这个和保持寄存器类似,但是也是只支持读而不能写,一般是读取各种实时数据。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04

modubs数据协议格式

1. ModbusRtu的报文格式如下(PDU):
第一部分:从站地址,占1个字节
第二部分:功能码,占1个字节
第三部分:数据部分,占N个字节
第四部分:校验部分,CRC校验,占2个字节

2. ModbusAscii的报文格式如下:
第一部分:开始字符(:)
第二部分:从站地址,占2个字节
第三部分:功能码,占2个字节
第四部分:数据部分,占N个字节
第五部分:校验部分,LRC校验,占2个字节
第六部分:结束字符(CR LF)

3. ModbusTcp的报文格式如下:
第一部分:事务处理标识符,占2个字节
第二部分:协议标识符,占2个字节
第三部分:长度,占2个字节
第四部分:单元标识符,占1个字节
第五部分:功能码,占1个字节
第六部分:数据部分,占N个字节

修改modbus数据的格式(tcp)

1)、0x01:读线圈

    在从站中读1~2000个连续线圈状态,ON=1,OFF=0

请求:MBAP 功能码 起始地址H 起始地址L 数量H 数量L(共12字节)
响应:MBAP 功能码 数据长度 数据(一个地址的数据为1位)
如:在从站0x01中,读取开始地址为0x0002的线圈数据,读0x0008位------------------00 01 00 00 00 06 01 01 00 02 00 08
回:数据长度为0x01个字节,数据为0x01,第一个线圈为ON,其余为OFF------------00 01 00 00 00 04 01 01 01 01

  (2)、0x05:写单个线圈

    将从站中的一个输出写成ON或OFF,0xFF00请求输出为ON,0x000请求输出为OFF

请求:MBAP 功能码 输出地址H 输出地址L 输出值H 输出值L(共12字节)
响应:MBAP 功能码 输出地址H 输出地址L 输出值H 输出值L(共12字节)
如:将地址为0x0003的线圈设为ON---------------00 01 00 00 00 06 01 05 00 03 FF 00
回:写入成功--------------------------------------------00 01 00 00 00 06 01 05 00 03 FF 00
  (3)、0x0F:写多个线圈

    将一个从站中的一个线圈序列的每个线圈都强制为ON或OFF,数据域中置1的位请求相应输出位ON,置0的位请求响应输出为OFF

请求:MBAP 功能码 起始地址H 起始地址L 输出数量H 输出数量L 字节长度 输出值H 输出值L
响应:MBAP 功能码 起始地址H 起始地址L 输出数量H 输出数量L
  (4)、0x02:读离散量输入

    从一个从站中读1~2000个连续的离散量输入状态

请求:MBAP 功能码 起始地址H 起始地址L 数量H 数量L(共12字节)
响应:MBAP 功能码 数据长度 数据(长度:9+ceil(数量/8))
如:从地址0x0000开始读0x0012个离散量输入-------------------------------------------------------------------------------------------------00 01 00 00 00 06 01 02 00 00 00 12
回:数据长度为0x03个字节,数据为0x01 04 00,表示第一个离散量输入和第11个离散量输入为ON,其余为OFF----------00 01 00 00 00 06 01 02 03 01 04 00
  (5)、0x04:读输入寄存器
    从一个远程设备中读1~2000个连续输入寄存器

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)
响应:MBAP 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)
如:读起始地址为0x0002,数量为0x0005的寄存器数据----------------------------00 01 00 00 00 06 01 04 00 02 00 05
回:数据长度为0x0A,第一个寄存器的数据为0x0c,其余为0x00----------------00 01 00 00 00 0D 01 04 0A 00 0C 00 00 00 00 00 00 00 00
  (6)、0x03:读保持寄存器

    从远程设备中读保持寄存器连续块的内容

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)
响应:MBAP 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)
如:起始地址是0x0000,寄存器数量是 0x0003----------------------------------00 01 00 00 00 06 01 03 00 00 00 03
回:数据长度为0x06,第一个寄存器的数据为0x21,其余为0x00-----------00 01 00 00 00 09 01 03 06 00 21 00 00 00 00
  (7)、0x06:写单个保持寄存器
    在一个远程设备中写一个保持寄存器

请求:MBAP 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L(共12字节)
响应:MBAP 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L(共12字节)
如:向地址是0x0000的寄存器写入数据0x000A------------------00 01 00 00 00 06 01 06 00 00 00 0A
回:写入成功--------------------------------------------------------------00 01 00 00 00 06 01 06 00 00 00 0A
  (8)、0x10:写多个保持寄存器

    在一个远程设备中写连续寄存器块(1~123个寄存器)

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L 字节长度 寄存器值(13+寄存器数量×2)
响应:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)
如:向起始地址为0x0000,数量为0x0001的寄存器写入数据,数据长度为0x02,数据为0x000F-----------00 01 00 00 00 09 01 10 00 00 00 01 02 00 0F
回:写入成功------------------------------------------------------------------------------------------------------------------------00 01 00 00 00 06 01 10 00 00 00 01

请求示例

请求内容:00 0B 00 00 00 06 06 01 00 02 00 08
事务处理标识(2字节):00 0B
协议标识符(2字节):00 00
长度(2字节):00 06
单元标识符(1字节):01
功能码(1字节):06
数据地址(2字节):00 02 
数据内容(2字节):00 08
响应内容:00 0B 00 00 00 06 06 01 00 02 00 08
事务处理标识(2字节):00 0B
协议标识符(2字节):00 00
长度(2字节):00 06
单元标识符(1字节):01
功能码(1字节):06
数据地址(2字节):00 02
数据内容(2字节):00 08

 请求测试数据

代码实现

 //拼接
 List<byte> SendCommand = new List<byte>();
 //事物处理标识符
 SendCommand.AddRange(new byte[] { 0x00, 0x0B });
 //协议标识符
 SendCommand.AddRange(new byte[] { 0x00, 0x00 });
 //长度
 SendCommand.AddRange(new byte[] { 0x00, 0x06 });
 //单元标识符
 SendCommand.Add(0x01);
 ////功能码
 //SendCommand.Add(0x03);
 //功能码
 SendCommand.Add(0x06);


 ////起始地址
 //SendCommand.Add((byte)(start / 256));
 //SendCommand.Add((byte)(start % 256));
 ////结束地址-长度
 //SendCommand.Add((byte)(length / 256));
 //SendCommand.Add((byte)(length % 256));

 ////起始地址
 //SendCommand.AddRange(new byte[] { 0x00, 0x00 });
 ////结束地址-长度
 //SendCommand.AddRange(new byte[] { 0x00, 0x0A });

 //起始地址
 SendCommand.AddRange(new byte[] { 0x00, 0x01 });
 //具体数据
 SendCommand.AddRange(new byte[] { 0x00, 0x12 });


 var sendData = Encoding.Default.GetString(SendCommand.ToArray());
 Task.Factory.StartNew(new Action(() =>
 {
     Thread.Sleep(1000);
     _client.SendData(sendData);
 }));

返回的结果处理

 private void HandelByte(byte[] buffer)
 {
     int count = buffer.Count();
     if (buffer[7] == 0x03)//读取多个
     {
         int length = buffer[8] / 2;
         if (count == 9 + 2 * length)
         {
             ShowData(buffer, length);
         }
     }
     else if (buffer[7] == 0x06)//写入单个寄存器
     {
         //解析
         ushort[] data = new ushort[1];
         data[0] = BitConverter.ToUInt16(new byte[] { buffer[11], buffer[10] }, 0);
         Console.WriteLine(data);
     }
 }

 /// <summary>
 /// 
 /// </summary>
 /// <param name="buffer"></param>
 /// <param name="length"></param>
 /// <param name="j"></param>
 private void ShowData(byte[] buffer, int length)
 {
     //解析
     ushort[] data = new ushort[length];
     for (int i = 0; i < data.Length; i++)
     {
         data[i] = BitConverter.ToUInt16(new byte[] { buffer[9 + 2 * i + 1], buffer[9 + 2 * i] }, 0);
     }
     Console.WriteLine(data);
 }

效果图(这样就修改好了一个地址数据)

 

posted @ 2023-09-13 17:54  世人皆萌  阅读(3283)  评论(0编辑  收藏  举报