← 返回 中控与协议

Modbus RTU / TCP 入门:功能码、寄存器、从站地址、CRC

最后更新 2026-06-22
s4 · 中控与协议 🟡 涉接线/工业配置
你将学到
  • 搞清 Modbus 的四种数据对象:线圈、离散输入、保持寄存器、输入寄存器,知道各自能读写哪些
  • 认识常用功能码 01/02/03/04/05/06/15/16 的含义和请求/应答帧格式
  • 理解 RTU(RS485 上)和 TCP(网络上)的帧格式差异:CRC vs MBAP 头
  • 会用真实帧示例逐字节拆解,知道地址偏移问题是怎么来的
  • 拿到一张 Modbus 故障排查表

你翻开科星某款网络继电器的手册,第一页就写"支持 Modbus RTU / Modbus TCP 协议"。翻到协议说明页,看到的是一张寄存器表、一堆十六进制数字,还有"功能码 01"、"功能码 03"、"线圈"、"保持寄存器"这些词,脑袋就开始转圈。

Modbus 是展厅里使用最广的工业控制协议,没有之一。科星的网络继电器、IO 控制器、采集器,有人串口服务器透传过来的设备,绝大多数都讲 Modbus。看懂这套协议,你就能控制展厅里绝大部分工业级设备。

好消息是:Modbus 其实很简单。它只有两件事:主控发请求,从站返应答。请求帧和应答帧都是定长或近定长的字节串,格式固定,每个字节的含义都在官方规范里写得清楚。这一节我们从数据模型开始,把帧格式、功能码、CRC 一次性说透。

涉及工业协议配置(R2)。帧格式、功能码、CRC 算法以 Modbus 官方规范(Modbus Application Protocol V1.1b3)为准(见文末来源);具体设备的寄存器地址以该设备手册的地址映射表为准,不可照此篇例子硬套——不同厂商对地址偏移的处理有所不同。


Modbus 数据模型:四张表

Modbus 把设备的所有数据组织成四张表,每张表都有不同的数据类型和读写权限:

数据对象 编号区间 数据类型 读写 典型用途
线圈(Coil) 00001 ~ 09999 1 位(bit),可读写 读写 继电器开关状态(0=OFF,1=ON),可直接写
离散输入(Discrete Input) 10001 ~ 19999 1 位(bit),只读 只读 干接点输入状态(传感器/按钮),只能读回来
保持寄存器(Holding Register) 40001 ~ 49999 16 位(word),可读写 读写 设置值(亮度/温度阈值/运行参数)
输入寄存器(Input Register) 30001 ~ 39999 16 位(word),只读 只读 测量值(温度/电压/电流)只能读

"编号区间"是 Modbus 的数据地址(Data Address)表示法,是用来帮人理解数据归属的命名约定;实际帧里传输的是协议地址(Protocol Address),规则是:协议地址 = 数据地址编号 - 该区的起始号 - 1

举例(以 Modbus Application Protocol 规范为准):

  • 线圈编号 1(第一个线圈)→ 协议地址 0x0000
  • 线圈编号 8(第八个线圈)→ 协议地址 0x0007
  • 保持寄存器编号 40001(第一个保持寄存器)→ 协议地址 0x0000
  • 保持寄存器编号 40010(第十个保持寄存器)→ 协议地址 0x0009
💡 提示

地址偏移是 Modbus 实战第一坑。 有些厂商手册把地址写成"线圈地址 1"对应 0x0001(即数据地址编号 = 协议地址),而不是规范的 0x0000。这种"偏移 +1"的写法在部分厂商文档里存在。遇到发帧应答都正常但实际控的是错误的线圈时,先试一下把地址 -1 或 +1 看看,或者直接以该设备手册的"协议地址"列为准——前提是手册里要明确标注出来是协议地址还是数据编号。


常用功能码:8 个够用

Modbus 定义了几十个功能码,但展厅里常用的就 8 个:

