using System;
using System.IO;
using System.IO.Ports;
using System.Threading.Tasks;
// Modbus RTU 主站类,用于与 Modbus 从站设备进行通信
public class ModbusRTUMaster : IDisposable
{
// 串口对象,用于实际的串行通信
private SerialPort _serialPort;
// 数据缓冲区,用于接收串口数据
private byte[] _buffer = new byte[256];
// 缓冲区索引,用于跟踪当前数据位置
private int _bufferIndex = 0;
// 锁对象,用于线程安全
private readonly object _lock = new object();
// 标记是否已释放资源
private bool _isDisposed = false;
// 默认超时时间
private const int DefaultTimeoutMs = 1000;
// Modbus 功能码枚举
public enum FunctionCode : byte
{
ReadCoils = 0x01,
ReadDiscreteInputs = 0x02,
ReadHoldingRegisters = 0x03,
ReadInputRegisters = 0x04,
WriteSingleCoil = 0x05,
WriteSingleRegister = 0x06,
WriteMultipleCoils = 0x0F,
WriteMultipleRegisters = 0x10,
ExceptionOffset = 0x80
}
/// <summary>
/// 初始化一个新的 Modbus RTU 主站实例
/// </summary>
/// <param name="portName">串口号(如 COM1)</param>
/// <param name="baudRate">波特率</param>
/// <param name="parity">校验方式</param>
/// <param name="dataBits">数据位</param>
/// <param name="stopBits">停止位</param>
public ModbusRTUMaster(string portName, int baudRate = 9600,
Parity parity = Parity.None, int dataBits = 8,
StopBits stopBits = StopBits.One)
{
// 参数校验
if (string.IsNullOrEmpty(portName))
{
throw new ArgumentException("串口号不能为空", nameof(portName));
}
try
{
// 初始化串口
_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
{
ReadTimeout = 1000,
WriteTimeout = 1000,
Handshake = Handshake.None,
DtrEnable = false,
RtsEnable = false
};
// 注册数据接收事件处理程序
_serialPort.DataReceived += OnDataReceived;
// 自动打开串口
if (!_serialPort.IsOpen)
{
_serialPort.Open();
}
}
catch (UnauthorizedAccessException ex)
{
_serialPort = null;
throw new ArgumentException($"访问被拒绝:{ex.Message}");
}
catch (IOException ex)
{
_serialPort = null;
throw new ArgumentException($"I/O 错误:{ex.Message}");
}
catch (Exception ex)
{
_serialPort = null;
throw new ArgumentException($"未知错误:{ex.Message}");
}
}
/// <summary>
/// 获取串口是否已成功打开
/// </summary>
public bool IsOpen => _serialPort?.IsOpen ?? false;
/// <summary>
/// 关闭串口
/// </summary>
public void Close()
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
}
/// <summary>
/// 清空接收缓冲区
/// </summary>
private void ClearBuffer()
{
lock (_lock)
{
_bufferIndex = 0;
}
}
/// <summary>
/// 串口数据接收事件处理程序
/// </summary>
/// <param name="sender">发送方</param>
/// <param name="e">事件参数</param>
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
lock (_lock)
{
while (_serialPort.BytesToRead > 0)
{
if (_bufferIndex >= _buffer.Length)
{
_bufferIndex = 0;
}
_buffer[_bufferIndex++] = (byte)_serialPort.ReadByte();
}
}
}
/// <summary>
/// 构建 Modbus 帧
/// </summary>
/// <param name="slaveAddr">从站地址</param>
/// <param name="funcCode">功能码</param>
/// <param name="data">数据</param>
/// <returns>完整的 Modbus 帧</returns>
private byte[] BuildModbusFrame(byte slaveAddr, byte funcCode, byte[] data)
{
byte[] frame = new byte[data.Length + 4];
frame[0] = slaveAddr;
frame[1] = funcCode;
Array.Copy(data, 0, frame, 2, data.Length);
// 计算CRC
ushort crc = CalculateCRC16(frame, 0, data.Length + 2);
frame[frame.Length - 1] = (byte)(crc & 0xFF);
frame[frame.Length - 2] = (byte)((crc >> 8) & 0xFF);
return frame;
}
/// <summary>
/// 计算 Modbus 协议使用的 CRC16 校验码(多项式为 0xA001)
/// </summary>
/// <param name="data">要计算校验的数据数组</param>
/// <param name="offset">起始偏移量</param>
/// <param name="length">数据长度</param>
/// <returns>计算出的 CRC16 校验值</returns>
private ushort CalculateCRC16(byte[] data, int offset, int length)
{
ushort crc = 0xFFFF;
for (int i = offset; i < offset + length; i++)
{
crc = (ushort)((crc >> 8) ^ CRC16_TABLE[(crc ^ data[i]) & 0xFF]);
}
return (ushort)(((byte)(crc & 0xFF) << 8) | (byte)(crc >> 8));
}
// 预先生成的CRC16查表(多项式0xA001)
private static readonly ushort[] CRC16_TABLE = new ushort[256]
{
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
/// <summary>
/// 读取响应数据
/// </summary>
/// <param name="expectedLength">预期的响应长度</param>
/// <returns>响应数据数组</returns>
private async Task<byte[]> ReadResponseAsync(int expectedLength)
{
if (expectedLength <= 0)
throw new ArgumentException("期望的响应长度必须大于0");
DateTime startTime = DateTime.Now;
lock (_lock)
{
_bufferIndex = 0;
}
while (true)
{
lock (_lock)
{
if (_bufferIndex >= expectedLength + 2)
{
// 检查CRC
ushort crcCalc = CalculateCRC16(_buffer, 0, expectedLength);
ushort crcRecv = (ushort)((_buffer[expectedLength] << 8) | _buffer[expectedLength + 1]);
if (crcCalc == crcRecv)
{
byte[] response = new byte[expectedLength + 2];
Array.Copy(_buffer, response, expectedLength + 2);
ClearBuffer();
return response;
}
else
{
ClearBuffer(); // CRC 错误,丢弃数据
throw new ArgumentException("CRC 校验失败");
}
}
else if ((DateTime.Now - startTime).TotalMilliseconds > DefaultTimeoutMs)
{
ClearBuffer();
throw new ArgumentException("等待 Modbus 响应超时");
}
}
await Task.Delay(1); // 避免CPU占用过高
}
}
/// <summary>
/// 发送请求并接收响应
/// </summary>
/// <param name="request">请求数据</param>
/// <param name="expectedLength">预期的响应长度</param>
/// <returns>响应数据数组</returns>
private async Task<byte[]> SendRequestAsync(byte[] request, int expectedLength)
{
ClearBuffer();
_serialPort.Write(request, 0, request.Length);
return await ReadResponseAsync(expectedLength);
}
/// <summary>
/// 读取线圈状态(功能码01)
/// </summary>
/// <param name="slaveAddr">从站地址</param>
/// <param name="startAddr">起始线圈地址</param>
/// <param name="numCoils">线圈数量</param>
/// <returns>线圈状态数组</returns>
public async Task<bool[]> ReadCoilsAsync(byte slaveAddr, ushort startAddr, ushort numCoils)
{
byte[] data = new byte[4];
data[0] = (byte)(startAddr >> 8);
data[1] = (byte)(startAddr & 0xFF);
data[2] = (byte)(numCoils >> 8);
data[3] = (byte)(numCoils & 0xFF);
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadCoils, data);
byte[] response = await SendRequestAsync(frame, 3 + (numCoils + 7) / 8);
// 校验响应是否来自正确的从站
if (response[0] != slaveAddr)
{
throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
}
if (response[1] == ((byte)FunctionCode.ReadCoils | (byte)FunctionCode.ExceptionOffset))
{
throw new ArgumentException($"Modbus 异常码: {response[2]}");
}
int byteCount = response[2];
bool[] coils = new bool[numCoils];
for (int i = 0; i < numCoils; i++)
{
int byteIndex = i / 8;
int bitIndex = i % 8;
if (byteIndex < byteCount)
{
coils[i] = (response[3 + byteIndex] & (1 << bitIndex)) != 0;
}
}
return coils;
}
/// <summary>
/// 读取离散输入状态(功能码02)
/// </summary>
/// <param name="slaveAddr">从站地址</param>
/// <param name="startAddr">起始输入地址</param>
/// <param name="numInputs">输入数量</param>
/// <returns>离散输入状态数组</returns>
public async Task<bool[]> ReadDiscreteInputsAsync(byte slaveAddr, ushort startAddr, ushort numInputs)
{
byte[] data = new byte[4];
data[0] = (byte)(startAddr >> 8);
data[1] = (byte)(startAddr & 0xFF);
data[2] = (byte)(numInputs >> 8);
data[3] = (byte)(numInputs & 0xFF);
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadDiscreteInputs, data);
byte[] response = await SendRequestAsync(frame, 3 + (numInputs + 7) / 8);
// 校验响应是否来自正确的从站
if (response[0] != slaveAddr)
{
throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
}
if (response[1] == ((byte)FunctionCode.ReadDiscreteInputs | (byte)FunctionCode.ExceptionOffset))
{
throw new ArgumentException($"Modbus 异常码: {response[2]}");
}
int byteCount = response[2];
bool[] inputs = new bool[numInputs];
for (int i = 0; i < numInputs; i++)
{
int byteIndex = i / 8;
int bitIndex = i % 8;
if (byteIndex < byteCount)
{
inputs[i] = (response[3 + byteIndex] & (1 << bitIndex)) != 0;
}
}
return inputs;
}
/// <summary>
/// 读取保持寄存器(功能码03)
/// </summary>
/// <param name="slaveAddr">从机地址</param>
/// <param name="startAddr">起始寄存器地址</param>
/// <param name="numRegs">要读取的寄存器数量</param>
/// <returns>返回读取到的寄存器数据数组</returns>
/// <exception cref="ArgumentException">如果读取寄存器数量为0,则抛出此异常</exception>
/// <exception cref="InvalidOperationException">如果响应数据为空、长度不足、包含异常码或数据长度不符合预期,则抛出此异常</exception>
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddr, ushort startAddr, ushort numRegs)
{
if (numRegs == 0)
throw new ArgumentException("读取寄存器数量不能为0", nameof(numRegs));
byte[] requestPayload = new byte[4];
requestPayload[0] = (byte)(startAddr >> 8);
requestPayload[1] = (byte)(startAddr & 0xFF);
requestPayload[2] = (byte)(numRegs >> 8);
requestPayload[3] = (byte)(numRegs & 0xFF);
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadHoldingRegisters, requestPayload);
byte[] response = await SendRequestAsync(frame, 3 + numRegs * 2);
// 校验响应是否来自正确的从站
if (response[0] != slaveAddr)
{
throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
}
if (response == null || response.Length < 3)
throw new InvalidOperationException("Modbus响应为空或长度不足");
if (response[1] == ((byte)FunctionCode.ReadHoldingRegisters | (byte)FunctionCode.ExceptionOffset))
{
if (response.Length < 3)
throw new InvalidOperationException("Modbus异常响应缺少异常码");
throw new InvalidOperationException($"Modbus 异常码: {response[2]}");
}
int expectedDataLength = 3 + numRegs * 2;
if (response.Length < expectedDataLength)
throw new InvalidOperationException($"Modbus响应数据长度不足,期望{expectedDataLength}字节,实际{response.Length}字节");
ushort[] registers = new ushort[numRegs];
for (int i = 0; i < numRegs; i++)
{
int offset = 3 + i * 2;
registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
}
return registers;
}
/// <summary>
/// 读取输入寄存器(功能码04)
/// </summary>
/// <param name="slaveAttrs">从机地址</param>
/// <param name="startAddr">起始寄存器地址</param>
/// <param name="numRegs">要读取的寄存器数量</param>
/// <returns>返回读取到的寄存器值数组</returns>
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddr, ushort startAddr, ushort numRegs)
{
// 参数合法性检查
if (numRegs == 0 || numRegs > 0x7D)
throw new ArgumentException("读取寄存器数量必须在 1 到 125 之间", nameof(numRegs));
byte[] data = new byte[4];
data[0] = (byte)(startAddr >> 8);
data[1] = (byte)(startAddr & 0xFF);
data[2] = (byte)(numRegs >> 8);
data[3] = (byte)(numRegs & 0xFF);
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadInputRegisters, data);
byte[] response = await SendRequestAsync(frame, 3 + numRegs * 2);
// 检查响应有效性
if (response == null || response.Length < 3)
throw new InvalidOperationException("无效的 Modbus 响应");
// 校验响应是否来自正确的从站
if (response[0] != slaveAddr)
{
throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
}
// 异常码处理
if (response[1] == ((byte)FunctionCode.ReadInputRegisters | (byte)FunctionCode.ExceptionOffset))
{
throw new ArgumentException($"Modbus 异常码: {response[2]}");
}
// 校验响应数据长度
if (response.Length < 3 + response[2])
throw new InvalidOperationException("响应数据长度不足");
// 确保返回的数据长度匹配
if (response[2] != numRegs * 2)
throw new InvalidOperationException("响应寄存器数量不匹配");
// 使用 BlockCopy 提高性能
ushort[] registers = new ushort[numRegs];
Buffer.BlockCopy(response, 3, registers, 0, numRegs * 2);
return registers;
}
/// <summary>
/// 写单个线圈(功能码05)
/// </summary>
/// <param name="slaveAddr">从站地址</param>
/// <param name="coilAddr">线圈地址</param>
/// <param name="value">要写入的值(true 表示 ON,false 表示 OFF)</param>
/// <returns>如果写入成功返回 true</returns>
/// <exception cref="ArgumentException">当从站地址非法时抛出</exception>
/// <exception cref="InvalidOperationException">当 Modbus 帧构建失败或写入失败时抛出</exception>
public async Task<bool> WriteSingleCoilAsync(byte slaveAddr, ushort coilAddr, bool value)
{
// 参数合法性检查
if (slaveAddr < 1 || slaveAddr > 247)
throw new ArgumentException("从站地址必须在 1~247 范围内", nameof(slaveAddr));
// 构建请求数据部分:4字节
byte[] data = new byte[4];
data[0] = (byte)(coilAddr >> 8);
data[1] = (byte)(coilAddr & 0xFF);
data[2] = (byte)(value ? 0xFF : 0x00);
data[3] = 0x00;
// 构建 Modbus 请求帧
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteSingleCoil, data);
if (frame == null)
throw new InvalidOperationException("构建 Modbus 帧失败");
// 发送请求并获取响应
byte[] response = await SendRequestAsync(frame, 6);
// 校验响应是否匹配预期:
// - 非空且长度足够
// - 地址一致
// - 功能码一致
// - 返回的地址与请求一致
if (response != null &&
response.Length >= 6 &&
response[0] == slaveAddr &&
response[1] == (byte)FunctionCode.WriteSingleCoil &&
response[2] == data[0] &&
response[3] == data[1])
{
return true;
}
// 若未满足条件,抛出写入失败异常
throw new InvalidOperationException($"写入线圈失败: 从站={slaveAddr}, 地址={coilAddr}, 值={value}");
}
/// <summary>
/// 写单个寄存器(功能码06)
/// </summary>
/// <param name="slaveAddr">从设备地址</param>
/// <param name="regAddr">寄存器地址</param>
/// <param name="value">要写入的值</param>
/// <returns>写入成功返回true,否则抛出异常</returns>
/// <exception cref="InvalidOperationException">抛出当Modbus帧构建失败、响应为空或长度不足、或写入寄存器失败时</exception>
public async Task<bool> WriteSingleRegisterAsync(byte slaveAddr, ushort regAddr, ushort value)
{
// 准备数据数组,包含寄存器地址和要写入的值
byte[] data = new byte[4];
data[0] = (byte)(regAddr >> 8);
data[1] = (byte)(regAddr & 0xFF);
data[2] = (byte)(value >> 8);
data[3] = (byte)(value & 0xFF);
// 构建Modbus帧
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteSingleRegister, data);
if (frame == null)
{
throw new InvalidOperationException("Modbus帧构建失败");
}
// 发送请求并接收响应
byte[] response = await SendRequestAsync(frame, 6);
if (response == null || response.Length < 6)
{
throw new InvalidOperationException("Modbus响应为空或长度不足");
}
// 可选:检查从站地址是否一致
// 校验响应是否来自正确的从站
if (response[0] != slaveAddr)
{
throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
}
// 验证响应是否与请求一致
if (response[1] == (byte)FunctionCode.WriteSingleRegister &&
response[2] == data[0] &&
response[3] == data[1])
{
return true;
}
// 如果写入失败,抛出异常
throw new InvalidOperationException("写入寄存器失败");
}
/// <summary>
/// 写多个线圈(功能码15)
/// </summary>
/// <param name="slaveAddr">从站地址</param>
/// <param name="startAddr">起始线圈地址</param>
/// <param name="values">要写入的布尔数组(true 表示 ON,false 表示 OFF)</param>
/// <returns>是否写入成功</returns>
public async Task<bool> WriteMultipleCoilsAsync(byte slaveAddr, ushort startAddr, bool[] values)
{
// 参数校验
if (values == null)
throw new ArgumentNullException(nameof(values));
if (values.Length == 0 || values.Length > 255) // Modbus 最大支持线圈数
throw new ArgumentException("线圈数量必须在1到255之间", nameof(values));
int byteCount = (values.Length + 7) / 8;
byte[] data = new byte[5 + byteCount];
// 填充数据字段
data[0] = (byte)(startAddr >> 8);
data[1] = (byte)(startAddr & 0xFF);
data[2] = (byte)(values.Length >> 8);
data[3] = (byte)(values.Length & 0xFF);
data[4] = (byte)byteCount;
for (int i = 0; i < values.Length; i++)
{
if (values[i])
{
data[5 + i / 8] |= (byte)(1 << (i % 8));
}
}
try
{
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteMultipleCoils, data);
byte[] response = await SendRequestAsync(frame, 6);
// 提前保存用于比较的字段
byte expectedStartAddrHigh = data[0];
byte expectedStartAddrLow = data[1];
byte expectedQuantityHigh = data[2];
// 验证响应
if (response != null && response.Length >= 5 &&
response[0] == slaveAddr && // 确保响应来自正确的从站
response[1] == (byte)FunctionCode.WriteMultipleCoils &&
response[2] == expectedStartAddrHigh &&
response[3] == expectedStartAddrLow &&
response[4] == expectedQuantityHigh)
{
return true;
}
throw new ArgumentException("写入多个线圈失败");
}
catch (Exception ex)
{
// 可选记录日志
// Logger.Error("Modbus写入线圈异常: ", ex);
throw new ArgumentException("写入多个线圈失败", ex);
}
}
/// <summary>
/// 写多个寄存器(功能码16)
/// </summary>
/// <param name="slaveAddr">从机地址。</param>
/// <param name="startAddr">起始寄存器地址。</param>
/// <param name="values">要写入的寄存器值数组。</param>
/// <returns>如果写入成功,则返回true;否则抛出ArgumentException异常。</returns>
public async Task<bool> WriteMultipleRegistersAsync(byte slaveAddr, ushort startAddr, ushort[] values)
{
// 准备数据数组,包括起始地址、寄存器数量和所有寄存器的值
byte[] data = new byte[5 + values.Length * 2];
// 设置起始地址的高字节和低字节
data[0] = (byte)(startAddr >> 8);
data[1] = (byte)(startAddr & 0xFF);
// 设置寄存器数量的高字节和低字节
data[2] = (byte)(values.Length >> 8);
data[3] = (byte)(values.Length & 0xFF);
// 设置所有寄存器值的总字节数
data[4] = (byte)(values.Length * 2);
// 遍历每个寄存器值,将其拆分为高字节和低字节,并存入数据数组
for (int i = 0; i < values.Length; i++)
{
data[5 + i * 2] = (byte)(values[i] >> 8);
data[6 + i * 2] = (byte)(values[i] & 0xFF);
}
// 构建Modbus帧并发送请求
byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteMultipleRegisters, data);
byte[] response = await SendRequestAsync(frame, 6);
// 验证响应数据,确保写入操作成功
if (response != null &&
response.Length >= 5 &&
response[1] == (byte)FunctionCode.WriteMultipleRegisters &&
response[2] == data[0] &&
response[3] == data[1] &&
response[4] == data[2])
{
return true;
}
// 如果写入失败,抛出异常
throw new ArgumentException("写入多个寄存器失败");
}
#region IDisposable Support
/// <summary>
/// 释放类的资源。
/// </summary>
/// <param name="disposing">指示是否应释放托管资源。如果为 true,则释放;否则,不释放。</param>
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
// 取消事件订阅并释放 SerialPort 资源
_serialPort.DataReceived -= OnDataReceived;
_serialPort.Dispose();
}
_isDisposed = true;
}
}
/// <summary>
/// 实现 IDisposable 接口,提供标准的释放资源方法。
/// </summary>
public void Dispose()
{
// 调用重载的 Dispose 方法,并指示应释放托管资源
Dispose(true);
// 告诉垃圾回收器不再需要调用终结器(析构器)
GC.SuppressFinalize(this);
}
#endregion
}
我辅助AI完成,并验证单机通信通过。
1497

被折叠的 条评论
为什么被折叠?



