@[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只读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 |
        | --- | --- | --- | --- | --- |
        | 01 | 03 | 00(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 |
        | --- | --- | --- | --- | --- |
        | 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);