功能码 十六进制 操作 操作对象
01 0x01 读多个线圈 Coil(1位可写)
02 0x02 读多个离散输入 Discrete Input(1位只读)
03 0x03 读多个保持寄存器 Holding Register(16位可写)
04 0x04 读多个输入寄存器 Input Register(16位只读)
05 0x05 写单个线圈 Coil
06 0x06 写单个保持寄存器 Holding Register
15 0x0F 写多个线圈 Coil(批量写)
16 0x10 写多个保持寄存器 Holding Register(批量写)

记忆方式:读 01~04(先读线圈和离散,再读两种寄存器);写 05/06(单写)、15/16(批量写)。奇数功能码写线圈(位),偶数功能码写保持寄存器(字)。


从站地址:每台设备的"名字"

Modbus 网络里,主控叫"主站(Master)",被控设备叫"从站(Slave)"。每台从站有一个地址(Slave ID),取值 1~247。

在 RTU 帧里,第一个字节就是从站地址。主控发出的每条请求帧带上目标从站地址,只有地址匹配的从站才处理并应答,其他从站保持沉默(地址 0 是广播,通常不要求应答)。

从站地址怎么配:大部分设备通过拨码开关或配置软件设置,详细在RS485 总线组网里讲过,这里不重复。


RTU 帧格式:逐字节拆解

Modbus RTU 是走 RS485(串口)时用的帧格式。一个完整的 RTU 请求帧长这样:

[从站地址 1B] [功能码 1B] [数据字段 nB] [CRC 2B]

例子:FC03 读保持寄存器(读 2 个寄存器,从地址 0x0000 开始)

请求帧(主控→从站,目标从站地址 0x01):

01  03  00 00  00 02  C4 0B
│   │   │────│  │────│  │──────
│   │   │    │  │    │  └──────── CRC16 低字节(0xC4),高字节(0x0B)
│   │   │    │  └──────────────── 读取数量:0x0002(读 2 个寄存器)
│   │   └────────────────────── 起始地址:0x0000(第 1 个保持寄存器)
│   └────────────────────────── 功能码:03 = 读保持寄存器
└────────────────────────────── 从站地址:01

正常应答帧(从站→主控,地址 0x01 回应):

01  03  04  xx xx  xx xx  CRC_L CRC_H
│   │   │   │────────────
│   │   │   └── 寄存器数据:两个 16 位寄存器共 4 字节,具体值以设备实际返回为准
│   │   └────── 字节数:0x04(2 个寄存器 × 2 字节)
│   └────────── 功能码:03(同请求,表示正常应答)
└────────────── 从站地址:01

CRC 是什么、怎么算:CRC16 是一种 16 位循环冗余校验算法,作用是让接收方检查帧是否在传输中出错。具体算法、手算方式和代码,在下一节校验算法专题里单独讲,这里先记住"RTU 帧最后 2 字节是 CRC,低字节在前高字节在后"。

例子:FC05 写单个线圈(线圈 1 置 ON)

请求帧(目标从站地址 0x01,线圈协议地址 0x0000,ON = 0xFF00):

01  05  00 00  FF 00  8C 3A
│   │   │────  │────  │────
│   │   │      │      └──── CRC16 校验(低字节 0x3A,高字节 0x8C)
│   │   │      └─────────── 输出值:FF 00 = ON;0000 = OFF(Modbus 规范定义,不是其他值)
│   │   └──────────────────── 线圈地址:0x0000(第 1 个线圈,协议地址)
│   └──────────────────────── 功能码:05 = 写单线圈
└──────────────────────────── 从站地址:01

正常应答帧:FC05 的正常应答帧是把整个请求帧原样回送(共 8 字节不含 CRC 头),你收到的应答和发出的请求一模一样——这是 Modbus 规范对 FC05 的定义。

例子:FC06 写单个保持寄存器

请求帧(目标从站地址 0x01,写寄存器 0x0001,写入值 0x0064 = 100):

