@[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);
-
-