C#实现Modbus RTU轮询:NModbus与手动编码返回结果不一致排查
为什么NModbus和手动Modbus RTU实现返回结果不同?
咱们直接拆解问题,看看你的手动实现里哪些细节出了问题,导致和NModbus的结果不一致:
1. 最直接的原因:手动代码只读取了1个字节的响应
你的手动实现里调用了 ReadExact(mySerialPort.BaseStream, 1),只读取了1个字节的响应数据。但Modbus RTU读取1个保持寄存器的响应帧是完整的7字节结构:
- 从站ID(1字节,值为0x02)
- 功能码(1字节,值为0x03)
- 数据长度(1字节,值为0x02,因为1个寄存器占2字节)
- 寄存器数据(2字节,值为0x0007,对应十进制7)
- CRC校验(2字节)
你只取了第一个字节,也就是从站ID的0x02,所以输出结果是2——这和NModbus读取的目标寄存器值完全不是一回事。
2. 请求帧的起始地址错误
NModbus里你传入的起始地址是 0x5E4,但手动代码里多做了 startAddress -1 的操作:
byte[] _adr = BitConverter.GetBytes(startAddress - 1);
这会导致你实际请求的寄存器地址变成 0x5E3,而非目标的 0x5E4。即使你修复了响应读取的问题,拿到的也会是错误寄存器的值。Modbus在NModbus中默认使用0基地址,不需要额外减1,这个多余的操作直接导致了请求地址偏移。
3. 额外验证:CRC计算是正确的
你的CRC计算逻辑(初始值0xFFFF,移位异或规则)和字节顺序(低字节在前、高字节在后)都是符合Modbus RTU规范的,这部分没问题。
修复建议
(1)修复响应读取长度
按照Modbus响应帧的结构,读取1个寄存器时应该读取7字节,然后解析数据部分:
byte[] response = ReadExact(mySerialPort.BaseStream, 7); // 解析寄存器值:响应的第3、4字节是大端存储的寄存器数据 ushort registerValue = (ushort)(response[3] << 8 | response[4]); Console.WriteLine("Register = {0}", registerValue);
(2)修复起始地址计算
去掉多余的 -1 操作,直接使用传入的起始地址:
byte[] _adr = BitConverter.GetBytes(startAddress);
这里你用 _adr[1] 作为地址高字节、_adr[0] 作为低字节的处理是正确的,因为Modbus采用大端传输,而BitConverter.GetBytes在小端系统上会返回低字节在前的结果。
结合旧程序的响应验证
你提到旧程序的响应内容是 02 03 05 E4 00 01 02 03 02 00 07,这其实是请求帧+响应帧的组合:
- 请求帧:
02 03 05 E4 00 01 [CRC],其中05 E4就是正确的起始地址0x5E4,00 01是读取长度1 - 响应帧:
02 03 02 00 07 [CRC],其中00 07就是寄存器的正确值,转十进制就是7,和NModbus的结果完全一致。这也验证了NModbus的请求是正确的,而手动实现的问题出在地址偏移和响应读取不完整上。
内容的提问来源于stack exchange,提问作者Tamerlan




