@[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
- 性能码: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);