(八)YModbus异常、超时、重试和原始报文诊断
2026/6/13 21:41:52 网站建设 项目流程

GitHub 项目地址:https://github.com/lidecong133/YModbus

Modbus 程序好写的一面,是读写方法都很直接。

难的一面,是失败以后你得判断到底哪一层出了问题。

现场最常见的说法是“通讯不通”。但这句话太粗了。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 帧 CRC
ModbusLrcExceptionASCII 帧 LRC

代码里可以先这样分开处理:

usingYModbus.Protocol;try{ushort[]values=awaitclient.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 不对
  • 总线上有多个主站
  • 设备响应时间比你设置的超时更长

所以第一次联调,超时别设太短。

TCP:

awaitusingModbusClientclient=awaitModbusClientFactory.CreateTcpAsync(host:"192.168.1.10",port:502,unitId:1,readTimeoutMilliseconds:3000,writeTimeoutMilliseconds:3000);

串口:

usingSerialPortport=new("COM3"){BaudRate=9600,Parity=Parity.None,DataBits=8,StopBits=StopBits.One,ReadTimeout=3000,WriteTimeout=3000};

先给设备足够时间,确认能通以后再优化速度。

重试是补救,不是诊断

YModbus 支持重试:

usingYModbus.Clients;usingYModbus.Transports;ModbusRetryOptionsretry=new(){RetryCount=2,RetryDelayMilliseconds=100,RetryOnTransportException=true,RetryOnServerDeviceBusy=true};awaitusingModbusClientclient=awaitModbusClientFactory.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));};

桌面主站/从站工具里也有报文窗口。联调时建议打开,尤其是主站和从站都在你手里的时候,两边对照最直观。

我的排查顺序

遇到问题,我通常按这个顺序看:

  1. TCP 是否连上,串口是否打开
  2. UnitId / SlaveId 是否正确
  3. 功能码是否和数据区对应
  4. 起始地址是否差 1
  5. 数量是否太大或跨界
  6. 从站有没有返回异常码
  7. 原始报文是否和预期一致
  8. 通讯成功后,再看字节序和比例系数

这个顺序比盲试快。

最怕的是一边改地址,一边改字节序,一边开重试。最后偶然读对了,也不知道到底是哪个问题。

到这里

YModbus 的异常、超时、重试、报文事件,目的都不是让代码显得复杂。

它们是为了让你失败时能判断:问题在链路、协议、地址,还是数据解释。

只要能把问题分层,现场调试就不会完全靠猜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询