(八)YModbus异常、超时、重试和原始报文诊断 GitHub 项目地址https://github.com/lidecong133/YModbusModbus 程序好写的一面是读写方法都很直接。难的一面是失败以后你得判断到底哪一层出了问题。现场最常见的说法是“通讯不通”。但这句话太粗了。TCP 连不上、RTU 没响应、设备返回异常码、地址读错、字节序不对都会被说成不通。写通讯库时成功路径只是基本功。真正能帮调试的是失败时把信息露出来。先把失败分层我一般把问题分成三层看。第一层是链路问题。比如 IP 不通、端口没开、串口打不开、线断了、响应超时。这类问题通常还没走到 Modbus 业务。第二层是协议问题。比如 CRC 错、LRC 错、响应长度不对、功能码不匹配、从站返回异常响应。说明链路上有数据但协议解释不通过。第三层是数据问题。比如地址差 1、功能码选错、UnitId 错、字节序错、比例系数没处理。这类问题有时不会抛异常设备会正常返回只是你读到的不是你想要的值。先分层再处理。不要一看到失败就同时改 IP、地址、功能码和字节序。异常响应不是网络问题Modbus 从站如果收到了请求但不接受这个请求会返回异常响应。例如主站发03从站可能回83后面带一个异常码。83就是03 0x80。常见异常码异常码含义常见原因01非法功能设备不支持这个功能码02非法数据地址地址不存在、地址差 1、数量跨界03非法数据值数量或写入值不合法04从站设备故障设备内部处理失败06从站设备忙设备暂时不能处理比如返回Illegal Data Address先别怀疑网线。设备已经回你了它是在告诉你地址不接受。这时候重点看功能码、起始地址、数量而不是去重插网线。YModbus里常见异常YModbus 会把一些错误转成明确异常。异常你应该先看什么ModbusTransportException连接、超时、串口、底层收发ModbusProtocolException异常响应、响应格式、功能码匹配ModbusCrcExceptionRTU 帧 CRCModbusLrcExceptionASCII 帧 LRC代码里可以先这样分开处理usingYModbus.Protocol;try{ushort[]valuesawaitclient.ReadHoldingRegistersAsync(0,10);}catch(ModbusProtocolExceptionexception){Console.WriteLine($协议异常:{exception.Message});}catch(ModbusTransportExceptionexception){Console.WriteLine($传输异常:{exception.Message});}不用一开始就写很复杂的异常处理。先把错误分清楚后面日志和界面提示才不会混成一句“读取失败”。超时只是现象“超时”只能说明在指定时间内没有收到有效响应。它不是根因。TCP 超时可能是IP 或端口不对设备没开 Modbus TCP网关后面的 RTU 从站没响应UnitId 不对设备响应慢连接被设备限制RTU 超时可能是COM 口选错波特率、校验位、停止位不一致A/B 线接反SlaveId 不对总线上有多个主站设备响应时间比你设置的超时更长所以第一次联调超时别设太短。TCPawaitusingModbusClientclientawaitModbusClientFactory.CreateTcpAsync(host:192.168.1.10,port:502,unitId:1,readTimeoutMilliseconds:3000,writeTimeoutMilliseconds:3000);串口usingSerialPortportnew(COM3){BaudRate9600,ParityParity.None,DataBits8,StopBitsStopBits.One,ReadTimeout3000,WriteTimeout3000};先给设备足够时间确认能通以后再优化速度。重试是补救不是诊断YModbus 支持重试usingYModbus.Clients;usingYModbus.Transports;ModbusRetryOptionsretrynew(){RetryCount2,RetryDelayMilliseconds100,RetryOnTransportExceptiontrue,RetryOnServerDeviceBusytrue};awaitusingModbusClientclientawaitModbusClientFactory.CreateTcpAsync(host:192.168.1.10,port:502,unitId:1,retryOptions:retry);重试适合处理偶发问题网络抖一下、串口丢一帧、设备短时间 busy。它不适合掩盖配置错误。地址错了重试几次都还是错。站号错了重试也不会变成对的。调试阶段建议少重试多看错误。项目上线后再根据设备稳定性加少量重试。原始报文最有说服力Modbus 问题到最后经常要看报文。比如这条 TCP 请求00 01 00 00 00 06 01 03 00 00 00 0A至少能看出UnitId 是01功能码是03起始地址是0000数量是000A如果你以为自己读的是 2 号站但报文里 UnitId 是01那就不用继续猜了。YModbus 的从站 server 和 network 都有Traffic事件server.Traffic(_,entry){Console.WriteLine(${entry.Direction}:{entry.Message});Console.WriteLine(Convert.ToHexString(entry.Frame));};桌面主站/从站工具里也有报文窗口。联调时建议打开尤其是主站和从站都在你手里的时候两边对照最直观。我的排查顺序遇到问题我通常按这个顺序看TCP 是否连上串口是否打开UnitId / SlaveId 是否正确功能码是否和数据区对应起始地址是否差 1数量是否太大或跨界从站有没有返回异常码原始报文是否和预期一致通讯成功后再看字节序和比例系数这个顺序比盲试快。最怕的是一边改地址一边改字节序一边开重试。最后偶然读对了也不知道到底是哪个问题。到这里YModbus 的异常、超时、重试、报文事件目的都不是让代码显得复杂。它们是为了让你失败时能判断问题在链路、协议、地址还是数据解释。只要能把问题分层现场调试就不会完全靠猜。