0
点赞
收藏
分享

微信扫一扫

Modbus协议与SerialPort端口读写

@[toc]

一、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 只读 1XXXX I/O系统提供这种类型数据 02
      输入寄存器 16-位字 只读 3XXXX I/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
      01 03 00(Hi)00(Lo) 00(Hi)0A(Lo) CS CD
    • 响应
      从站地址 功能码 字节数 寄存器值(1) 寄存器值(2) ...... 寄存器值(20) CRC
      01 03 14 00(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
      01 06 00(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
      01 10 00(Hi)00(Lo) 00(Hi)0A(Lo) 04 0A AB 00 01 XX XX
    • 响应
      从站地址 功能码 写入地址 写入数量 CRC
      01 0F 00(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
      01 01 00(HI) 00(LO) 00(HI) 0A(LO) xx xx
    • 响应
      从站地址 功能码 字节数 输出状态15-8 输出状态15-8 CRC
      01 01 02 00 00 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(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
      01 05 00(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
      01 0F 00(HI) 00(LO) 00(HI) 0A(LO) 02 0A(Hi) AB(Lo) xx xx
    • 响应
      从站地址 功能码 写入地址 写入数量 CRC
      01 0F 00(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 条评论