Tx 发送报文:00 C9 00 00 00 06 01 03 00 00 00 02
Rx 接收报文:00 C9 00 00 00 07 01 03 04 01 4D 00 01
Tx 发送报文:00 C9 00 00 00 06 01 03 00 00 00 02
00 C9 事务处理标识符 2字节
00 00 协议标识符 2字节 固定 00 00
00 06 长度 2字节 表示之后的字节总数 (01 03 00 00 00 02 6个字节)
01 单元标识符 Unit ID 1字节
00 C9 00 00 00 06 01 合起来就是 MPAP报文头
03 功能码 1字节
00 00 00 02 数据部分
00 00 开始地址 2个字节
00 02 数据的长度 2个字节, 此处是读取2个寄存器
Rx 接收报文:00 C9 00 00 00 07 01 03 04 01 4D 00 01
00 C9 事务处理标识符 2字节
00 00 协议标识符 2字节 固定 00 00
00 07 长度 2字节 表示之后的字节总数 ( 01 03 04 01 4D 00 01 7个字节)
01 单元标识符 Unit ID 1字节
00 C9 00 00 00 07 01 合起来就是 MPAP报文头
03 功能码 1字节
04 01 4D 00 01 数据部分
04 字节数 1个字节 2个寄存器 占4个字节
01 4D 数据1
00 01 数据2
保持寄存器的值可以读取也可以修改 ,而输入寄存器的值对于master来说就只能读取。所谓保持寄存器,指的是可以通过通信命令读或者写的寄存器;通常是一些功能控制寄存器或者输出寄存器等。
csharp
public class ModBusTCP
{
public int ushorts2int(ushort[] res)
{
int high = res[0];
int low = res[1];
int value = (high << 16) + low;
return value;
}
public ushort[] int2ushorts(int res)
{
ushort ust1 = (ushort)(res >> 16);
ushort ust2 = (ushort)res;
return new ushort[] { ust1, ust2 };
}
//声明一个Socket对象
private Socket tcpClient;
//单元标识符
public byte SlaveId { get; set; } = 0x01;
/// <summary>
/// 建立连接
/// </summary>
/// <param name="ip">IP地址 </param>
/// <param name="port">端口号</param>
/// <returns></returns>
public bool Connect(string ip, int port)
{
//实例化Socket对象
tcpClient =new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
try
{
//建立连接 三次握手
tcpClient.Connect(IPAddress.Parse(ip), port);
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 断开连接
/// </summary>
public void DisConnect()
{
// 4次握手
tcpClient?.Close();
}
/// <summary>
/// 读取多个数据
/// </summary>
/// <param name="startAdr"></param>
/// <param name="length"></param>
/// <returns></returns>
public byte[] ReadOutputRegisters(ushort startAdr, ushort length)
{
#region 1 拼接报文
List<byte> SendCommand = new List<byte>();
//ModBusTCP 报文 格式 MBAP + 功能码+ 数据部分
// MBAP 事务标识符(2字节) 协议标识符(2字节) 长度(2字节) 单元标识符(1字节)
// 功能码 (1字节)
// 数据部分 起始寄存器地址(2字节) 寄存器长度(2字节)
//=======================报文拼接===============================
//事务标识符 设置固定 0x00 0x00
// 等价 SendCommand.Add(0x00); SendCommand.Add(0x00);
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 协议标识符 设置固定 0x00 0x00
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 长度
SendCommand.AddRange(new byte[] {0x00,0x06});
// 单元标识符 1字节
SendCommand.Add(SlaveId);
//功能码 固定 0x03
SendCommand.Add(0x03);
//数据部分 起始寄存器地址(2字节)
#region ushort 转换字节数组
// % /
//byte Hbyte = (byte)(startAdr / 255);
//byte Lbyte = (byte)(startAdr % 255);
//SendCommand.AddRange(new byte[] { Hbyte,Lbyte});
#endregion
//BitConverter 大小端是颠倒
byte[] temp = BitConverter.GetBytes(startAdr);
SendCommand.AddRange(new byte[] { temp[1], temp[0] });
//数据部分 寄存器长度(2字节)
temp = BitConverter.GetBytes(length);
SendCommand.AddRange(new byte[] { temp[1], temp[0] });
#endregion
#region 2 发送报文
tcpClient.Send(SendCommand.ToArray());
#endregion
#region 3 接收报文
Thread.Sleep(50);
int count = tcpClient.Available;
byte[] buffer= new byte[count];
tcpClient.Receive(buffer,buffer.Length,SocketFlags.None);
#endregion
#region 4 验证报文
//Tx 发送报文: 00 C9 00 00 00 06 01 03 00 00 00 02
//Rx 接收报文: 00 C9 00 00 00 07 01 03 04 01 4D 00 01
// 校验 00 07 01 03 04 这几位
// 00 C9 00 00 00 07 01 03 04 这9位是长度固定的, + 获取寄存器长度length*2 每个寄存器占2字节
if (buffer.Length == 9+length*2)
{
//两位字节转换整数 result_h 高位字节 result_1 地位字节
//result = result_h * 256 + result_1
// 00 07
// 3+length*2 3来源于01 03 04 固定的
if (buffer[4] * 256 + buffer[5]==3+length*2)
{
// 单元标识符 和 功能码
if (buffer[6] == SlaveId && buffer[7] == 0x03)
{
#region 5 解析报文
// 返回的数据字节数,就是 要读取寄存器*2
byte[] result = new byte[length*2];
Array.Copy(buffer,9,result,0,length*2);
return result;
#endregion
}
}
}
return null;
#endregion
}
/// <summary>
/// 写入多数据
/// </summary>
/// <param name="startAdr">开始寄存器地址</param>
/// <param name="length">寄存器长度</param>
/// <param name="dates">具体数据</param>
/// <returns></returns>
public void WriteResisters(ushort startAdr, ushort length,ushort datecount, ushort[] dates)
{
#region 1 拼接报文
List<byte> SendCommand = new List<byte>();
// 事务标识符 2字节 固定 0x00 0x00
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 协议标识符 2字节 固定 0x00 0x00
SendCommand.AddRange(new byte[] { 0x00, 0x00 });
// 长度 2字节 7: 单元标识符1字节+功能码1字节+ 开始寄存器地址2字节 寄存器长度2字节 + 字节长度1字节
ushort[] lens=int2ushorts(2*length+7);
//byte[] start1 = BitConverter.GetBytes(lens[0]);
byte[] len = BitConverter.GetBytes(lens[1]);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(len);
}
SendCommand.AddRange(len);
// 单元标识符 1字节 固定 SlaveId
SendCommand.Add(SlaveId);
// 功能码 写入 多个保持寄存器
SendCommand.Add(0x10);
// 数据部分 开始寄存器地址 寄存器长度 字节长度 具体数据
// 开始寄存器地址
byte[] start = BitConverter.GetBytes(startAdr);
//数据值
List<byte> valueBytes = new List<byte>();
byte[] value;
foreach (ushort b in dates)
{
value= BitConverter.GetBytes(b);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(value);
}
valueBytes.AddRange(value);
}
//根据计算机大小端存储方式进行高低字节转换
if (BitConverter.IsLittleEndian)
{
Array.Reverse(start);
}
SendCommand.AddRange(start);
//寄存器长度 2字节
byte[] registLen = BitConverter.GetBytes(length);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(registLen);
}
SendCommand.AddRange(registLen);
//字节长度 1字节
SendCommand.Add((byte)(datecount%255));
//SendCommand.AddRange(new byte[] { 0,1});
SendCommand.AddRange(valueBytes);
#endregion
#region 2 发送报文
Thread.Sleep(50);
//tcpClient.Send(SendCommand.ToArray(),0,SendCommand.Count(),SocketFlags.Partial);
//tcpClient.Send(SendCommand.ToArray());
//tcpClient.Send(new byte[] { 00, 01, 00, 00, 00, 08, 0xFF, 0x05, 00, 05 , 00 ,01, 01, 01 });
//Rx: 000002 - 00 01 00 00 00 09 01 0A 00 05 00 01 01 00 0F
//事务标识符2字节 1F 97
//协议标识符2字节 00 00
//长度 2字节 00 11
//单元标识符 01
//功能码 10
//起始地址 2字节 00 04
//寄存器长度 2字节 00 02 读2个寄存器
//字节数 寄存器长度*2 04
//字节数组N*2字节 00 06 00 09
//1F 97 00 00 00 11 01 10 00 04 00 02 04 00 06 00 09
// 00 01 00 00 00 11 01 10 00 03 00 02 04 00 02 01 09
//tcpClient.Send(new byte[] { 00, 01, 00, 00, 00, 11, 01, 0x10, 00, 00 ,00 ,02 ,04,01,02, 30 ,39 });
//tcpClient.Send(new byte[] { 00, 01, 00, 00, 00, 11, 01, 0x10, 00, 00 ,00 ,02 ,04,01,02, 30 ,39 });
tcpClient.Send(SendCommand.ToArray());
#endregion
}
}
位操作,写写数据需要吧数据转换 2进制
4种寄存器
线圈寄存器 按位操作 可读可写
离散输入寄存器 按位 读取寄存器
保持寄存器
输入寄存器
》》 按位操作 所以 返回 1个字节