01  06  00 01  00 64  D9 E1
│   │   │────  │────  │────
│   │   │      │      └──── CRC16 校验(0xD9 低字节,0xE1 高字节)
│   │   │      └─────────── 写入值:0x0064 = 十进制 100
│   │   └──────────────────── 寄存器地址:0x0001(第 2 个保持寄存器,协议地址)
│   └──────────────────────── 功能码:06 = 写单保持寄存器
└──────────────────────────── 从站地址:01

正常应答帧:与 FC05 类似,FC06 正常应答也是原样回送请求帧。


TCP 帧格式:多了 MBAP 头,少了 CRC

网络控制 TCP/UDP 里已经讲过 MBAP 头的结构,这里对比汇总:

字段 RTU 帧 TCP 帧
从站地址 帧第 1 字节 单元标识符(MBAP 第 7 字节)
功能码 帧第 2 字节 MBAP 后第 1 字节(帧第 8 字节)
数据字段 功能码后 MBAP 后(帧第 9 字节起)
帧完整性校验 CRC16(帧末 2 字节) 无 CRC(TCP 层自带可靠性)
额外头部 MBAP 头(事务标识符 2B + 协议标识符 2B + 长度 2B)共 6 字节

Modbus TCP 帧 = MBAP 头(6 字节)+ 从站地址(1 字节)+ 功能码(1 字节)+ 数据

MBAP 头结构:

事务标识符(2B): 每次请求递增,主控用来匹配应答,通常从 0x0001 开始
协议标识符(2B): 固定 0x0000(表示 Modbus TCP)
长度(2B):       后续字节数(单元标识符+功能码+数据字段的总字节数)

以 FC05 写单线圈(从站地址 0x01,线圈 0x0000 置 ON)的 TCP 帧为例:

00 01  00 00  00 06  01  05  00 00  FF 00
│────  │────  │────  │   │   │────  │────
│      │      │      │   │   │      └── 输出值:FF 00 = ON
│      │      │      │   │   └──────── 线圈地址:0x0000
│      │      │      │   └──────────── 功能码:05
│      │      │      └──────────────── 单元标识符:01(即从站地址)
│      │      └───────────────────── 长度:0x0006(后续 6 字节)
│      └──────────────────────────── 协议标识符:0x0000(固定)
└─────────────────────────────────── 事务标识符:0x0001(第 1 次请求)

注意:TCP 帧里没有 CRC,不要加。这是很多人第一次写 Modbus TCP 帧时的失误——把 RTU 的 CRC 字节也加进去了,结果多出 2 个字节,设备无法解析。


Modbus 异常应答:设备告诉你哪里错了

从站出错时,不会静默,而是返回一个异常应答帧,格式是:

从站地址(1B) + (功能码 | 0x80)(1B) + 异常码(1B) + CRC(2B,RTU 时有)

功能码最高位置 1(即 OR 0x80)是异常应答的标志。异常码含义(以 Modbus Application Protocol V1.1b3 为准):

异常码 十六进制 含义
1 0x01 功能码不支持(设备不认识这个功能码)
2 0x02 数据地址无效(地址超出设备范围)
3 0x03 数据值无效(写入的值超出允许范围)
4 0x04 从站设备故障(设备内部错误)

例如,对从站 0x01 发了 FC03 请求(功能码 0x03),如果设备不支持,会回:

01  83  01  (CRC)
│   │   └──── 异常码 0x01 = 功能码不支持
│   └──────── 0x83 = 0x03 | 0x80,表示这是 FC03 的异常应答
└──────────── 从站地址 01
💡 提示

遇到异常应答,先看功能码(80 以上的那个字节 & 0x7F = 原功能码),再看异常码:

  • 0x01:确认设备手册,这台设备是否支持你用的功能码(有些只支持 03/06,不支持 01/05)
  • 0x02:地址超范围——要么你发的地址比设备最大地址还大,要么地址偏移算错了
  • 0x03:写入值不在允许范围——比如写入寄存器时超出了设备允许的最大值 这三条异常码在实战调试中是"设备在帮你缩小排查范围",比完全静默要好处理得多。

