一、Modbus通信实现
1. 使用NModbus库
csharp
// 安装NuGet包
Install-Package NModbus4
// 基本通信类
public class ModbusClient
{
private IModbusMaster _master;
private SerialPort _serialPort;
public void ConnectRtu(string portName, int baudRate = 9600)
{
_serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
_serialPort.Open();
_master = ModbusSerialMaster.CreateRtu(_serialPort);
_master.Transport.ReadTimeout = 3000;
_master.Transport.WriteTimeout = 3000;
}
public ushort[] ReadHoldingRegisters(byte slaveId, ushort startAddr, ushort count)
{
return _master.ReadHoldingRegisters(slaveId, startAddr, count);
}
public void WriteSingleRegister(byte slaveId, ushort addr, ushort value)
{
_master.WriteSingleRegister(slaveId, addr, value);
}
public void Disconnect()
{
_master?.Close();
_serialPort?.Close();
}
}
二、CRC校验实现
1. CRC16算法实现
csharp
public static class ModbusCrc16
{
private const ushort POLYNOMIAL = 0xA001; // 0x8005反转后的值
private const ushort INITIAL_VALUE = 0xFFFF;
public static byte[] Calculate(byte[] data)
{
ushort crc = INITIAL_VALUE;
foreach (byte b in data)
{
crc ^= b;
for (int i = 0; i < 8; i++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= POLYNOMIAL;
}
else
{
crc >>= 1;
}
}
}
return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}
public static bool Verify(byte[] frame)
{
if (frame.Length < 2) return false;
byte[] data = frame.Take(frame.Length - 2).ToArray();
byte[] receivedCrc = frame.Skip(frame.Length - 2).ToArray();
byte[] calculatedCrc = Calculate(data);
return receivedCrc.SequenceEqual(calculatedCrc);
}
}
2. 带CRC的完整帧构造
csharp
// 构造Modbus RTU请求帧
byte[] BuildRequestFrame(byte slaveId, byte functionCode, ushort startAddr, ushort count)
{
var frame = new List<byte>();
frame.Add(slaveId); // 从站地址
frame.Add(functionCode); // 功能码
frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddr))); // 起始地址
frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)count))); // 寄存器数量
byte[] crc = ModbusCrc16.Calculate(frame.ToArray());
frame.Add(crc[0]); // CRC低字节
frame.Add(crc[1]); // CRC高字节
return frame.ToArray();
}
三、完整通信流程示例
1. 读取保持寄存器
csharp
using (var client = new ModbusClient())
{
try
{
client.ConnectRtu("COM3");
// 构造请求帧
byte[] request = BuildRequestFrame(
slaveId: 1,
functionCode: 0x03, // 读保持寄存器
startAddr: 0x0000,
count: 10
);
// 发送请求并接收响应
byte[] response = client.SendRequest(request);
// 验证CRC
if (!ModbusCrc16.Verify(response))
throw new InvalidDataException("CRC校验失败");
// 解析数据
ushort[] registers = response.Skip(3).Take(20).ToArray()
.Select((b, i) => (ushort)(b << 8 | response[3 + i + 1]))
.ToArray();
Console.WriteLine($"读取到寄存器值: {string.Join(", ", registers)}");
}
finally
{
client.Disconnect();
}
}
四、关键功能扩展
1. 异常处理机制
csharp
public static class ModbusExceptionHandler
{
public static void HandleError(Exception ex)
{
if (ex is TimeoutException)
LogError("通信超时,请检查设备连接");
else if (ex is InvalidDataException)
LogError("数据校验失败,可能设备响应异常");
else
LogError($"未知错误: {ex.Message}");
}
private static void LogError(string message)
{
File.AppendAllText("modbus_errors.log",
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}\n");
}
}
2. 性能优化(查表法)
csharp
public static class ModbusCrc16Optimized
{
private static readonly ushort[] CrcTable = new ushort[256];
static ModbusCrc16Optimized()
{
for (ushort i = 0; i < 256; i++)
{
ushort crc = i;
for (byte j = 0; j < 8; j++)
{
crc = (crc & 1) != 0
? (ushort)((crc >> 1) ^ 0xA001)
: crc >> 1;
}
CrcTable[i] = crc;
}
}
public static ushort Calculate(byte[] data)
{
ushort crc = 0xFFFF;
foreach (byte b in data)
{
crc = (ushort)((crc >> 8) ^ CrcTable[(crc ^ b) & 0xFF]);
}
return crc;
}
}
五、工程实践建议
1. 配置管理
csharp
{
"ModbusConfig": {
"PortName": "COM3",
"BaudRate": 9600,
"Parity": "None",
"DataBits": 8,
"StopBits": 1,
"Timeout": 3000,
"RetryCount": 3
}
}
2. 多线程处理
csharp
public class ModbusPoller
{
private Timer _pollTimer;
private readonly ModbusClient _client;
public ModbusPoller(ModbusClient client)
{
_client = client;
_pollTimer = new Timer(5000);
_pollTimer.Elapsed += async (s, e) => await PollData();
}
private async Task PollData()
{
try
{
var data = await _client.ReadHoldingRegistersAsync(1, 0, 10);
UpdateUI(data);
}
catch (Exception ex)
{
ModbusExceptionHandler.HandleError(ex);
}
}
}
六、调试与验证
1. 使用Wireshark抓包分析
-
过滤Modbus协议:
tcp.port == 502 -
验证帧结构:
| 地址 | 功能码 | 数据 | CRC低 | CRC高 |
2. 单元测试示例
csharp
[TestFixture]
public class ModbusTests
{
[Test]
public void TestCRC16()
{
byte[] testData = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 };
byte[] expectedCrc = { 0x84, 0x0A };
Assert.AreEqual(expectedCrc, ModbusCrc16.Calculate(testData));
}
}
参考代码 C# Modbus通讯及CRC校验计算 www.youwenfan.com/contentcsp/93611.html
七、扩展功能实现
1. 异步通信
csharp
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveId, ushort startAddr, ushort count)
{
return await Task.Run(() =>
_master.ReadHoldingRegisters(slaveId, startAddr, count));
}
2. 批量操作
csharp
public void WriteMultipleRegisters(byte slaveId, ushort startAddr, ushort[] values)
{
_master.WriteMultipleRegisters(slaveId, startAddr, values);
}
八、常见问题解决
- CRC校验失败 检查字节序是否正确(Modbus要求小端序) 验证数据帧完整性(是否包含完整的2字节CRC)
- 通信超时 调整
ReadTimeout和WriteTimeout参数 检查物理连接(串口线/TCP网络) - 数据解析错误 确认寄存器地址和数据类型匹配 使用十六进制工具验证原始数据