基于C#实现Modbus通信及CRC校验

一、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抓包分析
  1. 过滤Modbus协议:tcp.port == 502

  2. 验证帧结构:

    复制代码
    | 地址 | 功能码 | 数据 | 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);
}

八、常见问题解决

  1. CRC校验失败 检查字节序是否正确(Modbus要求小端序) 验证数据帧完整性(是否包含完整的2字节CRC)
  2. 通信超时 调整ReadTimeoutWriteTimeout参数 检查物理连接(串口线/TCP网络)
  3. 数据解析错误 确认寄存器地址和数据类型匹配 使用十六进制工具验证原始数据
相关推荐
qq_1927798718 分钟前
C++模块化编程指南
开发语言·c++·算法
Mr.朱鹏34 分钟前
Nginx路由转发案例实战
java·运维·spring boot·nginx·spring·intellij-idea·jetty
代码村新手36 分钟前
C++-String
开发语言·c++
qq_401700411 小时前
Qt 中文乱码的根源:QString::fromLocal8Bit 和 fromUtf8 区别在哪?
开发语言·qt
EndingCoder2 小时前
案例研究:从 JavaScript 迁移到 TypeScript
开发语言·前端·javascript·性能优化·typescript
Yyyyy123jsjs2 小时前
如何通过免费的外汇API轻松获取实时汇率数据
开发语言·python
白露与泡影2 小时前
2026版Java架构师面试题及答案整理汇总
java·开发语言
历程里程碑2 小时前
滑动窗口---- 无重复字符的最长子串
java·数据结构·c++·python·算法·leetcode·django
qq_229058013 小时前
docker中检测进程的内存使用量
java·docker·容器