Modbus协议与SerialPort端口读写

本文深入介绍了Modbus协议,包括其开放性质、接口支持和简单格式。详细讲解了Modbus的分类,如串口RS485和以太网通信方式,并阐述了Modbus协议下的数据存储结构。同时,文章通过实例展示了如何解析和构建Modbus通信报文,涉及读写线圈、寄存器等操作。最后,提供了C#实现Modbus通信的代码示例。
摘要由CSDN通过智能技术生成

一、Modbus协议

  • 概念
    Modbus协议是MODICON(莫迪康)(现施耐德品牌)在1979年开发的,是全球第一个真正用于现场的总线协议。
    Modbus协议是应用于电子控制器的一种通用语言。通过此协议,可以实现控制器相互之间、控制器经由网络和其他设备之间的通信。
  • 特点
    • 标准开放、公开发表、无版税要求、无许可证费(没有费用)
    • 支持多种接口(RS232\RS422\RS485\RJ45);各种传输介质(双绞线,网线)
    • 格式简单、紧凑、通俗易懂,容易上手(好用)
  • Modbus总线通信环境
    • 基本通信
    • 从机编码

二、Modbus协议的分类

  • 分类
    • 串口 RS485(一注多从):ModbusAscii【Ascii字符方式进行发送】、ModbusRTU
    • 以太网(点对点链接)ModbusTCP、ModbusUDP
  • Modbus协议下的数据存储
    • 数据存储中的位、字节byte (8位)、字 word(2个字节,16位)、双字 word(4个字节 32位),C#中的数据显示:数据类型、显示格式
    • 内存分区与功能
      存储区对象类型访问类型存储区标识说明可用功能码
      线圈状态单个bit读写0XXXX通过应用程序改变这种类型数据01 05 15
      输入线圈单个bit只读1XXXXI/O系统提供这种类型数据02
      输入寄存器16-位字只读3XXXXI/O系统提供这种类型数据04
      保持寄存器16-位字读写4XXXX通过应用程序改变这种类型数据03 06 16
    • 操作存储区的命令
      • 功能码:01、02、03、04、05、06、15、16
        在这里插入图片描述

