一、简介
什么是协议?
Modbus调试软件--ModbusPoll、ModbusSlave使用详解_modbus slave-CSDN博客
本文全面是学习上面两篇博文
两台PC之间信息传输共同遵守的物理(高低电平) 的标准文件,就是协议,举一个很简单的例子,就是有两个家庭子女谈彩礼,男方(家境一般)感觉20w的彩礼是比较高的,属于高彩礼,但是女方(家庭很好)感觉50w 属于低彩礼,这时候两家都是站在自己的立场看问题,没有一个评判标准,什么是高 什么是低,那么就需要一个有威性的人来中间调和,定一个标准(这个就是大家都能接受的)来评判,这个标准就是协议(以上只是一个假想案例)。
Modbus的简介
Modbus 是一种用于电子设备之间的通信协议,广泛应用于工业自动化和控制系统中。它由 Modicon(现在是施耐德电气的一部分)在1979年开发,最初是为了他们的可编程逻辑控制器(PLC)而设计的
Modbus协议是一种应用层报文传输协议,协议本身并没有定义物理层,定义了控制器能够认识和使用的消息结构,不管它们是经过何种网络进行通信的
Modbus就是一个总线通信协议,像IIC SPI这种,但是他不依赖于硬件总线
Modbus之所以使用广泛,是有他的优点的
Modbus协议标准开放、公开发表且无版权要求
Modbus协议支持多种电气接口,包括RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线等
Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络
Modebus通信过程
模式:一主多从
模式:一主多从
模式:一主多从
Modbus通信中只有一个设备可以发送请求。其他从设备接收主机发送的数据来进行响应也就是说,Modbus不能同步进行通信,主机在同一时间内只能向一个从机发送请求,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信(从机不会自己发送消息给主站,只能回复从主机发送的消息请求)
什么是RS-485 RS-232
RS232,RS485是一种电平标准
RS-232
RS232:是电子工业协会(Electronic Industries Association,EIA) 制定的异步传输标准接口,同时对应着电平标准和通信协议(时序),其电平标准:+3V~+15V对应0,-3V~-15V对应1
RS485
RS485是一种串口接口标准,为了长距离传输采用差分方式传输,传输的是差分信号,抗干扰能力比RS232强很多。两线压差为-2~-6V表示0,两线压差为+2~+6V表示1
Modebus存储区
存储的数据类型可以分为 :布尔量 和 16位寄存器
Modbus协议规定了4个存储区 分别是0 1 3 4区, 其中0区和4区是可读可写,1区和3区是只读
从机地址: 每个从机都有唯一地址,占用一个字节,范围0-255,其中有效范围是1-247,其中255是广播地址(广播就是对所有从机发送应答)
功能码: 占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改从机的数据,所以不同功能码对应不同功能.
数据: 根据功能码不同,有不同功能,比方说功能码是查询从机的数据,这里就是查询数据的地址和查询字节数等。
校验: 在数据传输过程中可能数据会发生错误,CRC检验检测接收的数据是否正确
功能码
Modbus协议类型
Modbus RTU:
这是 Modbus 的一种串行通信形式,使用二进制编码和 RTU模式采用16 位CRC校(循环冗余检验)来保证数据的可靠性。它在串行线路上发送数据,以点对点或多点的方式进行通信 ,传输速度比较慢。十六进制表示数据的方式
Modbus报文帧结构
读取保持寄存器报文解析:
发送报文含义:
16位寄存器=16位=2个字节
16位寄存器=16位=2个字节
16位寄存器=16位=2个字节
功能码: 03 表示读取保存寄存器,而读取保持寄存器的是再4区,地址范围是从40001开始到99999的位置结束
知道了功能码之后,也就是说我们能传输的数据实在4区(40001-99999)上面写入的。
因此数据的起始位置就是 0x6B(十六进制)=6*16+11=107,那么对应的寄存器的位置是:
40001+107=40108 ,
数量高 和数量低 :0X02表示的是数据的个数,也就是说两个数据,
数据的位置就是0x6B 和0x6B+1=0x6C ,也就是我想要从站 寄存器的位置:40108 和40109 的值
返回报文含义
一个字节是8位,那么4个字节就是32位,32位表示用16进制表示就是2个,那么就是两个寄存器
同理:返回的报文的字节分别是02 2B 01 06 ,0x022B(十六进制)就是40108 对应的值
0x0106就是40109 的对应的内容
长度是和要读取的数据有馆:数量高*2+3
案例2
从VB4-VB99 是96个字节,两个字节是一个寄存器 ,那么就是96/2=48个寄存器。
由于开始的位置 是从寄存器的第二个位置开始,那么结束位置就是
48=30(十六进制)
length=48 个寄存器
CRC校验
错误校验(CRC)域占用两个字节包含了一个16位的二进制值。CRC值由传输设备计算出来,然后附加到数据帧上,接收设备在接收数据时重新计算CRC值,然后与接收到的CRC域中的值进行比较,如果这两个值不相等,就发生了错误。
比如主机发出01 06 00 01 00 17 98 04, 98 04 两个字节是校验位,那么从机接收到后要根据01 06 00 01 00 17 再计算CRC校验值,从机判断自己计算出来的CRC校验是否与接收的CRC校验(98 04主机计算的)相等,如果不相等那么说明数据传输有错误,这些数据就不能要。
Modbus ASCII:
这种形式使用 ASCII 字符进行数据编码,较 Modbus RTU 更容易调试,但相对来说效率较低,ASCII格式采用纵向冗余校验的校验和。 LRC校验
Modbus TCP/IP:
这是 Modbus 在以太网上的变体,通过 TCP/IP 协议进行数据传输。它结合了 Modbus 的通信协议和 TCP/IP 的网络功能,适用于局域网或广域网环境。
Modbus-TCP并不需要从从机地址,而是需要MBAP报文头
格局:
二、助手测试与安装
安装VSPD
添加端口
虚拟好端口后,左侧能看到新虚拟出的 COM3 和 COM4,此时两个端口都没有被占用,处于停用状态
安装调试串口助手
三、Serial Port 串口通信测试
收发数据:从com1 发数据 -》com2 来收数据,实现两个端口的互发互收
简介
Tx = 0表示向主站发送数据帧次数,图中为0次;
Err = 0表示通讯错误次数,图中为0次;
ID = 1表示模拟的Modbus子设备的设备地址,图中地址为1;
F = 03表示所使用的Modbus功能码,图中为03功能码;
SR = 1000ms表示发送周期,也就是1S一次。
红字部分,表示当前的错误状态,“No Connection”表示未连接状态
设置
设置从站
设置寄存器数量
用Poll 链接从站
从站-》发消息-》主站
主站-》发消息-》从站
显示:
四、Modbus TCP 通讯
Modbus TCP是不需要虚拟串口的
打开服务端
打开客户端
通讯S->C:
通讯C->S:
五、线圈和寄存器的区别
在 Modbus TCP 协议中,线圈(Coils)和寄存器(Registers)是两种不同的数据对象,它们用于表示和存储设备中的信息。下面是它们的主要区别:
线圈(Coils)
- 数据类型:线圈通常表示设备的开关状态或单个位的数据。它们是二进制的,值只能是 0(关闭)或 1(打开)。
- 地址范围:在 Modbus 协议中,线圈的地址通常从 00001 开始,地址范围是 00001 到 09999。
- 读写:线圈可以被读取和写入。可以通过 Modbus 指令读取线圈的状态,也可以通过指令设置或改变线圈的状态。
- 用途:线圈常用于控制设备的开关状态,例如电机的启停、灯光的开关等。
读线圈和写线圈
- 输出线圈:可读写,用于控制设备的状态(开/关),比如启动或关闭设备。
- 输入线圈:只读,用于读取设备的状态或传感器数据,反映设备的实际情况。
-
输出线圈
- 定义:输出线圈(或简称线圈)通常表示设备的开关状态,是一种可读写的二进制数据。你可以通过 Modbus 协议的指令来读取或写入这些线圈的状态。
- 用途:输出线圈用于控制设备的开关功能,比如打开或关闭电机、灯光等设备。它们的状态可以被修改,以反映设备的操作状态。
- 地址范围:在 Modbus 地址中,输出线圈的地址通常在 00001 到 09999 之间。
- 指令:你可以使用 Modbus 指令如
Write Single Coil
或Write Multiple Coils
来设置输出线圈的状态,并使用Read Coils
指令来读取它们的状态。
输入线圈
- 定义:输入线圈(或简称输入)通常表示设备的输入状态或传感器的状态,它们的值是只读的二进制数据。这些线圈的状态不能通过 Modbus 指令来修改,只能读取。
- 用途:输入线圈用于反映设备的状态或传感器的读数,比如传感器的开/关状态、按钮的按下状态等。它们用于监控设备的实际状态。
- 地址范围:在 Modbus 地址中,输入线圈的地址通常在 10001 到 19999 之间。
- 指令:你可以使用 Modbus 指令如
Read Discrete Inputs
来读取输入线圈的状态,但不能修改它们的值。
发送报文:
接受报文:
code:
/// <summary>
/// 读取输出线圈 功能码 01 所有 0x00000000 0x0006 0x01 0x01 0x00 0x13 0x00 0x1B
/// </summary>
/// <param name="iAddress"> 起始地址 19</param>
/// <param name="iLength">数据的长度 27</param>
/// <returns></returns>
public byte[] ReadOutPutCoils(ushort iAddress,ushort iLength)
{
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
_byteArray.Add(0x06); // 从单位标识开始计数 0x01 0x01 0x00 0x13 0x00 0x1B 表示是6个字节
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x01); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress/256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 长度 计算 1B(十六进制)=27 (十进制 )
_byteArray.Add((byte)(iLength / 256)); // 27/256=0 ->00(十六进制)
_byteArray.Add((byte)(iLength % 256)); // 27%256=27->1B(十六进制)
byte[] respone=null;
// 字节计数
int bytelength = iLength % 8 == 0 ? iLength / 8 : iLength / 8 + 1; //27%8=3 bytelength=4个字节
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array,ref respone))
{
// 验证报文
if (respone.Length==9+ bytelength)
{
// 字节个数是否针对 respone[8] 是前面的东西
if (respone[8]== bytelength && respone[6]==0x01)
{
// 从第10个字节开始才是真正返回的内容
return ByteArray.GetByteArray(respone, 9, bytelength);
// 解析报文
}
}
}
return null;
}
读取输入线圈
功能码:02H
// <summary>
/// 读取输入线圈 功能码 02
/// </summary>
/// <param name="iAddress"> 起始地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public byte[] ReadInPutCoils(ushort iAddress, ushort iLength)
{
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
_byteArray.Add(0x06); // 从单位标识开始计数 0x01 0x01 0x00 0x13 0x00 0x1B 表示是6个字节
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x02); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 长度 计算 1B(十六进制)=27 (十进制 )
_byteArray.Add((byte)(iLength / 256)); // 27/256=0 ->00(十六进制)
_byteArray.Add((byte)(iLength % 256)); // 27%256=27->1B(十六进制)
byte[] respone = null;
// 字节计数
int bytelength = iLength % 8 == 0 ? iLength / 8 : iLength / 8 + 1; //27%8=3 bytelength=4个字节
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文
if (respone.Length == 9 + bytelength)
{
// 字节个数是否针对 respone[8] 是前面的东西
if (respone[8] == bytelength && respone[6] == 0x02)
{
// 从第10个字节开始才是真正返回的内容
return ByteArray.GetByteArray(respone, 9, bytelength);
// 解析报文
}
}
}
return null;
}
写入单线圈:
#region 强制单线圈 功能码 05 预置单线圈
// <summary>
/// 强制单线圈 功能码 05 写入单线圈
/// </summary>
/// <param name="iAddress"> 起始寄存器地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public bool WriteForceCoils(ushort iAddress, bool setValue)
{
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
_byteArray.Add(0x06); // 从单位标识开始计数 0x01 0x01 0x00 0x13 0x00 0x1B 表示是6个字节
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x04); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 设定值
_byteArray.Add(setValue?new byte[] { 0xFF, 0xFF, 0x00, 0x00}:new byte[] { 0x00, 0x00, 0x00, 0x00});
byte[] respone = null;
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文 并解析报文
return ByteArray.AreByteArraysEqual(_byteArray.array,respone);
}
return false;
}
#endregion
写入多个线圈
#region 写入多个线圈 功能码 0F 强制多线圈
// <summary>
/// 写入多个线圈 功能码 0F 强制多线圈
/// </summary>
/// <param name="iAddress"> 起始寄存器地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public bool WriteForceMultColis(ushort iAddress, bool [] setValue)
{
// 0 验证写入的bool 数组是否正确
// 将 bool 值转换为字节数组
byte[] _isSetValue = ByteArray.BoolArrayToByteArray(setValue);
if (_isSetValue==null)
{
return false;
}
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
int _byteLength=7+ _isSetValue.Length;
_byteArray.Add((byte)(_byteLength / 256));
_byteArray.Add((byte)(_byteLength % 256));
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x0F); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 线圈数量
_byteArray.Add((byte)(_isSetValue.Length / 256));
_byteArray.Add((byte)(_isSetValue.Length % 256));
// 字节计数
_byteArray.Add((byte)setValue.Length);
// 字节数组
_byteArray.Add(_isSetValue);
byte[] respone = null;
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文 并解析报文 先截取在替换比较
byte[] _send = ByteArray.GetByteArray(_byteArray.array,0,12);
_send[4] = 0x00;
_send[5] = 0x06;
return ByteArray.AreByteArraysEqual(_send, respone);
}
return false;
}
#endregion
寄存器(Registers)
- 数据类型:寄存器用于存储更复杂的数据,通常是 16 位的整数。寄存器可以表示多种类型的数据,比如模拟量、计数器值、状态码等。
- 地址范围:寄存器的地址通常从 40001 开始,地址范围是 40001 到 49999(此范围表示“保持寄存器”),以及从 30001 到 39999(此范围表示“输入寄存器”)。
- 保持寄存器(Holding Registers):用于存储设备的可写数据,通常用于设置参数或保存变量。
- 输入寄存器(Input Registers):用于存储设备的只读数据,通常用于读取传感器或设备的测量值。
- 读写:保持寄存器可以被读取和写入,而输入寄存器通常只能被读取。
- 用途:寄存器用于存储和读取设备的各种数据,比如温度传感器的值、设置的参数、计数器的读数等。
读取保存寄存器:
发送报文
接收报文
code:
// <summary>
/// 读取保存寄存器 功能码 03
/// </summary>
/// <param name="iAddress"> 起始寄存器地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public byte[] ReadKeepRegisters(ushort iAddress, ushort iLength)
{
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
_byteArray.Add(0x06); // 从单位标识开始计数 0x01 0x01 0x00 0x13 0x00 0x1B 表示是6个字节
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x03); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 长度 计算 1B(十六进制)=27 (十进制 )
_byteArray.Add((byte)(iLength / 256)); // 27/256=0 ->00(十六进制)
_byteArray.Add((byte)(iLength % 256)); // 27%256=27->1B(十六进制)
byte[] respone = null;
// 字节计数
int bytelength = iLength * 2;
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文
if (respone.Length == 9 + bytelength)
{
// 字节个数是否针对 respone[8] 是前面的东西
if (respone[8] == bytelength && respone[6] == 0x03)
{
// 从第10个字节开始才是真正返回的内容
return ByteArray.GetByteArray(respone, 9, bytelength);
// 解析报文
}
}
}
return null;
}
读取输入寄存器:
#region 读取输入寄存器 功能码 04
// <summary>
/// 读取保存寄存器 功能码 03
/// </summary>
/// <param name="iAddress"> 起始寄存器地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public byte[] ReadInPutRegisters(ushort iAddress, ushort iLength)
{
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
_byteArray.Add(0x06); // 从单位标识开始计数 0x01 0x01 0x00 0x13 0x00 0x1B 表示是6个字节
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x04); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 长度 计算 1B(十六进制)=27 (十进制 )
_byteArray.Add((byte)(iLength / 256)); // 27/256=0 ->00(十六进制)
_byteArray.Add((byte)(iLength % 256)); // 27%256=27->1B(十六进制)
byte[] respone = null;
// 字节计数
int bytelength = iLength * 2;
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文
if (respone.Length == 9 + bytelength)
{
// 字节个数是否针对 respone[8] 是前面的东西
if (respone[8] == bytelength && respone[6] == 0x04)
{
// 从第10个字节开始才是真正返回的内容
return ByteArray.GetByteArray(respone, 9, bytelength);
// 解析报文
}
}
}
return null;
}
#endregion
写入单个寄存器
#region 强制单个寄存器 功能码 06 预置寄存器
// <summary>
/// 强制寄存器 功能码 06 写入单个寄存器
/// </summary>
/// <param name="iAddress"> 起始寄存器地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public bool WriteForceRegisters(ushort iAddress, short setValue)
{
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
_byteArray.Add(0x06); // 从单位标识开始计数 0x01 0x01 0x00 0x13 0x00 0x1B 表示是6个字节
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x06); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
// 案例: 19=13(十六进制) 00 13
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 设定值
// 将 short 值转换为字节数组
byte[] byteArrayT = BitConverter.GetBytes(setValue);
_byteArray.Add(byteArrayT);
byte[] respone = null;
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文 并解析报文
return ByteArray.AreByteArraysEqual(_byteArray.array, respone);
}
return false;
}
#endregion
写入多个寄存器
ModbusTCP协议报文解析_modbustcp报文解析-CSDN博客
#region 写入多个寄存器 功能码 10 强制寄存器
// <summary>
/// 写入多个寄存器 功能码 10 强制寄存器
/// </summary>
/// <param name="iAddress"> 起始寄存器地址 </param>
/// <param name="iLength">数据的长度 </param>
/// <returns></returns>
public bool WriteForceMultRegisters(ushort iAddress, byte[] setValue)
{
// 保证寄存器数量是偶数
if (setValue.Length%2==1|| setValue==null|| setValue.Length==0)
{
return false;
}
//1 拼接报文
ByteArray _byteArray = new ByteArray();
// 长度
int _byteLength = 7 + setValue.Length;
_byteArray.Add((byte)(_byteLength / 256));
_byteArray.Add((byte)(_byteLength % 256));
// 事务处理标识符 协议标识符
_byteArray.Add(new byte[] { 0x00, 0x00, 0x00, 0x00 });
// 单元标识符
_byteArray.Add(_SlaveAddress); // 是一个字节
// 添加功能码
_byteArray.Add(0x10); // 也是一个字节
// 起始位置 300 12C =01(起始高) 2C(起始低) 1 44
_byteArray.Add((byte)(iAddress / 256)); // 19 /256=0 300/256=1 (都要转成十六进制)
_byteArray.Add((byte)(iAddress % 256)); // 19 %256=19=13(十六进制) 300/256=44=2C(十六进制)
// 寄存器数量
int _regLength = setValue.Length / 2; // 一个寄存器是2个字节
_byteArray.Add((byte)(iAddress / 256));
_byteArray.Add((byte)(iAddress % 256));
// 字节计数
_byteArray.Add((byte)setValue.Length);
// 字节数组
_byteArray.Add(setValue);
byte[] respone = null;
// 发送报文 返回的是服务器1号输出线圈00020-00046 位置总共27个线圈的状态值 返回的是4个字节数 CD 6B B2 05
if (SendAndReceive(_byteArray.array, ref respone))
{
// 验证报文 并解析报文 先截取在替换比较
byte[] _send = ByteArray.GetByteArray(_byteArray.array, 0, 12);
_send[4] = 0x00;
_send[5] = 0x06;
return ByteArray.AreByteArraysEqual(_send, respone);
}
return false;
}
#endregion
文章评论