展厅常见场景:科星设备的 Modbus 使用

科星(corxnet.com)的网络继电器、IO 控制器普遍支持 Modbus RTU(RS485 口)和 Modbus TCP(网口),两种方式的功能码和地址一致,只是传输载体不同。

以控制继电器开关为例(具体地址以该型号官方文档为准):

  • 读继电器状态:用 FC01(读线圈)或 FC03(读保持寄存器,如果状态放在寄存器里),读回 1 表示闭合、0 表示断开
  • 写继电器状态:用 FC05(写单线圈)控单路,ON=0xFF00,OFF=0x0000;多路批量操作用 FC15
  • 读采集量(如电压/温度):用 FC04(读输入寄存器)或 FC03(读保持寄存器),返回的是原始值,再乘以手册里的换算系数
💡 提示

科星某些型号的 Modbus 地址表里,线圈地址从 0x0000 开始(符合规范),但也有型号从 0x0001 开始(地址偏移 +1)。接到新设备时先发一个 FC01 读从 0x0000 开始的 8 个线圈,如果应答的数据符合预期(继电器状态对得上),地址就是对的;如果全部偏移一位,就把地址改 +1 或 -1。这个偏移问题不是只有科星有,几乎所有 Modbus 设备都可能遇到,以手册为准是唯一可靠的方式。


故障排查表

现象 最可能的原因 怎么查 / 怎么办
发请求帧,完全无应答 从站地址不对 / 串口参数不对(RTU) 先单台、用广播地址 0x00 试(广播通常不要求应答,但确认设备能收到);再单独对一台确认地址
收到异常应答,异常码 0x01 功能码设备不支持 查手册,确认该型号支持的功能码列表,改用支持的功能码
收到异常应答,异常码 0x02 数据地址超范围 / 地址偏移算错 先读地址 0x0000 开始的少量(如 1 个),确认在范围内;核对手册地址偏移
RTU 帧发出无应答(工具显示已发) CRC 计算错误,从站丢弃 重新核算 CRC16(见下一节校验算法专题);或用自动计算 CRC 的工具
TCP 帧发出无应答 把 RTU 的 CRC 带进 TCP 帧了 TCP 帧不含 CRC,去掉末尾 2 字节
TCP 帧 MBAP 头长度字段算错 长度字段值不对,设备无法解析帧边界 长度 = 从单元标识符起的字节数 = 1(地址)+1(功能码)+数据字节数
多台设备时某台偶发不响应 RS485 总线问题(终端电阻/地址冲突) 参考RS485 总线组网故障排查
读回的值比预期大/小 1 地址偏移(±1 偏移) 从 0x0000 和 0x0001 都试一次,对照手册实际地址确认
写了 FC05 ON,继电器动了但状态读回还是 0 状态寄存器和控制寄存器地址不同 分别查手册"状态读取地址"和"控制写入地址",读写用不同地址

小结 · 你现在掌握了什么

  • Modbus 数据模型四张表:线圈(位/可写)、离散输入(位/只读)、保持寄存器(字/可写)、输入寄存器(字/只读)。
  • 8 个常用功能码:读四种(01/02/03/04)、单写两种(05/06)、批量写两种(0F/10)。
  • RTU 帧 = 从站地址 + 功能码 + 数据 + CRC(2B);TCP 帧 = MBAP 头(6B)+ 从站地址 + 功能码 + 数据,无 CRC
  • 地址偏移是第一坑,协议地址 ≠ 数据编号,以设备手册的"协议地址"列为准。
  • 异常应答是设备告诉你错在哪,0x01/0x02/0x03 三个异常码对应不支持/地址超范围/数据非法。

下一步:RTU 帧末尾的 CRC16 怎么算?和校验/异或/BCC 又是什么?展厅里还有很多设备用私有协议带校验,去看校验算法专题:Sum / XOR / CRC16 / BCC 手算与代码,把各种校验搞透,再也不怕对不上校验字节。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明

需要展厅软硬件方案或定制开发?