三、Modbus通信报文解读

  • 读寄存器消息帧格式

    • TX:发送 RX:接收
      示例如下: 16进制
           //01:读1号从站保持型寄存器 
           //03:功能码
           //00 00  :起始地址  (高低位)00 00 
           //00 0A  :读取数量  (高低位)00 0A
           //C5 CD:CRC校验
           Tx:000662-01 03 00 00 00 0A C5 CD
           Rx:000663-01 03 14 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DA 85
      
    • 0x03 0x04
      • 请求
        从站地址功能码起始地址读取长度(2byte - > 16bit)CRC
        010300(Hi)00(Lo)00(Hi)0A(Lo)CS CD
      • 响应
        从站地址功能码字节数寄存器值(1)寄存器值(2)寄存器值(20)CRC
        01031400(Hi)00(Lo)00(Hi)00(Lo)00(Hi)00(Lo)XX XX
      • 代码如下
        • 实现方式一
              SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
                              serialPort.Open();
                              Modbus.Device.ModbusMaster modbusMaster = Modbus.Device.ModbusSerialMaster.CreateRtu(serialPort);
                              Task.Run(() =>
                              {
                                  while (true)
                                  {
                                      Task.Delay(5000).Wait();
                                      ushort[] arry = modbusMaster.ReadInputRegisters(1, 0, 2);
                                      Console.WriteLine($"温度:{(arry[1] * 0.1).ToString("#0.0")} ℃");
                                      Console.WriteLine($"湿度:{(arry[0] * 0.1).ToString("#0.0")}%");
                                  }
                              });      
          
        • 实现方式二
             SerialPort serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
              serialPort.Open();
          
              List<byte> bytes = new List<byte>();
              //设备号
              bytes.Add(0x01);
              //功能码
              bytes.Add(0x03);
              //地址  两个字节
              ushort addr = 0;
              bytes.Add((byte)(addr / 256));  //高位
              bytes.Add((byte)(addr % 256)); //低位
                                             //数量
              ushort leng = 2;
              bytes.Add((byte)(leng / 256));  //高位
              bytes.Add((byte)(leng % 256)); //低位
                                             //CRC校验码
              bytes = CRC16(bytes);
              //发送报文
              serialPort.Write(bytes.ToArray(), 0, bytes.Count);
              //接收报文
          
              byte[] data = new byte[2 * 2 + 5];
              serialPort.Read(data, 0, data.Length);
              //解析报文  短整型    01 03 06 00 19 00 19 00 02 6C B1 
              for (int i = 3; i < data.Length - 2; i = i + 2)
              {
                  byte[] vb = new byte[2] { data[i + 1], data[i] };
                  ushort u = BitConverter.ToUInt16(vb);//无符号短整型   BitConverter 为小端处理
                  Console.WriteLine(u);
              }
          
              //解析浮点型    ABCD
              for (int i = 3; i < data.Length - 2; i += 4)
              {
                  var v = data[i + 3];  //D
                  var v1 = data[i + 2];//C
                  var v3 = data[i + 1];//B
                  var v4 = data[i];//A
                  byte[] vb = new byte[4] {
                          data[i + 3],
                          data[i + 2],
                          data[i + 1],
                          data[i]
                      };
                  float aa = BitConverter.ToSingle(vb);
                  //   Console.WriteLine(u);
              }
              static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
              {
                  if (value == null || !value.Any())
                      throw new ArgumentException("");
          
                  //运算
                  ushort crc = crcInit;
                  for (int i = 0; i < value.Count; i++)
                  {
                      crc = (ushort)(crc ^ (value[i]));
                      for (int j = 0; j < 8; j++)
                      {
                          crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                      }
                  }
                  byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                  byte lo = (byte)(crc & 0x00FF);         //低位置
          
                  List<byte> buffer = new List<byte>();
                  buffer.AddRange(value);
                  buffer.Add(lo);
                  buffer.Add(hi);
                  return buffer;
              }
          
  • 写单寄存器消息帧格式

    • 请求与响应
      从站地址功能码写入地址写入值(2)CRC
      010600(Hi)00(Lo)00(Hi)00(Lo)XX XX
    • 代码如下
              SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
              serialPort.Open();
              List<byte> bytes = new List<byte>();
              //设备号
              bytes.Add(0x01);
              //功能码
              bytes.Add(0x06);
              //地址  两个字节
              ushort addr = 3;
              bytes.Add((byte)(addr / 256));  //高位
              bytes.Add((byte)(addr % 256)); //低位
              // 写入寄存器的值
              ushort value = 100;
              bytes.Add((byte)(value / 256));  //高位
              bytes.Add((byte)(value % 256)); //低位
                                              //CRC校验码
              bytes = CRC16(bytes);
              serialPort.Write(bytes.ToArray(), 0, bytes.Count);
      
  • 写多寄存器消息帧格式

    • 请求
      从站地址功能码写入地址写入数量字节数写入值CRC
      011000(Hi)00(Lo)00(Hi)0A(Lo)040A AB 00 01XX XX
    • 响应
      从站地址功能码写入地址写入数量CRC
      010F00(Hi)00(Lo)00(Hi)0A(Lo)XX XX
    • 代码如下
      • 整形数据
               SerialPort serialPort = new SerialPort("COM1",9600,Parity.None,8,StopBits.One);
                serialPort.Open();
                List<byte> list = new List<byte>();
                //设备号
                list.Add(0x01);
                //功能码
                list.Add(0x10);
                //地址
                ushort addr = 0;
                list.Add((byte)(addr / 256));//高位
                list.Add((byte)(addr % 256));//低位 
                //写入多个相同类型的值
                List<ushort> values = new List<ushort>();
                values.Add(111);
                values.Add(item: 222);
                values.Add(333);
                //写入数量 
                list.Add((byte)(values.Count / 256));//高位
                list.Add((byte)(values.Count % 256));//低位 
                //写入字节数 6个字节
                list.Add((byte)(values.Count*2));
                for (int i = 0; i < values.Count; i++)
                {
                    //第一种
                    //list.Add((byte)(values[i] / 256));
                    //list.Add((byte)(values[i] %256));
                    //第二种
                    //list.Add(BitConverter.GetBytes(values[i])[1]);
                    // list.Add(BitConverter.GetBytes(values[i])[0]);
                    //第三种
                    list.AddRange(BitConverter.GetBytes(values[i]).Reverse());
                }
                list = CRC16(list);
                serialPort.Write(list.ToArray(),0, list.Count);        
           //验证检验码
           static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
            {
                if (value == null || !value.Any())
                    throw new ArgumentException("");
        
                //运算
                ushort crc = crcInit;
                for (int i = 0; i < value.Count; i++)
                {
                    crc = (ushort)(crc ^ (value[i]));
                    for (int j = 0; j < 8; j++)
                    {
                        crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                    }
                }
                byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                byte lo = (byte)(crc & 0x00FF);         //低位置
        
                List<byte> buffer = new List<byte>();
                buffer.AddRange(value);
                buffer.Add(lo);
                buffer.Add(hi);
                return buffer;
            }
        
      • 浮点型数据
               SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
                serialPort.Open();
                List<byte> list = new List<byte>();
                //设备号
                list.Add(0x01);
                //功能码
                list.Add(0x10);
                //地址
                ushort addr = 0;
                list.Add((byte)(addr / 256));//高位
                list.Add((byte)(addr % 256));//低位 
                List<float> values = new List<float>();
                values.Add(1.1f);
                values.Add(item: 2.1f);
                values.Add(item: 2.3f);
                //数量
                list.Add((byte)(values.Count * 2 / 256));
                list.Add((byte)(values.Count * 2 % 256));
                //字节长度
                list.Add((byte)(list.Count * 4));
                for (int i = 0; i < values.Count; i++)
                {
                    list.Add(BitConverter.GetBytes(values[i])[3]);  //A
                    list.Add(BitConverter.GetBytes(values[i])[2]); //B
                    list.Add(BitConverter.GetBytes(values[i])[1]);//C
                    list.Add(BitConverter.GetBytes(values[i])[0]);//D
                }
                list = CRC16(list);
                serialPort.Write(list.ToArray(), 0, list.Count);     
                //验证检验码
               static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
                {
                    if (value == null || !value.Any())
                        throw new ArgumentException("");
        
                    //运算
                    ushort crc = crcInit;
                    for (int i = 0; i < value.Count; i++)
                    {
                        crc = (ushort)(crc ^ (value[i]));
                        for (int j = 0; j < 8; j++)
                        {
                            crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                        }
                    }
                    byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                    byte lo = (byte)(crc & 0x00FF);         //低位置
        
                    List<byte> buffer = new List<byte>();
                    buffer.AddRange(value);
                    buffer.Add(lo);
                    buffer.Add(hi);
                    return buffer;
                }
        
  • 线圈状态

    • 读线圈消息帧格式:OXO1,0X02
      • 请求
        从站地址功能码起始地址读取长度CRC
        010100(HI) 00(LO)00(HI) 0A(LO)xx xx
      • 响应
        从站地址功能码字节数输出状态15-8输出状态15-8CRC
        0101020000xx xx
      • 代码如下
                  SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
                  serialPort.Open();
                  List<byte> list = new List<byte>();
                  // 设备名称
                  list.Add(0x01);
                  //功能码
                  list.Add(0x01);
                  //起始地址
                  ushort addr = 0;
                  list.Add((byte)(addr / 256));//高位
                  list.Add((byte)(addr % 256));//低位 
                  //读取寄存器数量
                  ushort leng = 10;
                  list.Add((byte)(leng / 256));  //高位
                  list.Add((byte)(leng % 256)); //低位
                  list = CRC16(list);
                  serialPort.Write(list.ToArray(), 0, list.Count);
        
                  //响应
                  byte[] data = new byte[(int)Math.Ceiling(leng * 1.0 / 8) + 5];
                  serialPort.Read(data, 0, data.Length);
                  List<byte> dataList = new List<byte>();
                  //获取字节数据  2个字节   16 位
                  for (int i = 3; i < data.Length && dataList.Count < (int)Math.Ceiling(leng * 1.0 / 8); i++)
                  {
                      dataList.Add(data[i]);
                  }
                  int count = 0;
                  //字节运算
                  for (int i = 0; i < dataList.Count; i++)
                  { 
                      //按位与运算的方式
                      for (int k = 0; k < 8; k++)
                      {
                          //移位
                          byte temp = (byte)(1 << k % 8);
                          //与运算
                          byte b = (byte)(dataList[i] & temp);
                          //输出结果 
                          Console.WriteLine((dataList[i] & temp) != 0);
                          count++;
                          if (count == leng)
                              break;
                      }
                  }
              //CRC校验码
              static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
              {
                  if (value == null || !value.Any())
                      throw new ArgumentException("");
        
                  //运算
                  ushort crc = crcInit;
                  for (int i = 0; i < value.Count; i++)
                  {
                      crc = (ushort)(crc ^ (value[i]));
                      for (int j = 0; j < 8; j++)
                      {
                          crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                      }
                  }
                  byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                  byte lo = (byte)(crc & 0x00FF);         //低位置
        
                  List<byte> buffer = new List<byte>();
                  buffer.AddRange(value);
                  buffer.Add(lo);
                  buffer.Add(hi);
                  return buffer;
              }
        
    • 写线圈状态帧 0x05
      • 请求
      • 响应
        从站地址功能码写入地址写入值CRC
        010500(HI) 00(LO)FF(HI)/00(HI) 00(Lo)xx xx
      • 代码如下
                  SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
                  serialPort.Open();
                  List<byte> data = new List<byte>();
                  //设备名称
                  data.Add(0x01);
                  //功能码
                  data.Add(0x05);
                  //地址
                  ushort addr = 11;
                  data.Add((byte)(addr/256));//高位
                  data.Add((byte)(addr % 256));//低位
                  //写入值  on:0xFF  0x00 off:0x00 0x00
                  data.Add(0x00);
                  data.Add(0x00);
                  //校验
                  data = CRC16(data);
                  serialPort.Write(data.ToArray(),0, data.Count);     
                  
              //CRC校验码
              static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF)
              {
                  if (value == null || !value.Any())
                      throw new ArgumentException("");
        
                  //运算
                  ushort crc = crcInit;
                  for (int i = 0; i < value.Count; i++)
                  {
                      crc = (ushort)(crc ^ (value[i]));
                      for (int j = 0; j < 8; j++)
                      {
                          crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);
                      }
                  }
                  byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                  byte lo = (byte)(crc & 0x00FF);         //低位置
        
                  List<byte> buffer = new List<byte>();
                  buffer.AddRange(value);
                  buffer.Add(lo);
                  buffer.Add(hi);
                  return buffer;
              }
        
    • 写多线圈状态帧 0x0F
      • 请求
        从站地址功能码写入地址写入数量字节数写入值CRC
        010F00(HI) 00(LO)00(HI) 0A(LO)020A(Hi) AB(Lo)xx xx
      • 响应
        从站地址功能码写入地址写入数量CRC
        010F00(HI) 00(LO)00(HI) 00(LO)xx xx
      • 代码如下
                  SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
                  serialPort.Open();
                  List<byte> data = new List<byte>();
                  //设备名称
                  data.Add(0x01);
                  //功能码
                  data.Add(0x0F);
                  //地址
                  ushort addr = 10;
                  data.Add((byte)(addr / 256));//高位
                  data.Add((byte)(addr % 256));//低位
               
                  //输入值
                  List<bool> state = new List<bool>() { true, false, true, false, true ,true, false, true, false, true };
                  //写入数量  写入多少个寄存器 
                  data.Add((byte)(state.Count / 256));//高位
                  data.Add((byte)(state.Count % 256));//低位 
        
                  byte data1 = 0;
                  List<byte> list = new List< byte > ();
                  int index = 0;
                  //0000 0000 
                  for (int i = 0; i < state.Count; i++)
                  {
                      if (i % 8 == 0)
                          list.Add(0x00);
                      index = list.Count-1;
                      if (state[i])
                      {
                          //移位
                          byte temp = (byte)(1 << (i%8));
                          //或运算
                          list[index]  |=  temp;
                      }
               
                  }
                  //字节数
                  data.Add((byte)list.Count);
                  // 添加
                  data.AddRange(list);
                  //校验
                  data = CRC16(data);
                  serialPort.Write(data.ToArray(),0,data.Count);
        
  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Modbus协议有多个分区,其中包括存储区、功能码和操作存储区的命令。 1. 存储区:Modbus协议定义了不同的存储区对象类型,包括线圈状态、输入线圈、输入寄存器和保持寄存器。每种存储区对象类型都有不同的访问类型和存储区标识。线圈状态和输入线圈是单个bit数据,可以读写。输入寄存器是16位字数据,只读。保持寄存器是16位字数据,可读可写。 2. 功能码:Modbus协议定义了一系列功能码,用于表示不同的操作。常见的功能码包括读取线圈状态、读取输入状态、读取保持寄存器、写单个线圈、写单个保持寄存器等。 3. 操作存储区的命令:Modbus协议中的功能码用于操作不同的存储区对象类型。例如,功能码01表示读取线圈状态,功能码02表示读取输入状态,功能码03表示读取保持寄存器,功能码04表示写单个保持寄存器,功能码05表示写单个线圈,功能码06表示写单个保持寄存器,功能码15表示写多个线圈,功能码16表示写多个保持寄存器。 因此,Modbus协议的分区包括存储区、功能码和操作存储区的命令。这些分区定义了不同的数据存储方式和操作方式,用于实现不同类型的通信和控制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Modbus协议](https://blog.csdn.net/weixin_44881106/article/details/126379273)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [modbus协议简介](https://blog.csdn.net/shui12912/article/details/130039846)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Modbus协议SerialPort端口读写](https://blog.csdn.net/Fu_Shi_rong/article/details/125181199)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值