施耐德PLC通讯是基于以太网TCP/IP的Modbus协议其中的TCP,也就是我们所说的ModbusTCP协议。
Modbus协议是一项应用层报文传输协议,其中包括ASCII、RTU、TCP等报文类型。本篇将介绍ModbusTCP通讯协议;我们知道标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master(客户端----主站)/slave(服务端----从站)方式通信。
适用范围:施耐德PLC、各种自定义可以实现ModbusTcp通讯协议的硬件设备。
首先,每一帧Modbus数据包都由以下5个部分组成:
01 03 40 00 00 02 地址 功能码 起始地址高位 起始地址低位 寄存器数量高位 寄存器数量低位
主要介绍一下功能码,如下图所示:
说明:
1、0X01 读线圈
线圈地址范围(00001~09999) bool型值,ON为1 OFF为0
请求:地址 功能码 起始地址H 起始地址L 长度H 长度L
响应:地址 功能码 数据长度 数据包(一个地址的数据为1位)
例如我们读取从起始地址为:0X0001的线圈数据,读取8个
则请求报文内容为:
02 01 00 00 00 06 01 01 00 01 00 08 |----固定----| 发送数据报文长度 地址 功能码 地址 寄存器数量
响应报文内容为:
02 01 00 00 00 04 01 01 01 00 |----固定----| 接受的数据报文长度 地址 功能码 数据长度 数据
将数据0X00转换成二进制字符串00000000,表明所有的线圈状态都是OFF。
2、0X05 写单个线圈
线圈地址范围(00001~09999) bool型值,0xFF00请求输出为ON,0X0000请求输出为OFF
请求:地址 功能码 输出地址H 输出地址L 输出值H 输出值L
响应:地址 功能码 输出地址H 输出地址L 输出值H 输出值L
例如我们将地址为:0X0001的线圈数据,设置为ON
则请求报文内容为:
02 01 00 00 00 06 01 05 00 01 FF 00 |----固定----| 发送数据报文长度 地址 功能码 输出地址 输出值
响应报文内容为:
02 01 00 00 00 06 01 05 00 01 FF 00 |----固定----| 接受的数据报文长度 地址 功能码 输出地址 输出值
下面只介绍报文格式,具体形式大家以此类推即可
3、0X0F 写多个线圈
线圈地址范围(00001~09999) bool型值,0xFF00请求输出为ON,0X0000请求输出为OFF
请求:地址 功能码 起始地址H 起始地址L 输出数量H 输出数量L 字节长度 输出值H 输出值L
响应:地址 功能码 起始地址H 起始地址L 输出数量H 输出数量L
4、0X02 读离散量输入
bool型值,ON=1,OFF=0
请求:地址 功能码 起始地址H 起始地址L 数量H 数量L
响应:地址 功能码 数据长度 数据(9+线圈数量/8)
其中数据长度是有8个位组成一个十六进制字节
5、0X04 读输入寄存器
地址范围(30001~39999) 整型
请求:地址 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L
响应:地址 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)
其中数据长度是两个十六进制字节组成一个INT型数据
6、0X03 读保持寄存器
地址范围(40001~49999) 整型
请求:地址 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L
响应:地址 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)
其中数据长度是两个十六进制字节组成一个INT型数据
7、0X06 写单个保持寄存器
地址范围(40001~49999) 整型
请求:地址 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L
响应:地址 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L
8、0X10 写多个保持寄存器
地址范围(40001~49999) 整型
请求:地址 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L 字节长度 寄存器值(13+寄存器数量×2)
响应:地址 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L
思路:由于TCP交互,我们之前的博客有介绍Socket客户端协议源码,具体链接C#Socket客户端程序源码
由于ModubusTcp使用的是指定的报文协议,因此,我们可以在次基础上进行修改拓展,具体修改细节筒子们可以在客户端源码的基础上进行修改,或者使用下面我写完成的ModbusTcp程序,需要的自提。上源码:
public class ModbusTcpHelper { #region 变量定义 /// <summary> /// 与PLC通信的socket客户端 /// </summary> private Socket socket; /// <summary> /// 是否已连接上PLC,true:已连接上PLC false:未连接 /// </summary> public bool isConnectPLC = false; #endregion #region 连接PLC /// <summary> /// 连接PLC,异步连接 /// </summary> /// <param name="ip"></param> /// <param name="port"></param> /// <returns></returns> public bool ConnectPLC(string ip, int port) { isConnectPLC = false; try { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IAsyncResult asyncResult = socket.BeginConnect(ip, port, CallbackConnect, socket); asyncResult.AsyncWaitHandle.WaitOne(); socket.ReceiveTimeout = 2000;//2000ms无数据接收则超时 Thread.Sleep(600);//异步连接,等待状态返回 return isConnectPLC; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("连接Modbus_TCP失败:" + ex.Message); isConnectPLC = false; } return isConnectPLC; } #endregion #region 异步连接PLC /// <summary> /// 异步连接PLC /// </summary> /// <param name="ar"></param> private void CallbackConnect(IAsyncResult ar) { isConnectPLC = false; try { Socket skt = ar.AsyncState as Socket; skt.EndConnect(ar); isConnectPLC = true; } catch (Exception ex) { //MessageBox.Show("连接Modbus_TCP失败:" + ex.Message); System.Diagnostics.Debug.WriteLine("连接Modbus_TCP失败:" + ex.Message); } } #endregion #region 关闭套接字连接 /// <summary> /// 关闭套接字连接 /// </summary> public void CloseConnect() { if (this.socket != null) { try { this.socket.Close(1000); isConnectPLC = false; } catch { } } } #endregion #region 保持寄存器操作 寄存器范围40001~49999 #region 读取单个保持寄存器值【40001~49999注意高低位】 /// <summary> /// 读取单个保持寄存器值【40001~49999注意高低位】 /// </summary> /// <typeparam name="T">基本的数据类型,如short,int,double等</typeparam> /// <param name="startAddress">起始地址</param> /// <param name="value">返回的具体指</param> /// <returns>true:读取成功 false:读取失败</returns> public bool ReadSingleHoldingRegisterValue<T>(int startAddress, out T value) { value = default(T); if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } if (startAddress < 0 || startAddress > 65535) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~65535之间"); return false; } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); byte wordLength = 0;//读取的地址个数【多少个字Word】 int,float需要两个字 long,double需要四个字 if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte) || typeof(T) == typeof(short) || typeof(T) == typeof(ushort)) { wordLength = 1; } else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float)) { wordLength = 2; } else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double)) { wordLength = 4; } else { //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节)等类型 System.Diagnostics.Debug.WriteLine("读Modbus数据暂不支持其他类型:" + value.GetType()); return false; } byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, addrArray[1], addrArray[0], 0x00, wordLength }; socket.Send(sendBuffer); DisplayBuffer(sendBuffer, sendBuffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; try { //协议错误时 Receive函数将发生异常 int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); //receiveBuffer[8] : 真实数据的字节流总个数 } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } if (typeof(T) == typeof(sbyte)) { byte b = receiveBuffer[10]; sbyte sb = (sbyte)b; value = (T)(object)sb; } else if (typeof(T) == typeof(byte)) { byte b = receiveBuffer[10]; value = (T)(object)b; } else if (typeof(T) == typeof(short)) { short s = BitConverter.ToInt16(new byte[] { receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)s; } else if (typeof(T) == typeof(ushort)) { ushort us = BitConverter.ToUInt16(new byte[] { receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)us; } else if (typeof(T) == typeof(int)) { int i = BitConverter.ToInt32(new byte[] { receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)i; } else if (typeof(T) == typeof(uint)) { uint ui = BitConverter.ToUInt32(new byte[] { receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)ui; } else if (typeof(T) == typeof(long)) { long l = BitConverter.ToInt64(new byte[] { receiveBuffer[16], receiveBuffer[15], receiveBuffer[14], receiveBuffer[13], receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)l; } else if (typeof(T) == typeof(ulong)) { ulong ul = BitConverter.ToUInt64(new byte[] { receiveBuffer[16], receiveBuffer[15], receiveBuffer[14], receiveBuffer[13], receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)ul; } else if (typeof(T) == typeof(float)) { float f = BitConverter.ToSingle(new byte[] { receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)f; } else if (typeof(T) == typeof(double)) { double d = BitConverter.ToDouble(new byte[] { receiveBuffer[16], receiveBuffer[15], receiveBuffer[14], receiveBuffer[13], receiveBuffer[12], receiveBuffer[11], receiveBuffer[10], receiveBuffer[9] }, 0); value = (T)(object)d; } return true; } #endregion #region 读取多个保持寄存器值【40001~49999注意高低位】 /// <summary> /// 读取多个保持寄存器值【40001~49999注意高低位】 /// </summary> /// <param name="startAddress">起始寄存器地址%MW startAddr</param> /// <param name="length">读取的字节个数</param> /// <param name="value">返回的字节流数据</param> /// <returns>true:读取成功 false:读取失败</returns> public bool ReadManyHoldingRegisterValue(int startAddress, int length, out byte[] value) { value = new byte[length]; if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } //读保持寄存器0x03读取的寄存器数量的范围为 1~125。因一个寄存器【一个Word】存放两个字节,因此 字节数组的长度范围 为 1~250 if (length < 1 || length > 250) { System.Diagnostics.Debug.WriteLine("返回的字节数组的长度范围为 1~250"); return false; } if (startAddress < 0 || startAddress > 65535) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~65535之间"); return false; } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); //读取的寄存器个数: 如果length为偶数 则为 length/2 如果length为奇数,则为(length+1)/2。因整数相除,结果不考虑余数,所以如下通用: byte registerCount = (byte)((length + 1) / 2); byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, addrArray[1], addrArray[0], 0x00, registerCount }; socket.Send(sendBuffer); DisplayBuffer(sendBuffer, sendBuffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; try { int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } //接收到的实际数据字节个数 byte receiveLength = receiveBuffer[8]; if (receiveLength != registerCount * 2) { System.Diagnostics.Debug.WriteLine("解析接收数据非法,接收的实际数据长度【不是】读取寄存器数量的2倍"); return false; } value = new byte[receiveLength]; for (int i = 0; i < receiveLength; i++) { value[i] = receiveBuffer[9 + i]; } return true; } #endregion #region 写单个保持寄存器【40001~49999注意高低位】 /// <summary> /// 写单个保持寄存器【40001~49999注意高低位】 /// </summary> /// <typeparam name="T">基本的数据类型,如short,int,double等</typeparam> /// <param name="startAddress">寄存器起始地址,范围:【0x0000~0xFFFF】</param> /// <param name="value">写入的值</param> /// <returns>true:写入成功 false:写入失败</returns> public bool WriteSingleHoldingRegisterValue<T>(int startAddress, T value) { if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } if (startAddress < 0 || startAddress > 65535) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~65535之间"); return false; } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); //sbyte,byte,short,ushort 占用一个寄存器(Word)范围的可以使用功能码0x06:写单个寄存器 //int,long,float,double 需要使用两个或两个以上寄存器,因此只能使用功能码0x10:写多个寄存器 其中int,uint,float占用两个寄存器 long,ulong,double占用四个寄存器 byte[] buffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, addrArray[1], addrArray[0], 0x00, 0x00 }; if (typeof(T) == typeof(sbyte)) { sbyte sb = Convert.ToSByte(value); byte b = (byte)sb; buffer[11] = b; } else if (typeof(T) == typeof(byte)) { byte b = Convert.ToByte(value); buffer[11] = b; } else if (typeof(T) == typeof(short)) { short s = Convert.ToInt16(value); byte[] writeValueArray = BitConverter.GetBytes(s); buffer[10] = writeValueArray[1]; buffer[11] = writeValueArray[0]; } else if (typeof(T) == typeof(ushort)) { ushort us = Convert.ToUInt16(value); byte[] writeValueArray = BitConverter.GetBytes(us); buffer[10] = writeValueArray[1]; buffer[11] = writeValueArray[0]; } else if (typeof(T) == typeof(int)) { int i = Convert.ToInt32(value); byte[] writeValueArray = BitConverter.GetBytes(i); buffer = new byte[17] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 }; buffer[13] = writeValueArray[3]; buffer[14] = writeValueArray[2]; buffer[15] = writeValueArray[1]; buffer[16] = writeValueArray[0]; } else if (typeof(T) == typeof(uint)) { uint ui = Convert.ToUInt32(value); byte[] writeValueArray = BitConverter.GetBytes(ui); buffer = new byte[17] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 }; buffer[13] = writeValueArray[3]; buffer[14] = writeValueArray[2]; buffer[15] = writeValueArray[1]; buffer[16] = writeValueArray[0]; } else if (typeof(T) == typeof(long)) { long l = Convert.ToInt64(value); byte[] writeValueArray = BitConverter.GetBytes(l); buffer = new byte[21] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; buffer[13] = writeValueArray[7]; buffer[14] = writeValueArray[6]; buffer[15] = writeValueArray[5]; buffer[16] = writeValueArray[4]; buffer[17] = writeValueArray[3]; buffer[18] = writeValueArray[2]; buffer[19] = writeValueArray[1]; buffer[20] = writeValueArray[0]; } else if (typeof(T) == typeof(ulong)) { ulong ul = Convert.ToUInt64(value); byte[] writeValueArray = BitConverter.GetBytes(ul); buffer = new byte[21] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; buffer[13] = writeValueArray[7]; buffer[14] = writeValueArray[6]; buffer[15] = writeValueArray[5]; buffer[16] = writeValueArray[4]; buffer[17] = writeValueArray[3]; buffer[18] = writeValueArray[2]; buffer[19] = writeValueArray[1]; buffer[20] = writeValueArray[0]; } else if (typeof(T) == typeof(float)) { float f = Convert.ToSingle(value); byte[] writeValueArray = BitConverter.GetBytes(f); buffer = new byte[17] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 }; buffer[13] = writeValueArray[3]; buffer[14] = writeValueArray[2]; buffer[15] = writeValueArray[1]; buffer[16] = writeValueArray[0]; } else if (typeof(T) == typeof(double)) { double d = Convert.ToDouble(value); byte[] writeValueArray = BitConverter.GetBytes(d); buffer = new byte[21] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x01, 0x10, addrArray[1], addrArray[0], 0x00, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; buffer[13] = writeValueArray[7]; buffer[14] = writeValueArray[6]; buffer[15] = writeValueArray[5]; buffer[16] = writeValueArray[4]; buffer[17] = writeValueArray[3]; buffer[18] = writeValueArray[2]; buffer[19] = writeValueArray[1]; buffer[20] = writeValueArray[0]; } else { //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节)等类型 System.Diagnostics.Debug.WriteLine("写Modbus数据暂不支持其他类型:" + value.GetType()); return false; } try { socket.Send(buffer); DisplayBuffer(buffer, buffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } return true; } #endregion #region 写多个保持寄存器的值【40001~49999需要注意高低位】 /// <summary> /// 写多个保持寄存器的值【40001~49999需要注意高低位】 /// </summary> /// <param name="startAddress">起始地址</param> /// <param name="buffer">要写入的字节数组,buffer数组长度范围:【1~240(0x01~0xF0)】</param> /// <returns>true:写入成功 false:写入失败</returns> public bool WriteManyHoldingRegisterValue(int startAddress, byte[] buffer) { //分奇数个字节、偶数个字节 if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } if (startAddress < 0 || startAddress > 65535) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~65535之间"); return false; } if (buffer == null || buffer.Length < 1 || buffer.Length > 240) { System.Diagnostics.Debug.WriteLine("写连续寄存器块范围:(1 至120 个寄存器)");//每个寄存器将数据分成两字节 return false; } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); //需要写入的寄存器个数 byte registerCount = (byte)((buffer.Length + 1) / 2); //实际写入的字节个数:注意buffer数组的长度为奇数时 需要将最后一个寄存器的高位设置为0 byte writeCount = (byte)(registerCount * 2); byte[] sendBuffer = new byte[13 + writeCount]; sendBuffer[0] = 0x02; sendBuffer[1] = 0x01; sendBuffer[5] = (byte)(7 + writeCount); sendBuffer[6] = 0x01; sendBuffer[7] = 0x10; sendBuffer[8] = addrArray[1]; sendBuffer[9] = addrArray[0]; sendBuffer[11] = registerCount; sendBuffer[12] = writeCount; for (int i = 0; i < writeCount - 2; i++) { sendBuffer[13 + i] = buffer[i]; } //最后两个元素[最后的一个寄存器]的处理 if (buffer.Length % 2 == 1) { //如果是奇数个,需要将最后一个寄存器的高位设置为0 sendBuffer[13 + writeCount - 2] = 0; sendBuffer[13 + writeCount - 1] = buffer[buffer.Length - 1]; } else { //如果是偶数个,则一一对应 sendBuffer[13 + writeCount - 2] = buffer[buffer.Length - 2]; sendBuffer[13 + writeCount - 1] = buffer[buffer.Length - 1]; } try { socket.Send(sendBuffer); DisplayBuffer(sendBuffer, sendBuffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } return true; } #endregion #endregion #region 线圈状态操作 寄存器范围00001~09999 #region 读单个线圈状态(位)00001~09999 /// <summary> /// 读单个线圈状态(位)00001~09999 /// </summary> /// <param name="startAddress"></param> /// <param name="value"></param> /// <returns></returns> public bool ReadSingleCoilStatus(int startAddress, out bool value) { bool[] boolArr = new bool[1] { false }; bool result = ReadManyCoilStatus(startAddress, 1, out boolArr); if (boolArr != null && boolArr.Length == 1) { value = boolArr[0]; } else { value = false; } return result; } #endregion #region 读多个线圈状态(位)00001~09999功能码0X01 /// <summary> /// 读多个线圈状态(位)00001~09999功能码0X01 /// </summary> /// <param name="startAddress">起始地址</param> /// <param name="length">长度</param> /// <param name="value">输出bool型数组</param> /// <returns></returns> public bool ReadManyCoilStatus(int startAddress, int length, out bool[] value) { value = new bool[length]; if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } //读线圈状态0x01读取的范围为 1~2000(0X7D0)。因一个线圈【一个位】 if (length < 1 || length > 2000) { System.Diagnostics.Debug.WriteLine("返回的字节数组的长度范围为 1~2000"); return false; } if (startAddress < 0 || startAddress > 65535) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~65535之间"); return false; } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); //读取的线圈状态个数: byte[] registerCount = BitConverter.GetBytes(length); byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, addrArray[1], addrArray[0], registerCount[1], registerCount[0] }; socket.Send(sendBuffer); DisplayBuffer(sendBuffer, sendBuffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; try { int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } //接收到的实际数据字节个数 byte receiveLength = receiveBuffer[8]; if (receiveLength == 0) { System.Diagnostics.Debug.WriteLine("解析接收数据非法,接收的实际数据长度不是 读取线圈状态(位)数量"); return false; } else { value = new bool[length]; byte[] byteNew = new byte[receiveLength]; for (int i = 0; i < byteNew.Length; i++) { byteNew[i] = receiveBuffer[9 + i]; } string strTemp = byteArrToBinaryString(byteNew); if (strTemp != "") { for (int i = 0; i < length; i++) { value[i] = strTemp.Substring(i, 1) == "1"; } } } return true; } #endregion #region 写单个线圈状态(功能码0X05) /// <summary> /// 写单个线圈状态(功能码0X05) /// </summary> /// <param name="startAddress">起始地址</param> /// <param name="buffer">要写入的整形数组,buffer数组长度范围:【1~1968(0x0001~0x07B0)】</param> /// <returns>true:写入成功 false:写入失败</returns> public bool WriteSingleCoilStatus(int startAddress, int buffer) { if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } if (startAddress < 0 || startAddress > 9999) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~9999之间"); return false; } if (buffer > 1) { System.Diagnostics.Debug.WriteLine("写线圈状态值范围:(0或者1)"); return false; } return WriteManyCoilStatus(startAddress, new int[] { buffer }); } #endregion #region 写多个线圈状态(功能码0X15) /// <summary> /// 写多个线圈状态(功能码0X15) /// </summary> /// <param name="startAddress">起始地址</param> /// <param name="buffer">要写入的整形数组,buffer数组长度范围:【1~1968(0x0001~0x07B0)】</param> /// <returns>true:写入成功 false:写入失败</returns> public bool WriteManyCoilStatus(int startAddress, int[] buffer) { if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } if (startAddress < 0 || startAddress > 9999) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~9999之间"); return false; } if (buffer == null || buffer.Length < 1 || buffer.Length > 1968) { System.Diagnostics.Debug.WriteLine("写线圈状态范围:(1 至1968 个寄存器)"); return false; } for (int i = 0; i < buffer.Length; i++) { if (buffer[i] > 1) { System.Diagnostics.Debug.WriteLine("buffer所有数据值必须为0或者1..."); return false; } } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); byte[] lengthArray = BitConverter.GetBytes((ushort)(buffer.Length)); int intTemp = buffer.Length % 8; int byteTemp = buffer.Length / 8; intTemp = intTemp > 0 ? 1 : 0; //实际写入的线圈状态个数:注意buffer数组的长度 byte[] sendBuffer = new byte[13 + intTemp + byteTemp]; sendBuffer[0] = 0x02; sendBuffer[1] = 0x01; sendBuffer[2] = 0x00; sendBuffer[3] = 0x00; sendBuffer[4] = 0x00; sendBuffer[5] = (byte)(7 + byteTemp + intTemp); sendBuffer[6] = 0x01; sendBuffer[7] = 0x0F; sendBuffer[8] = addrArray[1]; sendBuffer[9] = addrArray[0]; sendBuffer[10] = lengthArray[1]; sendBuffer[11] = lengthArray[0]; sendBuffer[12] = (byte)(byteTemp + intTemp); for (int i = 0; i < intTemp + byteTemp; i++) { string strTemp = ""; if (intTemp == 0) { for (int j = 0; j < 8; j++) { strTemp += buffer[i * 8 + 7 - j]; } } else { if (i < byteTemp) { for (int j = 0; j < 8; j++) { strTemp += buffer[i * 8 + 7 - j]; } } else { for (int j = 0; j < buffer.Length % 8; j++) { strTemp += buffer[buffer.Length - 1 - j]; } } } sendBuffer[13 + i] = GetByteValueFromBinaryStr(strTemp); } try { socket.Send(sendBuffer); DisplayBuffer(sendBuffer, sendBuffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } return true; } #endregion #endregion #region 输入寄存器操作 寄存器范围30001~39999 #region 读取多个输入寄存器值【30001~39999注意高低位】 /// <summary> /// 读取多个输入寄存器值【30001~39999注意高低位】 /// </summary> /// <param name="startAddress">起始寄存器地址</param> /// <param name="length">读取的字节个数</param> /// <param name="value">返回的字节流数据</param> /// <returns>true:读取成功 false:读取失败</returns> public bool ReadManyInputRegisterValue(int startAddress, int length, out byte[] value) { value = new byte[length]; if (socket == null || !socket.Connected) { System.Diagnostics.Debug.WriteLine("socket为空或者尚未建立与PLC_Modbus的连接..."); return false; } //读保持寄存器0x03读取的寄存器数量的范围为 1~125。因一个寄存器【一个Word】存放两个字节,因此 字节数组的长度范围 为 1~250 if (length < 1 || length > 250) { System.Diagnostics.Debug.WriteLine("返回的字节数组的长度范围为 1~250"); return false; } if (startAddress < 0 || startAddress > 65535) { System.Diagnostics.Debug.WriteLine("Modbus的起始地址必须在0~65535之间"); return false; } byte[] addrArray = BitConverter.GetBytes((ushort)startAddress); //读取的寄存器个数: 如果length为偶数 则为 length/2 如果length为奇数,则为(length+1)/2。因整数相除,结果不考虑余数,所以如下通用: byte registerCount = (byte)((length + 1) / 2); byte[] sendBuffer = new byte[12] { 0x02, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, addrArray[1], addrArray[0], 0x00, registerCount }; socket.Send(sendBuffer); DisplayBuffer(sendBuffer, sendBuffer.Length, true); Thread.Sleep(50);//等待50ms byte[] receiveBuffer = new byte[1024]; try { int receiveCount = socket.Receive(receiveBuffer); DisplayBuffer(receiveBuffer, receiveCount, false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("接收Modbus的响应数据异常,请查看发送的报文格式是否有误:" + ex.Message); return false; } //接收到的实际数据字节个数 byte receiveLength = receiveBuffer[8]; if (receiveLength != registerCount * 2) { System.Diagnostics.Debug.WriteLine("解析接收数据非法,接收的实际数据长度【不是】读取寄存器数量的2倍"); return false; } value = new byte[receiveLength]; for (int i = 0; i < receiveLength; i++) { value[i] = receiveBuffer[9 + i]; } return true; } #endregion #endregion #region 读取起始地址开始存储的条码,默认读取最大长度为100的条码字符串 /// <summary> /// 读取起始地址开始存储的条码,默认读取最大长度为100的条码字符串 /// </summary> /// <param name="startAddress">起始地址</param> /// <param name="barcode">返回的条码字符串</param> /// <returns>true:读取成功 false:读取失败</returns> public bool ReadBarcode(int startAddress, out string barcode) { barcode = string.Empty; byte[] dataBuffer = new byte[100]; bool result = ReadManyHoldingRegisterValue(startAddress, 100, out dataBuffer); if (!result) { return false; } List<byte> list = new List<byte>(); for (int i = 0; i < dataBuffer.Length; i += 2) { //因一个寄存器存储的数据 是一个字Word,分成两个字节Byte【高位字节、低位字节】,存储的条码是低位在前,因此每隔两个需要交换顺序 list.Add(dataBuffer[i + 1]); list.Add(dataBuffer[i]); //遇到'\0'后面的数据无效 if (dataBuffer[i] == 0 || dataBuffer[i + 1] == 0) { break; } } byte[] actualBuffer = list.ToArray(); barcode = Encoding.ASCII.GetString(actualBuffer).Trim('\0').Trim(); return result; } #endregion #region 打印Debug发送或接收字节数组信息 /// <summary> /// 打印Debug发送或接收字节数组信息 /// </summary> /// <param name="buffer"></param> /// <param name="count"></param> /// <param name="isSend"></param> public void DisplayBuffer(byte[] buffer, int count, bool isSend) { StringBuilder sb = new StringBuilder(); sb.Append((isSend ? "发送" : "接收到") + "的字节流:\n"); for (int i = 0; i < count; i++) { if (i > 0) { sb.Append(" "); } sb.Append(buffer[i].ToString("X2")); } string content = sb.ToString(); System.Diagnostics.Debug.WriteLine(content); } #endregion #region byte[]Arr转int /// <summary> /// byte[]Arr转int /// </summary> /// <param name="byteIn"></param> /// <returns></returns> private int byteArrToInt(byte[] byteIn) { int value = 0; for (int i = 0; i < 4; i++) { int shift = (4 - 1 - i) * 8; value += (byteIn[i] & 0X000000FF) << shift; } return value; } #endregion #region byteArr转二进制字符串 /// <summary> /// byte转二进制字符串(高位在前) /// </summary> /// <param name="byteIn"></param> /// <returns></returns> private string byteArrToBinaryString(byte[] byteIn) { string result = ""; for (int i = 0; i < byteIn.Length; i++) { string str = Convert.ToString(byteIn[i], 2).PadLeft(8, '0'); for (int j = 0; j < str.Length; j++) { result += str[8 - 1 - j]; } } return result; } #endregion #region 用于写位状态线圈时进行字节写入 private byte GetByteValueFromBinaryStr(string strIn) { int result = 0; if (strIn.Length != 8) { strIn = strIn.PadLeft(8, '0'); } for (int i = 0; i < strIn.Length; i++) { int intTemp = int.Parse(strIn.Substring(i, 1)); result += intTemp * ((int)Math.Pow(2, (7 - i))); } return (byte)result; } #endregion }
下面分享ModbustTcp仿真小工具,需要的筒子们也可以下载,方面我们程序开发完成后可以进行自己模拟测试。
ModbusTcp客户端模拟工具:https://pan.baidu.com/s/1w657nPv_pneWLQZvDAPHYQ
提取码:jnel
ModbusTcp服务端模拟工具:https://pan.baidu.com/s/1GioalFIJEmEWcaorvDyLIg
提取码:0l9e
具体使用大家自行百度,简单易懂。Over!