串口参数不匹配是90%“读不到数据”的主因,需严格同步波特率、数据位、停止位、校验位;modbus地址需换算(如40001→0);务必使用serialportstream替代serialport;crc不可重复计算,且响应后须手动校验。

串口参数不匹配,90%的“读不到数据”都卡在这儿
不是代码写错了,是设备和电脑根本没对上“暗号”。Modbus RTU通信前必须严丝合缝地同步4个参数:波特率、数据位、停止位、校验位。工业现场绝大多数设备默认用 9600 波特率、8 数据位、1 停止位、Parity.None ——但你不能假设,必须查手册或用 Modbus Poll 实测确认。
- 常见错误现象:
ReadTimeout异常、返回全零、IOException提示“端口未响应” - 最容易被忽略的坑:USB转RS485适配器驱动安装后,设备管理器里显示的是
COM3,但实际在虚拟环境(如com0com)中配成了COM4,程序连错端口却无报错 - 调试建议:先用串口助手发一帧原始字节(例如
01 03 00 00 00 02 C4 0B),看从站是否回数据;能通再换C#跑
用 NModbus4 创建 RTU 主站,别漏掉 SerialPortStream
NModbus4 是目前最稳的开源库,但它对 System.IO.Ports.SerialPort 的线程安全封装不够——尤其在高频率轮询时容易丢帧或抛 InvalidOperationException。官方推荐搭配 SerialPortStream 使用,它内部做了读写锁和缓冲区管理,比原生 SerialPort 更适合工业场景。
- 必须安装两个 NuGet 包:
NModbus4和SerialPortStream(不是System.IO.Ports的内置类) - 初始化不能直接传
SerialPort实例,要这样写:var stream = new SerialPortStream("COM3", 9600);<br>var master = ModbusSerialMaster.CreateRtu(stream); - 如果坚持用原生
SerialPort,务必手动加锁 + 设置ReadTimeout/WriteTimeout≥1000,否则电磁干扰下极易超时
寄存器地址换算错误,40001 不等于协议里的 0x0000
Modbus 协议本身地址是 0-based(从 0 开始),但设备手册和 HMI 界面习惯标成 1-based(比如 “40001” 表示保持寄存器第 1 个)。你调用 ReadHoldingRegisters 时,第二个参数必须是协议地址,不是手册地址。
- 正确换算:
40001 → 0,40010 → 9,40100 → 99;公式为protocolAddress = humanAddress - 40001 - 错误现象:读出来全是 0 或乱值,或者抛
Modbus.SlaveException(从站返回异常码 0x02,表示非法地址) - 功能码也要对得上:
0x03对应保持寄存器(4xxxx),0x01或0x05才对应线圈(0xxxx);混用会直接被从站拒收
CRC 校验失效,不是库的问题,是你没关掉“自动添加”
NModbus4 默认会自动生成并追加 CRC,这是好事;但如果你用 SerialPort 手动拼帧、再用 Write 发送,又让库再算一遍 CRC,结果就是帧尾多出 2 字节,从站直接丢弃整帧——看起来像“发了但没回”。
- 关键判断点:用串口监视工具(如
AccessPort)抓包,看发出的帧末尾是不是有 2 字节 CRC;如果有,且你代码里又手动调用了ModbusChecksum.ComputeCrc16,那一定重复了 - 真实工业环境必须校验:即使用了
NModbus4,也要在收到响应后检查response.Length >= 5 && VerifyCrc(response),否则噪声干扰导致的错帧会悄悄污染数据 - 别信“CRC 自动处理就万事大吉”——总线干扰强的现场(比如变频器旁边),CRC 错误率可能达 1~3%,不校验等于裸奔
工业通信没有“一次配置永久有效”这回事。同一个 COM 口,换台电脑可能驱动映射不同;同一台设备,断电重启后站号可能恢复出厂值;甚至温湿度变化都会影响 RS485 信号质量。所有参数、地址、超时值,都得当变量来管,而不是硬编码进 const。










