异常帧长什么样
Modbus 的异常响应和正常响应只有一位之差。마스터发出请求后,슬레이브把기능 코드的最高位置 1,后面跟一个单바이트的异常码,帧就종료了。没有데이터区。
正常帧 vs 异常帧的结构对比:
请求: 01 03 00 00 00 01 — 读 1 号站,유지 레지스터 0x0000
正常响应: 01 03 02 12 34 — 02=데이터바이트数,0x1234=读回的值
异常响应: 01 83 02 — 83=0x03|0x80,02=异常码 0x02
上面这个例子:기능 코드 0x03 的最高位置 1 后变成 0x83。异常码 0x02 代表「非法데이터주소」——설명 0x0000 这个주소在该슬레이브里不存在。
多看一眼 CRC 검증:不管正常响应还是异常响应,CRC 都是슬레이브 주소开始算到最后一个데이터바이트(不含 CRC 自己)。上面我故意没写 CRC,实际帧末尾还有两个바이트。
RTU 和 TCP 的异常帧有区别,差异在封装层。TCP 的 MBAP 头里有一个 2 바이트的「길이」필드,这个길이包含了유닛 식별자 + 기능 코드 + 异常码。而 RTU 靠 3.5 문자静默时间来分隔帧。프로토콜层不一样,但기능 코드和异常码的语义完全一样——这是 Modbus 最干净的设计之一。
还有一个细节容易被忽略:异常响应的帧길이是固定的。对기능 코드 0x01~0x06,异常响应永远只有 3 个바이트(주소 + 기능 코드|0x80 + 异常码),CRC 另算。对 0x0F 和 0x10 这类多操作기능 코드,异常响应也是 3 바이트。슬레이브不需要告诉你「哪一部分出错了」,只告诉你「整个请求被拒了」。这个设计很粗暴,但也很简单——마스터收到异常码后自己想办法重试或报错。
시리얼 포트层面的时序也有讲究。Modbus RTU 规定슬레이브必须在收到请求后的指定时间内开始响应——但표준并没有硬性规定这个时间的具体值。实践中多数슬레이브的响应时间在 5~50ms 之间。超过 1 秒没收到响应(或者没收到异常帧),마스터应该认为슬레이브无响应——这是超时,不是异常码。很多산업 제어软件把「No Response」和「Exception Response」混在一起报,给你一种设备返回了异常码的错觉。实际上슬레이브根本没回任何东西。区分这两种情况对排查문제至关重要——没响应是链路层或设备层的문제,异常码是슬레이브적용层在告诉你「我收到了但我不干」。
下面逐个拆解 7 种표준异常码。每个都配了真实프레임和现场案例——不是拿 Wireshark 仿真抓的,是产线、变电站、水泵房里实实在在遇到的。
0x01 — 非法기능 코드
슬레이브不支持마스터请求的기능 코드,或者슬레이브当前状态下该기능 코드被禁用了。
你给一台支持 03/06/16 的표준 Modbus 设备发 0x08 诊断기능 코드,大概率收到 0x01。因为诊断기능 코드是可选的,很多廉价 I/O 模块根本没实现。
프레임예제
请求: 02 08 00 00 AA 55
异常响应: 02 88 01
0x08 = 诊断,子码 0x0000 = 回环测试,데이터 0xAA55。슬레이브直接回 0x88(0x08|0x80) + 0x01。
案例 1:台达 DVP 시리즈 PLC 不支持诊断기능 코드
台达 DVP-SX2 시리즈,固件버전 V3.8。项目中做 RS-485 线路质量测试,用 Modbus Poll 的 Test Center 发 0x08 诊断,结果返回异常码 0x01。翻了 DVP 的手册确认:DVP 只实现了 01/03/05/06/0F/10 六个기능 코드,0x08 不在其列。想要诊断线路质量只能用 0x01 读코일或者 0x03 读레지스터,通过响应时间和连续成功率间接判断。这个事情告诉我们,诊断기능 코드虽然写进了 Modbus 표준,但不是所有厂商都买账。
案例 2:西门子 S7-1200 做 Modbus Server 时不支持读코일
西门子 S7-1200 从 TIA Portal V14 开始支持 Modbus TCP Server。但它的实现只是把 DB 块映射到유지 레지스터(0x03/0x06/0x10),코일 0x01/0x05/0x0F 压根没做。跟上位机디버깅的时候,SCADA 用 0x01 去读设备状态字,S7-1200 直接甩回一个 0x81 + 0x01。解决办法:把코일주소映射到유지 레지스터区段,上位机改用 0x03 读。
案例 3:变频器运行时禁止写매개변수
汇川 MD500 变频器,运行状态下尝试用 0x06 写频率给定레지스터 0x2000。返回 0x01。不是变频器不支持 0x06 기능 코드,而是当前状态不允许写。汇川的手册里管这叫「运行禁止쓰기」,但프로토콜层用的就是표준异常码 0x01。这种语义扩展在很多国产设备上都能见到——用표준异常码表达厂商自定义的限制条件。
案例 4:Modbus Plus 特有的기능 코드在 TCP 게이트웨이上被拒
一台老旧的 Modicon Quantum PLC,通过 BM85 网桥做 Modbus Plus 到 Modbus TCP 的转换。Modbus Plus 有自己的一套扩展기능 코드(0x14~0x18 用于네트워크管理),但 BM85 网桥只透传표준 Modbus 기능 코드。上位机不小心发了 0x14 读 PLC 통계信息的请求,网桥在적용层检查后直接返回 0x81+0x01。注意这里返回异常码的是게이트웨이,不是 PLC——PLC 可能支持 0x14,但게이트웨이不支持。网桥로그里没有这个기능 코드的분석路由,直接拒绝了。게이트웨이做기능 코드安全过滤的情况在实际项目里很常见,尤其是有防火墙功能的工业路由器。
0x02 — 非法데이터주소
슬레이브支持该기능 코드,但请求的시작주소 + 数量超出了슬레이브的有效주소范围。这是现场디버깅碰到最多的异常码,没有之一。
关键点:마스터软件里填的주소和프로토콜层实际보내기的주소有 offset。Modbus 프로토콜规定,주소从 0 开始번호。你在 Modbus Poll 里填的 40001(1-based 유지 레지스터),프로토콜层发的是 0x0000。주소계산逻辑弄反了,轻则读到错误데이터,重则触发 0x02。
PLC/SCADA 주소表示: 40001 (1-based, 유지 레지스터)
프로토콜层 PDU 주소: 0x0000 (0-based)
프레임예제
请求: 01 03 00 64 00 0A — 读 0x0064 = 400101,读 10 个
异常响应: 01 83 02
슬레이브的有效유지 레지스터范围是 40001~40090(即 PDU 주소 0x0000~0x0059),请求的시작주소 0x0064 超了,直接 0x02。
案例 1:Schneider TM3 扩展模块주소不够
Schneider Modicon M221 挂了两块 TM3 扩展。설정里第一块 TM3DI16 占 %IW0~%IW1,第二块 TM3AI4 占 %IW2~%IW5。上位机 SCADA 用 0x04(读입력 레지스터)去读 %IW10,슬레이브返回 0x02。原因很简单——M221 只分配了 %IW0~%IW5,%IW10 不在物理 I/O 映射里。SoMachine 里看一眼 I/O 映射表就明白了。这种문제属于설정和实际硬件不일치,不是프로토콜缺陷。
案例 2:Modbus Poll 주소 40001 和 400001 的坑
一个新手엔지니어링师用 Modbus Poll 点表里抄的주소是 40001,Modbus Poll 实际发的是 0x0000,读到的是设备第一个유지 레지스터,没문제。但后来换了另一个国产구성(SCADA)软件,那个软件把 40001 분석成了 4000 + 1 的偏移量,实际发的是 0x0FA0。슬레이브根本没有那么多레지스터,返回 0x02。同一份点表、同一个슬레이브,不同的마스터软件주소约定不一样,结果就不一样。这不是 Modbus 프로토콜的문제,是도구链各玩各的约定导致的。建议直接用 PDU 주소(0-based)跟同事沟通,别说什么 4 区 5 区,那都是 Modicon 的老黄历。
案例 3:读多个레지스터时末尾주소越界
一台 Modbus 电表,유지 레지스터 0x0000~0x003F(64 个레지스터)。마스터请求读 0x0038 起 10 个레지스터(0x0038~0x0041)。슬레이브检查:最后一个주소 0x0038 + 9 = 0x0041 > 0x003F,直接扔 0x02。很多슬레이브在做주소合法性检查时,会先算시작 + 数量是否在范围内,只要任何一个주소越界就整包拒绝。不是一个레지스터越界返回部分데이터——Modbus 표준没有部分响应的概念。
案例 4:이산 입력和코일주소混淆
这个坑在用了 Modicon 老式 5 位주소标注的公司里特别常见。1xxxx 是이산 입력(只读位),0xxxx 是코일(读写位)。上位机구성(SCADA)里설정了주소 10001 来读设备状态,但实际该슬레이브把设备状态放在了코일区(0xxxx 对应 0x01 기능 코드)。上位机用 0x02(读이산 입력)去请求주소 0x0000,슬레이브里 0x0000 在이산 입력区确实不存在——이산 입력总共有 8 路(주소 0x0000~0x0007)但那 8 路已经分配给了其他 DI。슬레이브返回 0x02。换个기능 코드或者换个주소都能解决,但前提是你得知道슬레이브的주소映射表长什么样。不少산업 제어엔지니어링师直接把所有离散信号全映射到코일区或全映射到유지 레지스터里,省得用户搞混——但前提是你手里的슬레이브允许你这么做。
0x03 — 非法데이터值
주소有效、기능 코드有效,但请求体中携带的데이터值不在允许范围内。
这是一个容易被忽略的异常码。很多时候你会先怀疑주소설정错了,但实际是写的值越界了。
프레임예제
请求: 01 06 00 10 FF FF — 写单个레지스터,주소 0x0010,值 0xFFFF
异常响应: 01 86 03 — 기능 코드 0x06|0x80 = 0x86,异常码 0x03
为什么 0xFFFF(65535)非法?因为那个레지스터是 0~10000 的엔지니어링值范围。65535 在 16 位表示上是合法的,但在业务逻辑上不合法。
案例 1:写 EEPROM 매개변수超出설정范围
欧姆龙 E5CC 温控器,레지스터 0x0103(PID 比例带)。该매개변수的有效范围是 0.1~999.9,以 0.1°C 为单位存储,即레지스터 값 1~9999。上位机下发 0x0000(即 0.0°C),슬레이브체크섬后发现值小于下限 1,返回 0x03。手册上写了范围,但上位机代码里偷懒没做边界检查。调了半小时才发现是写值超范围——这种低级错误每个人都会犯,重要的是记住先翻手册再看异常码。
案例 2:对只读레지스터执行写操作
ABB ACS580 变频器,레지스터 0x2104(实际转速反馈,只读)。上位机试图用 0x06 쓰기 1500(想手动给转速值),슬레이브返回 0x03。0x2104 这个주소本身存在,기능 코드 0x06 也被支持,但该주소在슬레이브内部被기호为只读属性。「非法데이터值」在这里的含义是「你往一个不接受쓰기的주소写了데이터」——不是值本身非法,是写这个动作非法。Modbus 프로토콜사양里,0x03 的措辞是「Value is not allowed」,给了슬레이브实现自由度。许多厂商就把只读保护归到 0x03 下。
案例 3:多레지스터写 —— 部分필드值非法
用 0x10 批量写 5 个레지스터,前 4 个值没毛病,第 5 个레지스터要求值在 0~100,你写了 200。有些슬레이브会체크섬전체데이터后才回复,5 个全合法就执行,任何一个不合法就整体拒绝返回 0x03。有些슬레이브则逐个체크섬、发现第一个不合法就拒绝。사양没说必须按哪种방식,各家的实现不一样。建议你对自己用的设备做一下边界测试,心里有数。
案例 4:Modbus 电表的需量复位레지스터权限控制
施耐德 PM800 시리즈电力仪表,레지스터 0x2F01(需量复位命令)只能쓰기特定的控制字组合(0x1234 或 0x5678)。上位机误写了 0x0001 试图清需量,仪表返回 0x03。手册上明确写了:复位命令레지스터的有效值是 0x1234 和 0x5678,其他任何值都触发 0x03。这个设计是为了防止误操作——毕竟某些레지스터写错了不是报错那么简单,是真的会改매개변수。
还有一个与 0x03 相关的陷阱:기능 코드 0x06 写单个레지스터时,如果该레지스터是 32 位值的低 16 位,只写低 16 位而高 16 位未初始化,슬레이브可能返回 0x03。比如 AB ACS580 的매개변수 0x1000(32 位浮点),你必须用 0x10 一次写 2 个레지스터,不能拆成两个 0x06。分开写的话슬레이브会在第一个 0x06 时就报 0x03——因为它知道这个주소是 32 位对齐的,不接受 16 位写操作。
0x04 — 슬레이브设备故障
슬레이브在处理请求的过程中发生了不可恢复的内部错误,导致命令无法执行。这个异常码告诉你「不是你的请求有문제,是我自己坏了」。
0x04 是现场最让人紧张的异常码。0x01~0x03 你改改설정就能解决,0x04 意味着可能要去车间里爬梯子了。
프레임예제
请求: 03 03 00 20 00 04 — 读 4 个유지 레지스터
异常响应: 03 83 04
同一个请求,十分钟前还正常返回데이터,现在开始持续返回 0x04。硬件故障的可能性大了。
案例 1:Modbus 센서 EEPROM 쓰기失败
一台昆仑海岸 JWSK-6 온습도变送器,Modbus RTU 인터페이스。远程改了设备주소从 0x01 到 0x05,写 EEPROM 的过程中전원 공급抖动了一下。쓰기失败,设备固件检测到 EEPROM checksum 不일치,进入了 fail-safe 모드。之后所有기능 코드请求(不管 0x03 读还是 0x06 写)전체返回 0x04。查了手册确认:该센서在 EEPROM 自检失败后会锁死통신,只能物理断电重启恢复出厂设置。这属于固件保护机制——比让你读到错误值强。
案例 2:温控器探头短路导致 AD 转换异常
RKC CB100 温控器,热电偶입력端因为배선端子进水短路了。AD 转换芯片读到的是溢出值,固件判定센서故障,然后所有 0x03 读 PV 值的请求都返回 0x04。这个 0x04 不是 Modbus 통신芯片坏,是센서前端故障通过固件的异常传递机制反映到了프로토콜层。换了个热电偶、擦干端子,0x04 消失了。
案例 3:扩展 I/O 模块掉线
Siemens ET200SP 通过인터페이스模块做 Modbus TCP Server。一个 DI 模块因为背板연결器接触不良掉线了。对于掉线模块的레지스터주소,슬레이브统一返回 0x04。但其他正常模块的레지스터주소읽기正常。这설명슬레이브内部的故障隔离做得好——坏一块不影响全局。
案例 4:模拟量입력模块过压保护触发
研华 ADAM-4117 模拟量입력模块,量程设置 0~5V。某个通道意外接入了 12V 信号,模块内过压保护电路启动,该通道进入故障状态。所有 0x04 读该通道值的请求返回 0x04,설정文件显示错误标志位置位。拔掉过压信号、重新上电后恢复。这个 0x04 的根因在物理배선,不在통신 프로토콜。排查时先把线甩了,再用仿真信号测——不过我记得 ADAM-4117 有时要软复位,单靠重新上电不够。
0x05 — 确认(ACK)
0x05 不是错误。它是슬레이브对마스터说:「收到了,正在做,你先别催。」
표준措辞是「Acknowledge」。슬레이브已经接受了请求,但处理需要较长时间(比如写 Flash、执行自整定),先回一个 0x05 让마스터知道연결没断,后续处理完成后再通过正常响应或状态位告知结果。
这是 7 个표준异常码里最特殊的一个——它是唯一不表示문제的异常码。
프레임예제
请求: 01 06 0F A0 00 00 — 写레지스터 0x0FA0 = 0x0000,触发매개변수저장
异常响应: 01 86 05
案例 1:变频器매개변수쓰기 EEPROM
台达 VFD-M 变频器,写 0x2000(命令레지스터)= 0x0010 触发「매개변수쓰기 EEPROM」操作。EEPROM 擦写周期在 10~30ms 左右。슬레이브收到命令后先回 0x86 + 0x05,表示「知道你要저장매개변수,正在写 Flash」。大약 20ms 后可以再次轮询命令레지스터,读到 0x0000 表示쓰기完成。如果在 0x05 之后 50ms 内没有完成,有些变频器会超时回退。
案例 2:온도제어기 PID 自整定
欧姆龙 E5CC,写 0x0101(AT 执行/정지)= 0x0001 启动自整定。这个操作需要几分钟。E5CC 先回 0x05,然后自整定过程中可以正常읽기 PV 和 SV 值,只是 AT 标志位为 ON。自整定完成后,AT 自动 OFF。上位机需要在收到 0x05 后启动超时计时器,不要一直在那死等——有些设备的自整定能跑 30 分钟。
案例 3:게이트웨이后端的슬레이브响应慢
Modbus TCP 转 RTU 게이트웨이(比如 MOXA MGate MB3170),마스터通过게이트웨이读一个 9600bps 低速总线上的슬레이브。마스터发了读 125 个레지스터的请求,게이트웨이把这个请求转发到 RTU 总线,但因为데이터量大、통신 속도低,RTU 슬레이브需要 200ms+ 才能回完데이터。게이트웨이在收到完整 RTU 响应之前,先给 TCP 마스터回一个 0x05 占位——这就是게이트웨이常见的「pending」处理방식。Modbus TCP 사양里其实没有明确规定 0x05 在这种시나리오下的用法,但很多게이트웨이厂商就这么干了。
一个需要注意的设计细节:0x05 触发后,마스터侧的대기策略。不应바로重发同样的请求——这会让슬레이브收到重复命令。正确的做法是:轮询一个状态레지스터(如果슬레이브提供了)或者设立超时计时器,超时后发新的查询命令。有些 SCADA 驱动(比如 Kepware 的 Modbus 驱动)内置了 0x05 的处理逻辑,有些则直接报超时。如果你自己写 Modbus 驱动,0x05 的处理逻辑是必做项。
0x06 — 슬레이브忙
슬레이브现在太忙了,处理不了你的请求。命令被接受了但无法执行,마스터应该稍后重试。
0x05 和 0x06 的区别:0x05 是「收到了,在处理,等结果」;0x06 是「现在没空,你过会儿再来」。0x05 意味着슬레이브已经开始执行命令了,0x06 意味着슬레이브根本没开始执行。
프레임예제
请求: 01 06 20 00 07 D0 — 写레지스터 0x2000 = 0x07D0(2000)
异常响应: 01 86 06 — 忙,没空处理
案例 1:写完매개변수바로读
쓰기一个需要写 EEPROM 的매개변수后,10ms 内바로发下一个请求。슬레이브 Flash 제어기还没释放总线,直接回 0x06。很多엔지니어링师在디버깅스크립트里几毫秒一个请求连续发,슬레이브根本来不及处理。不是设备慢,是你太快了。Modbus 没有流控机制,마스터需要自己控制节奏——写完 Flash 相关레지스터后,留 30~50ms 再发下一个读写请求。
案例 2:高速轮询导致低速设备缓冲区溢出
用 Modbus Poll 以 20ms 间隔轮询一台 9600bps 的 RTU 온습도센서。刚启动时正常,运行一分钟后开始间歇性出现 0x06。9600bps 传输一个바이트약 1ms,一个最小读请求+响应来回大概 30~40ms。20ms 的轮询间隔已经小于设备的最小响应周期。슬레이브시리얼 포트수신缓冲区被塞满了,它在忙着丢弃溢出的帧,没精力处理新请求。把轮询间隔调到 100ms 就好了。换成 115200bps 能进一步缩短,但很多산업 제어设备最高只支持到 38400bps 甚至 19200bps。
案例 3:多마스터同时访问一个슬레이브
一个 RS-485 总线上的 Modbus RTU 슬레이브,同时接了 PLC 和上位机 SCADA 两个마스터。两个마스터之间没有协调,各自以 500ms 和 300ms 间隔轮询。碰撞概率上来了——两个请求帧重叠,슬레이브收到的是乱码。有些슬레이브在检测到帧错误后会忽略,有些슬레이브会消耗时间做错误处理,导致刚지우기수신缓冲区时下一个마스터的帧就到了。还没准备好수신新帧,只能回 0x06。RS-485 多마스터的冲突处理,建议上硬件流控或在마스터侧做互斥锁,别指望슬레이브自己搞定。
案例 4:슬레이브上电初始化期间收到请求
大部分 Modbus 슬레이브上电后有几十毫秒到几秒的初始化时间。在这个窗口里发请求,슬레이브的 Modbus 프로토콜栈还没准备好。有些슬레이브直接不回(No Response),有些슬레이브回 0x06。这个行为跟具体的固件实现有关。丹佛斯 VLT FC302 变频器上电后大약 2 秒内会返回 0x06,初始化完成后恢复正常。如果你的 SCADA 启机就去读슬레이브,头几个请求可能收到 0x06——在上位机代码里加个上电지연或者对 0x06 做自动重试(间隔 500ms,最多 3 次)就能规避。
0x06 和 0x05 很容易搞混,这里说清楚:0x05 是「正在处理你的请求」,处理完了会回正常데이터。0x06 是「没空处理你的请求,请求被丢弃了」。마스터收到 0x05 应该대기,收到 0x06 应该重试。如果把 0x06 当成 0x05 处理——等着等着就超时了。
0x0A — 게이트웨이路径不可用
这个异常码只在게이트웨이시나리오出现。게이트웨이和下游슬레이브之间的통신出了문제——게이트웨이本身没문제,但它后面的设备连不上了。
Modbus 표준定义的措辞是「Gateway Path Unavailable」——无法建立到目标设备的路径。这里的目标设备不是주소 0x01 的슬레이브本体,而是게이트웨이内部的「路由」到下游슬레이브的路径。
프레임예제
请求: 01 03 00 00 00 01 — 通过게이트웨이读下游슬레이브
异常响应: 01 83 0A — 게이트웨이:后面那个슬레이브没响应
案例 1:시리얼 디바이스 서버后端的슬레이브掉线
USR-N510 시리얼 디바이스 서버做 Modbus TCP 转 RTU 게이트웨이,下面挂了 3 台 Modbus RTU 센서(주소 0x01、0x02、0x03)。0x02 슬레이브因为电源模块烧了,完全掉线。마스터通过게이트웨이读 0x02 슬레이브时,시리얼 디바이스 서버尝试在 RS-485 总线上广播请求,等 500ms 超时没收到响应,然后在 TCP 侧给마스터回了 0x0A。0x01 和 0x03 슬레이브的통신完全正常——게이트웨이本身活着,只是某一条路径断了。
案例 2:게이트웨이설정了错误的下游매개변수
MOXA MGate MB3170,설정下游为 9600bps、8N1。但实际 RS-485 总线上的设备是 19200bps、8E1(짝수 패리티)。게이트웨이按照 9600bps 发请求帧,슬레이브收到的是乱码,不响应。게이트웨이超时后返回 0x0A。这种문제在有多家的设备混装的 RS-485 总线上尤其常见——不同厂商的默认통신매개변수不一样,项目初期不统一调一遍就会踩坑。
案例 3:Modbus TCP 级联게이트웨이
一个大型분산형시나리오:中心 SCADA → Modbus TCP 主게이트웨이 → 光纤环网 → 就地子게이트웨이 → RS-485 슬레이브。主게이트웨이到子게이트웨이之间的光纤断了,主게이트웨이对子게이트웨이下面所有슬레이브的请求都返回 0x0A。这时候排查思路是逐级 ping:先确认主게이트웨이到子게이트웨이的 TCP 연결,再确认子게이트웨이到 RS-485 总线,最后才是시리얼 포트设备本身。
厂商自定义异常码
除 7 种표준异常码,很多厂商在 0x80~0xFF 区段定义了私有异常码。这不是违反표준——Modbus 사양允许厂商扩展。但你的上位机代码如果只认识 01~0A,碰到 0x90 就会报「Unknown Exception」或者直接丢帧。
一些常见厂商扩展:
| 厂商 | 自定义异常码 | 含义 |
|---|---|---|
| 西门子 S7-1200/1500 | 0x80 | Modbus Server DB 块未初始化 |
| 汇川 AM600 | 0x81 | 매개변수锁定(需要先写解锁레지스터) |
| 台达 AS 시리즈 | 0x8B | 기능 코드在当前 PLC 运行모드下被禁止 |
| 部分国产温控器 | 0x90~0x9F | 매개변수체크섬失败(写值与 EEPROM 不符) |
| Schneider M221 | 0xF0 | 固件不支持的레지스터区域 |
这些异常码没有统一표준,得翻各家手册。有些厂商在手冊的 Modbus 통신章节会列出完整的异常码表,有些藏在附录里,还有些干脆不写——你只能 debug 抓到之后凭经验判断。
有个坑:部分国产 PLC 在遇到 0x02 条件时也会回 0x03(不区分非法주소和非法데이터值),因为它们内部的异常处理把「주소不存在」和「值不合法」归为同一个错误路由。你按표준预期是 0x02,实际收到 0x03——别较真,对着手册的레지스터 매핑表查就是了。
还有一个容易被忽略的:CPU 停机모드下的异常码行为。很多 PLC 在 STOP 状态下仍然运行 Modbus 프로토콜栈,但行为不同。西门子 S7-1200 做 Modbus Server 时,CPU 从 RUN 전환到 STOP,读到已映射的 DB 块返回 0x04(「设备故障」),而不是 0x01。这个语义很微妙——PLC 没坏,但在 STOP 모드下 DB 데이터不可用。施耐德 M221 在 STOP 状态下则仍然响应 Modbus 请求,只是데이터不更新。三菱 FX5U 做 Modbus Server 时,STOP 状态下直接不响应任何请求,上位机看到的是超时,不是异常码。同一套 SCADA 系统要适配不同品牌 PLC 的 STOP 行为,驱动层面就得做差异化处理。
厂商扩展异常码还经常被用于安全시나리오。比如某些支持 Modbus Security(基于 TLS)的게이트웨이设备,会在인증失败时返回自定义异常码 0xE0~0xEF 表示安全层拒绝。这不是 Modbus 표준定义的,但确实存在。如果你的 Modbus TCP 통신突然开始收到 0xE1,不是프로토콜栈분석错了,是게이트웨이在告诉你「TLS 握手没过」。
用 Wireshark 抓异常帧
不管 RTU 还是 TCP,Wireshark 都能直接분석 Modbus 프로토콜。前提是你的抓包点在正确的네트워크位置。
TCP 시나리오
Modbus TCP 走 502 포트,Wireshark 默认就분석。在过滤器栏입력 `modbus`,异常帧会显示为红色背景。
展开异常响应帧,看这几个关键필드:
Modbus/TCP
Transaction Identifier: 1
프로토콜 식별자: 0
Length: 3 ← 注意这个길이
Unit Identifier: 1
Modbus
기능 코드: 131 (0x83) ← 83 = 03 | 0x80,Wireshark 直接显示了
Exception Code: 2 (Illegal 데이터 주소)
`Length: 3` 是 MBAP 头里的길이필드,它 = Unit Identifier(1) + 기능 코드(1) + Exception Code(1) = 3 바이트。正常响应这个值会更大(因为后面有데이터)。只看길이就能初步判断是异常响应还是正常响应——异常响应的 TCP 载荷通常只有 3 바이트。
RTU 시나리오
RTU 抓包需要用 RS-485 转 USB 컨버터搭一个监听节点。Wireshark 对 RTU 的支持不如 TCP 好,需要手动指定포트설정(통신 속도、체크섬等)。抓到的异常帧형식和 TCP 类似,但没有 MBAP 头:
01 83 02 xx xx — 주소 01,기능 코드 83,异常码 02,CRC xx xx
RTU 抓包里没有 Transaction ID,你只能靠时间戳来判断请求和响应的对应关系。디버깅复杂的多슬레이브 RTU 总线时,建议在 Wireshark 里按 Modbus 주소过滤,把每个슬레이브的流量分开看。
Wireshark 的 Modbus 분석器从 3.x 버전开始支持异常码的중국어显示。在 Preferences → Protocols → Modbus 里勾选 Exception 的분석选项。但注意:Wireshark 只能분석표준异常码 01~0A,厂商自定义码会显示数字而非설명文本。
还有一个 Wireshark 小技巧:用 `modbus.exception_code` 做显示过滤器。`modbus.exception_code == 2` 过滤出所有 0x02 异常帧。`modbus.exception_code >= 1 && modbus.exception_code <= 10` 过滤所有표준异常。在현장 문제 해결时,先看全局통계——Statistics → Protocol Hierarchy → Modbus,能看到异常帧占통신总量的百分比。如果异常帧超过 10%,你的설정大概率有문제。如果只有偶发的 0x06,那是时序문제。如果持续 0x04,准备换设备。
Modbus Poll 里的异常提示
Modbus Poll 是最常用的디버깅 도구。它的异常信息直接显示在窗口底部状态栏,长这样:
Modbus 예외 응답
기능: 3, Exception: 2 (Illegal 데이터 주소)
第一眼看到红字别慌,先看 기능 是什么,再看 Exception 是什么。기능 코드告诉你哪个操作报错了,异常码告诉你为什么。
열기 Display → Communication,能看到完整的송수신 프레임。异常帧用红色기호,点开看原始16진수:
Tx: 01 03 00 00 00 01 84 0A
Rx: 01 83 02 C0 F1
`Rx` 的第二바이트是 0x83(=0x03|0x80),第三바이트 0x02 就是异常码。`C0 F1` 是 CRC。Modbus Poll 帮你분석出来了,但学会自己看原始帧是一项基本功——上线部署后你不会永远有 Modbus Poll 可以用。
同一个请求连续报 0x02,先别改주소,先确认你的시작주소和数量是否在슬레이브手册列出的有效范围内。很多时候是주소계산逻辑错了,不是你주소写错了。尤其是从 1-based 레지스터주소전환到 0-based PDU 주소的时候——这个坑基本每个新手都要踩一次。
디버깅方法论:收到异常码后的排查清单
现场出了异常码,按下面这个顺序走——不是표준操作流程,是老엔지니어링师的血泪经验。
**第一步:确认是哪个슬레이브、哪个기능 코드、哪个异常码**
因为现场通常不止一台슬레이브。用 Modbus Poll 或 Wireshark 抓원본 프레임,拿到슬레이브 주소 + 기능 코드 + 异常码这三个数。如果你是用스크립트在测,加一行 `print(hex(response[0]), hex(response[1]), hex(response[2]))`,别只看로그里的「통신失败」。
**第二步:异常码分类——软件原因还是硬件原因?**
0x01 / 0x02 / 0x03 大概率是你的请求有문제。查설정、查주소映射、查데이터范围。0x04 / 0x0A 大概率是슬레이브或链路有문제。先断电重启슬레이브试试——如果是 0x04 变正常了,설명是设备内部故障恢复,但不代表根本原因已解决。0x06 是时序문제,调慢轮询间隔。
**第三步:隔离변수**
同一台마스터,换一个슬레이브 주소试试。能통신 → 문제在原슬레이브。不能통신 → 문제在마스터端或总线。同一个슬레이브 주소,用 Modbus Poll 试试。能통신 → 你的上位机代码有문제。不能통신 → 슬레이브或链路有문제。这是最常见的二分排查法,但很多人跳过了这一步,直接怀疑硬件坏了。
**第四步:看手册**
异常码出来了,翻슬레이브手册的 Modbus 통신章节。有些厂商会列出所有可能返回的异常码和触发条件。如果手册没有异常码表,找주소映射表,手动对照你的请求주소是否在有效范围内。很多现场的「통신故障」其实是你读了一个保留주소或者只写주소。
**第五步:抓包**
用 Wireshark 或者시리얼 포트监控도구抓원본 프레임。看的是:마스터到底发出了什么?슬레이브到底回复了什么?有没有 CRC 错误?有没有帧不完整?很多时候你看로그以为发出去了 01 03 00 00 00 01,实际抓包发现통신 속도错了슬레이브当噪音扔了。不要相信你的代码로그,要相信抓包도구。
**第六步:替换测试**
换一台同型号的新슬레이브接上去,同样的请求看结果。如果新设备正常,原设备返异常码——设备硬件故障。如果新设备也返同样的异常码——你的请求有문제。如果手头没有备用设备,换一个确定能正常工作的슬레이브 주소来测통신链路。链路的嫌疑排除了,문제就在特定设备上。
**第七步:如果还是搞不定**
把원본 프레임(16진수)、슬레이브型号和固件버전、你的마스터平台信息发给슬레이브厂商的 FAE。别发로그截图,发원본 프레임——FAE 看원본 프레임比看你的설명准得多。如果 FAE 也不回,去 modbus.cn 论坛贴원본 프레임求助。社区的디버깅经验有时比厂商手册还靠谱,因为手册是理论,社区是血泪。
**补充:自己写 Modbus 驱动时的异常码处理模板**
如果你在写上位机的 Modbus 마스터驱动(用 C/Python/Node.js 等),异常帧处理至少要覆盖这几种情况:
1. 检查响应的第一个바이트是否等于请求的슬레이브 주소(주소不일치 = 不是给你的响应,丢弃) 2. 기능 코드的 bit 7 是否为 1(为 1 则提取异常码,为 0 则正常분석데이터区) 3. 异常码 0x05 特殊处理:不抛出异常,进入대기状态,轮询状态레지스터或超时重发 4. 异常码 0x06 特殊处理:지연 50~100ms 后重试,最多重试 3 次 5. 其他异常码:记录로그、通知上位层进行错误处理(告警、重试、전환备用链路等) 6. 厂商自定义码 0x80~0xFF:需要设备型号相关的字典来분석,否则按 Unknown Exception 处理
错误的做法是把所有异常码都当作「통신失败」然后重试——0x02 重试一万次也是 0x02,주소错了就是错了。正确的做法是根据异常码分类采取不同策略。这部分逻辑写好了,你的驱动就能适配市面 90% 的 Modbus 슬레이브设备。剩下的 10% 是那些不按표준返回异常码的厂商——它们用正常响应替代异常响应,把错误信息塞在레지스터데이터里。遇到这种设备只能翻专用手册。
异常码速查表
| 异常码 | 名称 | 一句话解释 |
|---|---|---|
| 0x01 | 非法기능 코드 | 슬레이브不支持你发的기능 코드,或当前状态不允许 |
| 0x02 | 非法데이터주소 | 请求的주소在슬레이브的레지스터 매핑里不存在 |
| 0x03 | 非法데이터值 | 주소存在但写的值超出允许范围或写了只读레지스터 |
| 0x04 | 슬레이브设备故障 | 슬레이브内部硬件或固件出了不可恢复的错 |
| 0x05 | 确认 (ACK) | 不是错误,슬레이브在处理长耗时操作,等着 |
| 0x06 | 슬레이브忙 | 슬레이브现在没空处理,稍后重试 |
| 0x0A | 게이트웨이路径不可用 | 게이트웨이到下游슬레이브不通,或설정不일치 |
| 0x80~FF | 厂商自定义 | 翻各自的手册,语义不通用 |
这张表建议打印出来贴在디버깅电脑旁边。现场出异常码不用掏手机搜,看一眼表就知道大概方向。具体的诊断方法,回到上面每个异常码的案例里找——所有案例都是现场碰过的,不是编的。
답글 작성