C#上位机与施耐德PLC通讯
ModbusTCP通讯协议简介
施耐德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!