一、引言
建议先大致了解c# Socket通讯,之后再学习modbusTCP协议
ModbusTCP 协议概念:1996年施耐德公司推出基于以太网TCP/IP的Modbus协议。 Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。报文格式是协议的重点。 TCP 协议 知道格式就完事了,主要是格式里面的数据。
二、ModbusTCP 报文格式–读取保持寄存器
1、ModbusTCP报文格式:报文头+功能码+数据
① 报文头:服务器与客户端之间的“握手”,当一台服务器连接n台客户端,报文头能够保证服务器精准地给其中一客户端发送数据(或者服务器接收到数据,能够明白是哪一台客户端发送的)。
② 功能码:服务器与客户端交流是读取数据 还是写数据
③ 数据:读取的数据内容 或者要写数据的内容(前面一对乱七八糟 就是为了得到这个玩意)
2、报文头
报文头 | 字节长度 | 作用 |
---|---|---|
事务标志符 | 2个字节 | Modbus 请求/响应事务(一般为16进制 00,00 ) |
协议标志符 | 2个字节 | Modbus 协议(一般为00,00) |
长度 标志符 | 2个字节 | 单元标志符+功能码+数据 总共的字节数量(知道字节数,可以知道数据是否丢失) |
单元标志符 | 1个字节 | 总线的从站识别(相当于给每一个客户端一个号码/地址,知道你是谁) |
一般格式为:
事务 | 协议 | 长度 | 单元标志 |
---|---|---|---|
0x00,0x00 | 0x00,0x00 | 0x00,0x06 | 0x01 |
解释:报文格式均为16进制
事务/协议:0x00000000
长度:0x06 — 6个字节–后面的报文的字节总数量 :单元1+功能码1+地址2+线圈数量2
单元标识:0x01 —客户端号码/地址为1
2、功能码
Modbus的操作对象有四种:线圈、离散输入、保持寄存器、输入寄存器。占用一个字节
功能码 | 含义 |
---|---|
0x01 | 读线圈 |
0x05 | 写单个线圈 |
0x0F | 写多个线圈 |
0x02 | 读离散量输入 |
0x04 | 读输入寄存器 |
0x03 | 读保持寄存器 |
0x06 | 写单个保持寄存器 |
0x10 | 写多个保持寄存器 |
不同的操作对象,报文格式不同,这里下面的主要介绍0x03 读保持寄存器----就是可以读取数据。
明白其中一种操作对象报文格式,其它功能码内容类似。
3、数据(寄存器)
读取保持寄存器报文又分为发送报文和返回报文两种
例如当你给服务器发送数据,这个数据格式是按照读保持寄存器报文格式发送的
服务器接收到你发的信息,就明白你是来读取数据的,之后就会回复给你信息,因为你发送很规范,服务器回复你的时候就会按照的你报文格式回复(返回报文格式)
发送报文格式如下:
起始地址低位、起始地址高位、线圈数量高位、线圈数量低位均占一个字节
事务协议缩写 0x00000000,实际为:0x00,0x00,0x00,0x00
长度缩写0x0006,实际为:0x00,0x06
--------------报文头-------------- | 功能 | ------------------------数据-------------------------- |
---|
事务协议 | 长度 | 单元标志 | 功能码 | 起始地址高位 | 起始地址低位 | 数量高位 | 数量低位 |
---|---|---|---|---|---|---|---|
0x00000000 | 0x0006 | 0x01 | 0x03 | 0x00 | 0x6B | 0x00 | 0x02 |
发送报文含义:
读取1号服务器保持寄存器(数据),数据起始地址0x6B=107,对应地址40108,读取寄存器数量0x02=2,即读取1号数据,地址从40108-40109,共两个寄存器数组(4个字节)。
为什么地址是从40000开始,因为读保持寄存器地址为0x03;前面有0x00、0x01、0x02。
返回报文格式如下:
事务协议缩写 0x00000000,实际为:0x00,0x00,0x00,0x00
长度缩写0x0006,实际为:0x00,0x06文章来源:https://uudwc.com/A/3meWn
--------------报文头-------------- | 功能 | ----------------------------------数据----------------------------------- |
---|
事务协议 | 长度 | 单元标志 | 功能码 | 字节数量 | 1寄存器高位 | 1寄存器低位 | 2寄存器高位 | 2寄存器低位 |
---|---|---|---|---|---|---|---|---|
0x00000000 | 0x0007 | 0x01 | 0x03 | 0x04 | 0x00 | 0x01 | 0x00 | 0x02 |
返回报文含义:
返回服务器1号从站保持寄存器40108-40109,共两个寄存器的数值,返回的字节数为4个
40108对应数值为0x0001,40109对应数值为0x00109;转换为10进制分别为1和2;
即读取到数据为1 和 2文章来源地址https://uudwc.com/A/3meWn
三、C# ModbusTCP编程
1、通讯连接
public bool Connect()
{
//实例化
tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
//连接
tcpClient.Connect(System.Net.IPAddress.Parse(IPAddress), Port);
}
catch(Exception ex)
{
//日志处理
return false;
}
return true;
}
2、断开通讯
public void DisConnect()
{
if(tcpClient !=null)
{
tcpClient.Close();
}
}
2、读取保持型寄存器
public ushort[] ReadHoldingRegisters(ushort start,ushort length)
{
//五步走
//1、拼接报文
//集合放整个报文码
List<byte> SendCommand = new List<byte>();
//事务 协议
SendCommand.AddRange(new byte[] { 0, 0, 0, 0 });
//长度--两个字节
SendCommand.AddRange(new byte[] { 0, 6 });
//单元标志符
SendCommand.Add(SlaveId);
//功能码
SendCommand.Add(0x03);
//数据 起始寄存器地址+寄存器数量 两个字节组成
//起始寄存器地址
SendCommand.Add((byte)(start / 256)); //获得高位 一个字节8位 1111 1111为255 寄存器地址由两个字节组成 例如:1234 5678 0000 0000 高位:1234 5678
SendCommand.Add((byte)(start % 256)); //获得低位 1234 5678 0000 0000 低位:0000 0000
//读寄存器数量
SendCommand.Add((byte)(length / 256));
SendCommand.Add((byte)(length% 256));
//2、发送报文
//SendCommand.ToArray() 集合变数值
tcpClient.Send(SendCommand.ToArray());
//3、接收报文
byte[] buffer = new byte[512];
//Receive(buffer) 返回字节的长度
int count = tcpClient.Receive(buffer);
//4、验证报文 读二个寄存器 返回长度7 读三个寄存器返回9
if(count==2*length+9)
{
//获取真正的报文 字节数组的截取
byte[] des = new byte[count];
//将buffer复制给des
Array.Copy(buffer, 0, des, 0, count);
//返回的报文 des[0]--des[5] 前6个字节为 事务2个字节 协议2个字节 长度2个字节
//接下来是 单元标识1个字节 功能码1个字节 字节计数1个字节
//二次验证 des[6]为单位识别码;des[5]为高位识别码 des[7]为功能码 des[8]为字节计数
if (des[6] == SlaveId && des[7] == 0x03&&des[8]==2*length)
{
//5、解析报文
//字节数组截取
byte[] res = new byte[count];
//将des复制给res des[9] 第九位为信息 高位和低位
Array.Copy(des, 9, res, 0, 2 * length);
//解析 BitConverter.ToUInt16()将字节数组转换unint无符号的整数
List<ushort> result = new List<ushort>();
for (int i = 0; i < res.Length; i+=2)
{
result.Add(Convert.ToUInt16( res[i]*256 + res[i+1]));
//result.Add(BitConverter.ToUInt16(res, i));
}
return result.ToArray();
}
}
return null;
}