用nmodbus4(.net standard 2.0+),tcp走以太网(modbusipmaster+tcpclient),rtu走串口(modbusrtumaster+serialport,严格匹配波特率等);地址从1起始需减1传参;readcoils仅读输出线圈,输入点用readinputs;异步操作需串行化访问master实例。

怎么选NModbus版本和协议类型
直接说结论:用 NModbus4(.NET Standard 2.0+ 兼容),别碰老版 NModbus(只支持 .NET Framework,已停止维护)。TCP 和 RTU 不是“随便切”的协议——TCP 走以太网,RTU 走串口(RS-485/232),底层通信机制完全不同,不能混用同一套连接对象。
- TCP 场景:PLC 有网口、IP 可配(如西门子 S7-1200、汇川 H3U),用
ModbusIpMaster+TcpClient - RTU 场景:PLC 只有 RS-485 接口(常见于三菱 FX 系列、台达 DVP),用
ModbusRtuMaster+SerialPort,必须严格匹配波特率、数据位、校验位(比如 9600-8-N-1) - 别在 RTU 下误传 TCP 的 IP 地址;也别在 TCP 连接里填 COM3——会直接抛
IOException或卡死在Connect()
读保持寄存器时地址偏移怎么算
Modbus 地址从 1 开始编号(比如 PLC 手册写“40001”),但 NModbus4 的 ReadHoldingRegisters 函数要求从 0 开始——必须减 1。不处理这个偏移,读出来的数据永远错一位,而且错得毫无规律,容易误判为通信不稳定。
- 手册地址 40001 → 实际调用传
0 - 手册地址 40100 → 实际传
99 - 如果读多个寄存器(如 40001~40005),传起始地址
0+ 数量5,不是1和5 - 有些国产 PLC(如信捷 XC 系列)还额外加偏移(比如+1),得查具体文档,不能无脑套公式
为什么 ReadCoils 总返回空数组或超时
最常见原因是功能码不匹配或线圈地址类型搞错:ReadCoils 对应的是离散量输出(Q 点 / DO),而很多人误拿它去读输入点(I 点 / DI)——该用 ReadInputs。另外,TCP 下默认超时是 3 秒,RTU 下更敏感,串口未打开、接线反了、终端电阻没接,都会表现为“读不到数据”,而非报错。
- 读 PLC 输入点(如 I0.0)→ 用
ReadInputs - 读输出点(如 Q0.0)→ 用
ReadCoils - 写单个线圈(如置位 Q0.0)→ 用
WriteSingleCoil,参数是true/false,不是1/0 - RTU 下务必检查
SerialPort的Open()是否已调用,且DataReceived事件没被意外屏蔽
异步读写怎么避免线程阻塞和连接冲突
NModbus4 提供了同步和异步 API,但它的异步方法(如 ReadHoldingRegistersAsync)**不是完全线程安全的**:同一个 IModbusMaster 实例不能并发调用多个读写操作,否则可能抛 ObjectDisposedException 或返回乱序数据。真实产线中,多个传感器轮询必须串行化。
- 用
lock或SemaphoreSlim控制对 master 实例的访问 - 别把
TcpClient或SerialPort包装成单例全局共享——TCP 连接断开后不会自动重连,RTU 串口被其他程序占用会直接失败 - 建议每个通信通道(如一个 PLC)单独持有一个 master 实例,并封装重连逻辑(比如检测到
IOException后主动Dispose并重建) - 调试时关掉 Windows 防火墙——TCP 下它常静默丢包,现象就是“偶尔超时”,很难定位
实际跑起来最难的不是语法,是硬件握手细节:RTU 的收发使能控制、TCP 的 KeepAlive 设置、PLC 端 Modbus 服务是否真正启用(很多厂商默认关闭)、甚至网线质量差导致 CRC 校验频繁失败——这些都不会在 C# 报错里直接体现,得靠 Wireshark 或串口助手交叉验证。











