4. Modbus功能码详解
在Modbus协议中,功能码(Function Code)是决定“这次通信到底要做什么”的核心字段。无论是串行链路上的RTU/ASCII,还是以太网上的TCP,请求PDU中的第一个字节就是功能码,它直接决定了后续数据区的组织方式、地址范围、数量限制以及异常响应的判断逻辑。对于设备测试与故障诊断而言,如果只记住“03是读保持寄存器”而说不清请求与响应的字节结构,就很难从抓包数据中定位问题。本节围绕最常用的八个功能码——01、02、03、04、05、06、15、16,逐一拆解其请求与响应格式,并给出测试中可直接对照的字节级说明。
一、核心原理:功能码决定数据区的“语法”
Modbus的PDU由“功能码 + 数据”组成。功能码告诉从站两件事:第一,操作哪一类对象(线圈、离散输入、保持寄存器、输入寄存器);第二,是读还是写。八个常用功能码可以按对象和方向分为四组:
- 位对象读取:01读线圈、02读离散输入;
- 寄存器读取:03读保持寄存器、04读输入寄存器;
- 位对象写入:05写单个线圈、15写多个线圈;
- 寄存器写入:06写单个保持寄存器、16写多个保持寄存器。
读操作请求的核心参数是“起始地址 + 数量”,响应则返回“字节数 + 数据”;写操作请求的核心参数是“地址 + 值”或“地址 + 数量 + 字节数 + 数据”,响应通常回显地址和数量/值,用于确认写入已被接受。理解这一对称关系,就能在抓包时快速判断报文是否完整。
二、方法流程:逐字节拆解请求与响应
下面以RTU帧为例,给出各功能码的典型报文结构。RTU帧由“从站地址 + PDU + CRC”组成,以下只展示PDU部分,便于对照功能码本身。
01 读线圈(请求):01 | 00 13 | 00 25
含义:功能码01,起始地址0x0013,读取0x0025=37个线圈
01 读线圈(响应):01 | 05 | CD 6B B2 0E 1B
含义:功能码01,字节数5,5字节位数据(37位按位打包)
02 读离散输入(请求):02 | 00 C4 | 00 16
含义:功能码02,起始地址0x00C4,读取22个离散输入
03 读保持寄存器(请求):03 | 00 6B | 00 03
含义:功能码03,起始地址0x006B,读取3个保持寄存器
03 读保持寄存器(响应):03 | 06 | 02 2B 00 00 00 64
含义:功能码03,字节数6,3个寄存器值:0x022B、0x0000、0x0064
04 读输入寄存器(请求):04 | 00 08 | 00 01
含义:功能码04,起始地址0x0008,读取1个输入寄存器
05 写单个线圈(请求):05 | 00 AC | FF 00
含义:功能码05,地址0x00AC,写入FF00表示置位ON
05 写单个线圈(响应):05 | 00 AC | FF 00
含义:响应回显地址和值,确认写入成功
06 写单个保持寄存器(请求):06 | 00 01 | 00 03
含义:功能码06,地址0x0001,写入值0x0003
15 写多个线圈(请求):0F | 00 13 | 00 0A | 02 | CD 01
含义:功能码15,起始地址0x0013,写10个线圈,字节数2,数据CD 01
16 写多个保持寄存器(请求):10 | 00 01 | 00 02 | 04 | 00 0A 01 02
含义:功能码16,起始地址0x0001,写2个寄存器,字节数4,数据00 0A 01 02
16 写多个保持寄存器(响应):10 | 00 01 | 00 02
含义:响应回显起始地址和寄存器数量,确认写入
从上述报文可以看出,读请求的“数量”字段在响应中并不直接回显,而是通过“字节数”间接体现;写多个请求的“字节数”必须等于“数量×2”(寄存器)或“ceil(数量/8)”(线圈)。测试时若发现字节数与数量不匹配,从站通常会返回异常码03(非法数据值)。
三、案例/场景:一次03功能码读取失败的排查
某现场使用Modbus RTU读取电表保持寄存器,主站发送:01 03 00 00 00 02 C4 0B,期望读取地址0x0000开始的2个寄存器。从站无响应。排查过程如下:
- 确认串口参数:波特率9600、8数据位、无校验、1停止位,与电表手册一致;
- 用串口监听工具抓取原始字节,发现主站实际发出的是
01 03 00 00 00 02 C4 0B,CRC正确; - 检查电表手册,发现其保持寄存器地址从1开始编号,协议地址应为0x0000对应手册的40001,但该电表要求读取“电压”时起始地址为0x0000、数量为2,理论上正确;
- 进一步检查发现电表从站地址实际为0x02,而非0x01,修改从站地址后通信恢复。
这个案例说明:功能码格式正确并不等于通信成功,从站地址、串口参数、地址基准、数量限制都是测试中必须同步核对的要素。尤其在多设备总线上,从站地址冲突或错位是高频故障。
四、误区/复盘:功能码使用中的常见陷阱
误区一:把“起始地址”直接当作设备手册上的1-based地址。Modbus协议地址从0开始,手册常标40001、30001等。若直接发送40001,会因地址越界返回异常码02(非法数据地址)。正确做法是:协议地址 = 手册地址 - 40001(保持寄存器)或 - 30001(输入寄存器)。
误区二:忽略数量上限。01/02最多读2000个位,03/04最多读125个寄存器,15最多写1968个线圈,16最多写123个寄存器。超限时从站可能返回异常码03,也可能直接截断,导致数据不完整。
误区三:写多个线圈时字节数计算错误。15功能码的字节数 = ceil(数量/8)。例如写10个线圈,字节数为2;写9个线圈,字节数也为2,但最后一个字节的高位补0。若字节数写成1,从站会判为非法数据值。
复盘建议:测试时先用手工构造的报文验证单个功能码,再逐步增加数量;抓包时重点核对“功能码、起始地址、数量、字节数、CRC”五个字段。对于写操作,务必检查响应是否回显了正确的地址和数量/值,不能只看“有响应”就判定成功。
五、常用功能码速查清单
| 功能码 | 名称 | 对象 | 请求关键字段 | 响应关键字段 | 数量上限 |
|---|---|---|---|---|---|
| 01 (0x01) | 读线圈 | 线圈 | 起始地址、数量 | 字节数、位数据 | 2000 |
| 02 (0x02) | 读离散输入 | 离散输入 | 起始地址、数量 | 字节数、位数据 | 2000 |
| 03 (0x03) | 读保持寄存器 | 保持寄存器 | 起始地址、数量 | 字节数、寄存器值 | 125 |
| 04 (0x04) | 读输入寄存器 | 输入寄存器 | 起始地址、数量 | 字节数、寄存器值 | 125 |
| 05 (0x05) | 写单个线圈 | 线圈 | 地址、值(FF00/0000) | 回显地址和值 | 1 |
| 06 (0x06) | 写单个保持寄存器 | 保持寄存器 | 地址、值 | 回显地址和值 | 1 |
| 15 (0x0F) | 写多个线圈 | 线圈 | 起始地址、数量、字节数、数据 | 回显起始地址和数量 | 1968 |
| 16 (0x10) | 写多个保持寄存器 | 保持寄存器 | 起始地址、数量、字节数、数据 | 回显起始地址和数量 | 123 |
掌握这八个功能码的请求与响应格式,是Modbus设备测试的基本功。在实际诊断中,建议将抓包报文与上表逐字段对照,先确认功能码和地址范围,再核对数量与字节数,最后检查CRC或MBAP头,绝大多数通信问题都能快速定位。