@[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);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。