Modbus 协议深度解析

Modbus 协议深度解析:变种、功能码与位级细节

工控领域的事实标准协议,自 1979 年由 Modicon(现施耐德电气)发布以来,至今仍是 PLC、传感器、执行器、RTU、IED 之间最广泛使用的通信协议。本文从 变种差异、帧结构、功能码清单、位级细节 四个维度完整拆解 Modbus。


目录

  1. 协议概述与变种一览
  2. [Modbus RTU](#Modbus RTU)
  3. [Modbus ASCII](#Modbus ASCII)
  4. [Modbus TCP](#Modbus TCP)
  5. [Modbus RTU-over-TCP(Modbus TCP 变体)](#Modbus RTU-over-TCP(Modbus TCP 变体))
  6. [Modbus Plus(MB+)](#Modbus Plus(MB+))
  7. 数据模型与地址空间
  8. 功能码详解
  9. 异常码详解
  10. 功能码位级详细拆解
  11. [附录:PDU 与 ADU 结构速查](#附录:PDU 与 ADU 结构速查)

1. 协议概述与变种一览

Modbus 是一个 主/从(Master/Slave)客户端/服务器(Client/Server) 协议,物理层可承载于串口、以太网或光纤等介质。协议栈只定义 应用层(PDU)数据链路层封装(ADU),物理层由下层标准负责。

变种 传输介质 帧界定方式 最大寻址 速率 典型场景
Modbus RTU RS-232 / RS-485 3.5 字符静默间隔 247 从站 9600 ~ 115200 bps 工业现场总线,最广泛
Modbus ASCII RS-232 / RS-485 : 起始 + CR/LF 结束 247 从站 同 RTU 但传输效率 ~50% 时间宽松、字符调试友好
Modbus TCP 以太网 (TCP/IP) 内置长度字段(无校验) 无限制(IP 层) 100 Mbps ~ 1 Gbps SCADA、上位机、跨系统集成
Modbus RTU-over-TCP 以太网 与 RTU 相同 + TCP 流 无限制 同上 串口协议平滑迁移至 TCP
Modbus Plus (MB+) 双绞线(专用) 令牌传递 64 节点 1 Mbps 施耐德 PLC 高速对等网络

:2019 年后施耐德推动术语从 "Master/Slave" 转为 "Client/Server",TCP 变种也建议用 "Client/Server"。本文在 RTU/ASCII 上下文保留 "主/从" 以匹配行业习惯。


2. Modbus RTU

2.1 帧结构(ADU)

一条 RTU 报文由以下四部分组成,顺序为:地址 + PDU + CRC

字段 地址 PDU CRC 校验
大小 1 字节 N 字节(功能码 + 数据) 2 字节
说明 0x00 = 广播, 1--247 = 从站地址 应用层协议数据单元 CRC-16 (Modbus), 低字节在前

帧之间必须有 ≥ 3.5 个字符时间 的静默间隔(Silent Interval),接收方以此判断一帧结束。

波特率与 1.5T / 3.5T 的关系

复制代码
1.5T = 1.5 × 字符时间
3.5T = 3.5 × 字符时间

字符时间 = 1 / 波特率 × (数据位 + 起始位 + 停止位 + 校验位)

例:9600 bps, 8N1 → 9.6k / (1+8+1) ≈ 1.04 ms/char
    3.5T ≈ 3.64 ms  (大多数实现取 3.5 ms 整数倍)
  • 地址域:0x00 为广播地址,1--247 为从站地址
  • CRC :CRC-16 (Modbus 多项式 x¹⁶ + x¹⁵ + x² + 1,初始值 0xFFFF),低字节在前
  • 最大报文长度:256 字节

2.2 位级编址特性

RTU 的 PDU 内 多字节整数以 Big-Endian(大端)编码,即高字节在前。

复制代码
示例:0x1234 → 编码为 0x12 0x34

3. Modbus ASCII

3.1 帧结构

字段 起始 地址 功能码 数据 (HEX 字符) LRC 结束
内容 : (0x3A) 2 字符 2 字符 N × 2 字符 2 字符 CRLF (0x0D 0x0A)

每个字节被拆分为两个 ASCII 十六进制字符发送,报文体积是 RTU 的约 2 倍

3.2 LRC(纵向冗余校验)

LRC = (所有字节的累加和) 的补码,只覆盖地址 + 功能码 + 数据(不包括起始 : 和结束 CRLF)。计算方式:

复制代码
LRC = (~(byte_sum) + 1) & 0xFF
等价于:LRC = (-byte_sum) & 0xFF
等价于:LRC = (0x100 - byte_sum) & 0xFF
  • 发送方:将 LRC 结果以 2 字符 ASCII 追加
  • 接收方:从 : 后到 LRC 前的所有字节做和(含地址、功能码、数据),并与 LRC 相加,结果应为 0x00(模 256)

3.3 与 RTU 的核心差异

特性 RTU ASCII
帧界定 静默 3.5T :CRLF
校验 CRC-16 (2 字节) LRC (1 字节)
效率 ~1 字节/字节 ~2 字符/字节
调试 二进制,不可读 纯 ASCII,可读
容错 静默间隔被噪声触发时可误判 字符帧头帧尾明确,更健壮

4. Modbus TCP

4.1 帧结构 (ADU)

MBAP 头 (7 字节) 功能码 数据
事务ID(2) + 协议ID(2) + 长度(2) + 单元ID(1) 1 字节 N 字节(同 RTU PDU)

MBAP(Modbus Application Protocol)Header 各字段详情:

字段 大小 值范围 说明
事务标识符 (Transaction ID) 2 字节 0x0000--0xFFFF 客户端生成,用于请求/响应配对
协议标识符 (Protocol ID) 2 字节 0x0000 Modbus 协议(其他值保留)
长度 (Length) 2 字节 后续字节数 当前报文剩余字节总数(单元标识符 + PDU)
单元标识符 (Unit ID) 1 字节 0x00--0xFF 原 RTU "从站地址" 的继承,TCP 网关转发时填入实际从站地址

关键点

  • 无 CRC:TCP 校验由底层 TCP 协议完成
  • 无地址 0x00 广播 :TCP 没有真正的广播语义,单元标识符 0xFF 约定为广播
  • MBAP 中的 Length 字段使 TCP 流可以正确切分报文,无需静默间隔
  • 最大报文长度:260 字节

4.2 PDU 一致性

Modbus TCP 的 PDU(功能码 + 数据) 与 RTU/ASCII 完全一致,这是协议统一性的核心保证:

复制代码
Modbus TCP  PDU = [功能码 | 数据]
Modbus RTU  PDU = [功能码 | 数据]         // 相同

5. Modbus RTU-over-TCP(Modbus TCP 变体)

也称为 Modbus TCP/IP with RTU framingRaw Modbus over TCP

将完整的 RTU 帧(含 CRC)直接封装进 TCP 载荷发送,不使用 MBAP 头 。接收方按 RTU 规则解析(静默间隔或 length 预先协商)。常用于串口转以太网模块的透明传输模式。

复制代码
TCP Payload = [地址 | 功能码 | 数据 | CRC-16]

        |<--- 完整的 RTU ADU --->|

注意:RFC 标准建议使用正规 Modbus TCP + MBAP。RTU-over-TCP 是事实存在的兼容方式,但非标准做法。


6. Modbus Plus (MB+)

施耐德电气专有协议,不对外开放帧格式。关键特性:

  • 物理层:RS-485 同轴双绞线,1 Mbps
  • 介质访问:令牌传递(Token Passing),节点数量 2--64
  • 通信模式对等(Peer-to-Peer),无需主站
  • 可承载 Modbus 消息:MB+ 数据帧内部包含标准 Modbus 应用数据

因 MB+ 帧格式为闭源,本文不做位级展开。需使用专用芯片或施耐德通信模块(如 Modicon MBP-M)接入。


7. 数据模型与地址空间

Modbus 定义了四种基本数据对象:

对象类型 大小 访问类型 编号规则 传统 PLC 命名
Coil (线圈) 1 bit 可读可写 00001--09999 %Q, %M
Discrete Input (离散输入) 1 bit 只读 10001--19999 %I
Input Register (输入寄存器) 16 bits 只读 30001--39999 %IW, %AI
Holding Register (保持寄存器) 16 bits 可读可写 40001--49999 %MW, %QW

7.1 地址空间说明

重要历史遗留问题 :传统 5 位 PLC 地址(如 40001)对应 协议内实际地址偏移 1

复制代码
PLC 地址 40001 → 协议帧中地址字段 = 0x0000
PLC 地址 40002 → 协议帧中地址字段 = 0x0001
...
PLC 地址 40100 → 协议帧中地址字段 = 0x0063

现代实现推荐直接使用 0-based 十六进制地址,避免 5 位 PLC 编号混淆。

7.2 数据打包规则

多字节寄存器(32-bit 整数、浮点数等):

数据模型无强制字节序标准,常见的三种实现:

方案 32-bit 值 0x12345678 的字节序列 说明
AB-CD (Big-Endian) 0x1234 0x5678 大部分 PLC 默认
CD-AB (Big-Endian Swap) 0x5678 0x1234 一些 DCS 系统
BA-DC (Little-Endian) 0x3412 0x7856 Windows 兼容设备

位打包 :线圈/离散输入请求中,响应数据每个字节的 LSB 对应最低起始地址

复制代码
例:请求地址 0 读取 8 个线圈,响应字节的位映射:
  bit 0 = 地址 0 的线圈
  bit 1 = 地址 1 的线圈
  ...
  bit 7 = 地址 7 的线圈

8. 功能码详解

8.1 功能码分类

Modbus 按功能码范围分为三类:

范围 分类 说明
1--64 (0x01--0x40) 公共功能码 (Public) 由 Modbus 组织定义并公开文档
65--72 (0x41--0x48) 用户定义功能码 由用户自行实现,无需审批
73--119 (0x49--0x77) 保留 用于扩展
120--127 (0x78--0x7F) 保留 用于内部调试
128--255 (0x80--0xFF) 异常响应 功能码 + 0x80 标识异常

8.2 公共功能码完整清单

位操作(Bit Access)
功能码 名称 操作对象 最大操作量 广播支持 说明
01 (0x01) Read Coils Coil (bit) 2000 线圈 读取 1--N 个线圈状态
02 (0x02) Read Discrete Inputs Discrete Input (bit) 2000 输入 读取 1--N 个离散输入状态
05 (0x05) Write Single Coil Coil (bit) 1 线圈 写入一个线圈为 ON 或 OFF
15 (0x0F) Write Multiple Coils Coil (bit) 1968 线圈(最大 246 字节) 批量写入多个线圈
16 位寄存器操作(16-bit Access)
功能码 名称 操作对象 最大操作量 广播支持 说明
03 (0x03) Read Holding Registers Holding Register (16-bit) 125 寄存器 读取 1--N 个保持寄存器
04 (0x04) Read Input Registers Input Register (16-bit) 125 寄存器 读取 1--N 个输入寄存器
06 (0x06) Write Single Register Holding Register (16-bit) 1 寄存器 写入一个保持寄存器
16 (0x10) Write Multiple Registers Holding Register (16-bit) 123 寄存器(最大 246 字节) 批量写入多个保持寄存器
22 (0x16) Mask Write Register Holding Register (16-bit) 1 寄存器 通过 AND/OR 掩码原子修改单个寄存器
23 (0x17) Read/Write Multiple Registers Holding Register (16-bit) 读 125 / 写 121 一次事务中先读后写同一地址区域
文件记录操作(File Record Access)
功能码 名称 操作对象 说明
20 (0x14) Read File Record 文件子记录 (Sub-record) 读取最多 1--3 个文件记录,每个 1--1000 寄存器
21 (0x15) Write File Record 文件子记录 (Sub-record) 写入最多 1--3 个文件记录,每个 ≤ 646 字节
诊断与系统(Diagnostic & System)
功能码 名称 说明
07 (0x07) Read Exception Status 读取 8 个异常状态位(仅用于串行线路)
08 (0x08) Diagnostic (子功能码 0x00--0x18) 通信诊断、计数器复位、监听等
11 (0x0B) Get Com Event Counter 获取通信事件计数器值
12 (0x0C) Get Com Event Log 获取通信事件日志(64 字节)
17 (0x11) Report Server ID 返回设备的服务器 ID、运行状态、扩展数据
24 (0x18) Read FIFO Queue 读取 FIFO 队列内容
43 (0x2B) Encapsulated Interface Transport 封装接口传输,用于 CANopen 等上层协议

9. 异常码详解

9.1 异常响应结构

当从站无法处理请求时,返回 功能码 + 0x80 + 异常码

复制代码
请求:    0x03 0x00 0x64 0x00 0x01   (读保持寄存器)
异常响应:0x83 0x02                  (功能码 0x83 = 0x03 | 0x80, 异常码 0x02)

9.2 异常码表

码值 名称 含义
01 (0x01) ILLEGAL FUNCTION 功能码不被该从站支持
02 (0x02) ILLEGAL DATA ADDRESS 请求的地址超出范围
03 (0x03) ILLEGAL DATA VALUE 请求的数据值无效(如读取超量)
04 (0x04) SLAVE DEVICE FAILURE 从站执行时发生不可恢复错误
05 (0x05) ACKNOWLEDGE 从站已接受请求,但需长时间处理(仅串行)
06 (0x06) SLAVE DEVICE BUSY 从站忙,请稍后重试
08 (0x08) MEMORY PARITY ERROR 存储区奇偶校验错误
10 (0x0A) GATEWAY PATH UNAVAILABLE 网关路径不可用
11 (0x0B) GATEWAY TARGET DEVICE FAILED TO RESPOND 目标设备无响应

10. 功能码位级详细拆解

本章按功能码逐一拆解 请求 PDU响应 PDU 的每个字节、每个字段的每一位含义。


10.1 功能码 01 (0x01) --- Read Coils

请求 PDU:3 个字段,共 5 字节

字段 字节偏移 大小 范围 说明
功能码 0 1 字节 0x01
起始地址 1--2 2 字节 0x0000--0xFFFF 大端,PLC 地址偏移 -1 后的协议地址
线圈数量 (N) 3--4 2 字节 0x0001--0x07D0 (1--2000) 最大 2000 个线圈

响应 PDU:功能码 + 字节计数 + 线圈数据

字段 字节偏移 大小 说明
功能码 0 1 字节 0x01
字节计数 (M) 1 1 字节 N ÷ 8 向上取整(例: N=10 → M=2)
线圈状态 2 ~ 2+M-1 M 字节 每 1 bit 表示一个线圈状态

线圈位映射:每个字节 8 个线圈,LSB 对应最低地址。

复制代码
字节内位排列(LSB 在右,对应最低地址):
  b7    b6    b5    b4    b3    b2    b1    b0
  ↑                                             ↑
 地址 base+7         ...         地址 base+1    地址 base+0

b0 = 起始地址 + 0 的线圈状态
b1 = 起始地址 + 1 的线圈状态
b2 = 起始地址 + 2 的线圈状态
...
b7 = 起始地址 + 7 的线圈状态
未使用的位(超出 N 的部分)= 0(必须置 0)

线圈状态值

位值 状态
1 (0x01) ON (闭合)
0 (0x00) OFF (断开)

示例 :请求读取地址 0x0000 开始的 10 个线圈

复制代码
请求:0x01 0x00 0x00 0x00 0x0A
返回:0x01 0x02 0xCD 0x01

0xCD = 1100 1101 (二进制)
  b0 = 1 → 地址 0x0000 (ON)
  b1 = 0 → 地址 0x0001 (OFF)
  b2 = 1 → 地址 0x0002 (ON)
  b3 = 1 → 地址 0x0003 (ON)
  b4 = 0 → 地址 0x0004 (OFF)
  b5 = 0 → 地址 0x0005 (OFF)
  b6 = 1 → 地址 0x0006 (ON)
  b7 = 1 → 地址 0x0007 (ON)

0x01 = 0000 0001 (二进制)
  b0 = 1 → 地址 0x0008 (ON)
  b1~b7 未使用,应为 0

10.2 功能码 02 (0x02) --- Read Discrete Inputs

请求 PDU:与功能码 01 完全相同,仅功能码不同

字段 大小 范围
功能码: 0x02 1 字节
起始地址 2 字节 0x0000--0xFFFF
输入数量 (N) 2 字节 0x0001--0x07D0 (1--2000)

响应 PDU

字段 大小 说明
功能码: 0x02 1 字节
字节计数 (M) 1 字节 N ÷ 8 向上取整
输入状态数据 M 字节 位映射同 Read Coils (LSB = 最低地址)

限制 :最大读取 2000 个离散输入。

位值含义

位值 状态
1 输入为 HIGH (闭合)
0 输入为 LOW (断开)

10.3 功能码 03 (0x03) --- Read Holding Registers

请求 PDU

字段 大小 范围
功能码: 0x03 1 字节
起始地址 2 字节 (大端) 0x0000--0xFFFF
寄存器数量 (N) 2 字节 (大端) 0x0001--0x007D (1--125)

响应 PDU

字段 大小 说明
功能码: 0x03 1 字节
字节计数 1 字节 N × 2
寄存器数据 N × 2 字节 大端,每寄存器 2 字节

寄存器数据格式(大端序,高字节在前):

复制代码
寄存器值 = 0x12AB  →  0x12 (高字节)  0xAB (低字节)

多位数据打包示例(32-bit IEEE 754 浮点 123.456 = 0x42F6E979):

复制代码
大端序 (AB-CD)       : 寄存器 0 = 0x42F6, 寄存器 1 = 0xE979
字节序 (Big-Endian)   : 0x42 0xF6 0xE9 0x79

10.4 功能码 04 (0x04) --- Read Input Registers

请求 PDU:同功能码 03,仅功能码不同

字段 大小 范围
功能码: 0x04 1 字节
起始地址 2 字节 (大端) 0x0000--0xFFFF
寄存器数量 2 字节 (大端) 0x0001--0x007D (1--125)

响应 PDU

字段 大小 说明
功能码: 0x04 1 字节
字节计数 1 字节 N × 2
寄存器数据 N × 2 字节 大端序

与 Holding Register 的区别

特性 03 Holding Register 04 Input Register
存取类型 可读可写 只读
物理来源 RAM / 逻辑输出映像 物理传感器 / 模拟量输入通道
典型用途 配置参数、累计值、控制输出 温度、压力、流量、AD 采样值
可写操作 06, 16, 22, 23

10.5 功能码 05 (0x05) --- Write Single Coil

请求 PDU

字段 大小 范围 说明
功能码: 0x05 1 字节
线圈地址 2 字节 (大端) 0x0000--0xFFFF
线圈值 2 字节 (大端) 0xFF000x0000 见下方线圈值编码

线圈值编码 --- 这是最常被误解的字段之一:

写入值 (十六进制) 含义 位模式
0xFF00 ON (闭合) 1111 1111 0000 0000
0x0000 OFF (断开) 0000 0000 0000 0000

任何其他值都是非法 。标准强制要求使用 0xFF000x0000 这两个精确值。

为什么使用 0xFF00 而不是 0x0001?

历史原因:早期机电式 PLC 输出模块通过检测高字节 0xFF(全 1)来触发继电器动作,低字节 0x00 保证与传统继电器的兼容性。这个设计沿用至今。

响应 PDU完全回显请求 PDU(正常响应就是请求的原始内容)。

广播支持:当地址域为 0x00 时,所有从站同时执行写入,从站不返回响应。


10.6 功能码 06 (0x06) --- Write Single Register

请求 PDU

字段 大小 范围 说明
功能码: 0x06 1 字节
寄存器地址 2 字节 (大端) 0x0000--0xFFFF
寄存器值 2 字节 (大端) 0x0000--0xFFFF 写入的 16 位数据

响应 PDU:完全回显请求。

典型用途

  • 设置设定值 (Setpoint)
  • 控制电机启停(通过寄存器位)
  • 写入配置参数(Modbus 寄存器映射)

10.7 功能码 07 (0x07) --- Read Exception Status

仅在 串行线路 (RTU/ASCII) 有效,TCP 不支持。

请求 PDU :仅 1 字节 0x07,无数据字段。

响应 PDU

字段 大小 说明
功能码: 0x07 1 字节
异常状态字节 1 字节 8 个状态位

异常状态字节位含义(由设备厂商定义,以下为最常见约定):

含义
b0 从站状态是否正常 (1=正常)
b1 通信事件缓冲区是否满 (1=满)
b2 当前是否有未处理的异常 (1=有)
b3--b7 厂商自定义,或未使用置 0

10.8 功能码 08 (0x08) --- Diagnostic

请求 PDU

字段 大小 说明
功能码: 0x08 1 字节
子功能码 2 字节 (大端) 0x0000--0x0018
数据 2 字节 (大端) 取决于子功能

响应 PDU:与请求结构一致(回显 + 处理结果数据)。

子功能码速查表

子功能码 名称 数据字段说明
0x0000 Return Query Data 回显请求中的数据(环回测试)
0x0001 Restart Communications Option 0x0000 = 无变化重启;0xFF00 = 清除日志后重启
0x0002 Return Diagnostic Register 返回 16 位诊断寄存器值
0x0003 Change ASCII Input Delimiter 设置 ASCII 报文结束符(默认 0x0A)
0x0004 Force Listen Only Mode 从站进入仅监听模式(不可恢复,需复位)
0x000A Clear Counters and Diagnostic Register 清零所有通信计数器
0x000B Return Bus Message Count 返回接收到的总报文数
0x000C Return Bus Communication Error Count 返回 CRC/帧错误计数
0x000D Return Bus Exception Error Count 返回产生的异常响应计数
0x000E Return Server Message Count 返回从站发出的报文总数
0x000F Return Server No Response Count 返回从站未响应(广播除外)的计数
0x0010 Return Server NAK Count 返回收到非法请求计数
0x0011 Return Server Busy Count 返回从站忙计数
0x0012 Return Bus Character Overrun Count 返回字符超限计数(仅串行线路)
0x0014 Clear Overrun Counter and Flag 清除超限计数器和标志
0x0015--0x0018 保留 用于扩展诊断

子功能码 0x0000 环回测试示例

复制代码
请求:0x08 0x00 0x00 0xA5 0x5A
响应:0x08 0x00 0x00 0xA5 0x5A

10.9 功能码 15 (0x0F) --- Write Multiple Coils

请求 PDU

字段 大小 范围 / 说明
功能码: 0x0F 1 字节
起始地址 2 字节 (大端) 0x0000--0xFFFF
线圈数量 (N) 2 字节 (大端) 0x0001--0x07B0 (1--1968)
字节计数 (M) 1 字节 N ÷ 8 向上取整,1--246
线圈数据 M 字节 每 1 bit 一个线圈,LSB = 最低地址

线圈数据位打包

复制代码
例:N = 20 个线圈,M = 3 字节
  字节 0: 线圈 0--7
  字节 1: 线圈 8--15
  字节 2: 线圈 16--19 (b0--b3 有效,b4--b7 = 0)

响应 PDU(正常):

字段 大小 说明
功能码: 0x0F 1 字节
起始地址 2 字节 回显请求的起始地址
线圈数量 2 字节 回显请求的线圈数量

广播支持:地址域 = 0x00 时广播。


10.10 功能码 16 (0x10) --- Write Multiple Registers

请求 PDU

字段 大小 范围 / 说明
功能码: 0x10 1 字节
起始地址 2 字节 (大端) 0x0000--0xFFFF
寄存器数量 (N) 2 字节 (大端) 0x0001--0x007C (1--123)
字节计数 (M) 1 字节 N × 2,1--246
寄存器值 N × 2 字节 (大端) 每寄存器 2 字节,高字节在前

响应 PDU(正常):

字段 大小 说明
功能码: 0x10 1 字节
起始地址 2 字节 回显请求
寄存器数量 2 字节 回显请求

广播支持:地址域 = 0x00 时广播。


10.11 功能码 22 (0x16) --- Mask Write Register

这是 Modbus 中最原子化 的位操作功能码:通过 AND 掩码和 OR 掩码在 单次总线事务 中修改寄存器的指定位,无需"读-改-写"三步。

请求 PDU

字段 大小 说明
功能码: 0x16 1 字节
引用地址 2 字节 (大端) 目标寄存器地址
AND_MASK 2 字节 (大端) AND 掩码
OR_MASK 2 字节 (大端) OR 掩码

计算公式

复制代码
寄存器新值 = (当前值 & AND_MASK) | (OR_MASK & ~AND_MASK)

位逻辑解释

复制代码
AND_MASK = 0 的位 → 该位被 OR_MASK 覆盖
AND_MASK = 1 的位 → 该位保留原值(OR_MASK 无效)

最终效果:
  - AND_MASK 为 0 的位 → 由 OR_MASK 决定(OR_MASK=1 则置 1,=0 则置 0)
  - AND_MASK 为 1 的位 → 保持原值不变

示例:将寄存器 bit 5 置 1,bit 3 清 0,其余位不变

复制代码
AND_MASK = 0xFFD7  (二进制: 1101 1111 1101 0111)
            bit 3 = 0 → 允许 OR_MASK 覆盖
            bit 5 = 0 → 允许 OR_MASK 覆盖
            其余位 = 1 → 保留原值

OR_MASK  = 0x0020  (二进制: 0000 0000 0010 0000)
            bit 5 = 1 → 将 bit 5 置 1
            bit 3 = 0 → bit 3 被置 0 (AND_MASK=0 且 OR_MASK=0)

响应 PDU:完全回显请求 PDU。

典型应用

  • 原子式修改 PLC 输出映像字的单个或多个位
  • 同时设置/清除标志寄存器中的不同标志位
  • 多线程/多主站环境下避免读-改-写竞争

10.12 功能码 23 (0x17) --- Read/Write Multiple Registers

在同一事务中 先读后写 ,读写操作可以重叠同一地址

请求 PDU

字段 大小 范围 / 说明
功能码: 0x17 1 字节
读起始地址 2 字节 (大端)
读寄存器数量 (NR) 2 字节 (大端) 0x0001--0x007D (1--125)
写起始地址 2 字节 (大端)
写寄存器数量 (NW) 2 字节 (大端) 0x0001--0x0079 (1--121)
写字节计数 1 字节 NW × 2
写寄存器值 NW × 2 字节 (大端) 要写入的数据

关键语义设备先执行读操作,再执行写操作。即便读写地址重叠,写入值也不影响当前读取的返回值。

响应 PDU

字段 大小 说明
功能码: 0x17 1 字节
读字节计数 1 字节 NR × 2
读到的寄存器数据 NR × 2 字节 (大端) 执行写入前的寄存器值

典型应用:PLC 扫描周期内原子式更新控制参数并读取反馈。


10.13 功能码 20 (0x14) --- Read File Record

用于读写 Modbus 设备内存中抽象的 文件记录(File Record) 。每个文件由 1--10000 个 16 位寄存器 组成。

请求 PDU:功能码 + 1--3 个子请求

字段 大小 说明
功能码: 0x14 1 字节
子请求 1 7 字节 见下方子请求结构
子请求 2 (可选) 7 字节
子请求 3 (可选) 7 字节

每个子请求结构

字段 大小 范围 / 说明
引用类型 1 字节 0x06 (固定值,标准文件记录引用)
文件号 2 字节 (大端) 0x0000--0xFFFF
记录起始地址 2 字节 (大端) 文件内偏移
记录长度 2 字节 (大端) 0x0001--0x03E8 (1--1000 寄存器)

响应 PDU

字段 大小 说明
功能码: 0x14 1 字节
文件响应 1 可变 见下方文件响应结构
文件响应 2... 可变

每个文件响应结构

字段 大小 说明
响应长度 1 字节 该子请求响应字节总数
引用类型 1 字节 回显 0x06
记录数据 响应长度 - 1 字节 每个寄存器 2 字节,大端

10.14 功能码 24 (0x18) --- Read FIFO Queue

从设备的 FIFO 队列中读取数据,FIFO 深度通过 FIFO Count Register 指示。

请求 PDU

字段 大小 说明
功能码: 0x18 1 字节
FIFO 计数值寄存器地址 2 字节 (大端) 存放当前 FIFO 深度的寄存器地址

响应 PDU

字段 大小 说明
功能码: 0x18 1 字节
数据长度 2 字节 (大端) 当前响应剩余字节总数(队列计数 + 数据)
FIFO 队列计数 2 字节 (大端) 队列中的数据条目数
FIFO 数据 可变 按 FIFO 顺序排列的寄存器值,每 2 字节大端

限制 :一次最多读取 31 个寄存器 + 队列计数 = 64 字节数据长度。


10.15 功能码 43 (0x2B) / MEI 类型 14 (0x0E) --- Encapsulated Interface Transport

字段 大小 说明
功能码: 0x2B 1 字节
MEI 类型 1 字节 0x0E = CANopen, 0x0D = 其他 MEI
MEI 数据 N 字节 取决于上层协议

最广泛使用的子功能是 0x0E (CANopen 网关)


11. 附录:PDU 与 ADU 结构速查

11.1 PDU (Protocol Data Unit)

PDU = 协议独立的数据单元,所有变种一致

字段 大小 说明
功能码 1 字节 0x01--0x2B (公共码)
数据 N 字节 取决于功能码

11.2 ADU (Application Data Unit)

ADU = PDU + 变种特定的封装。

Modbus RTU ADU

字段 大小 说明
地址 1 字节 0x00 = 广播, 1--247 = 从站地址
PDU N 字节 功能码 + 数据
CRC-16 2 字节 低字节在前
最大总长 256 字节

Modbus ASCII ADU

字段 大小 说明
起始 1 字符 : (0x3A)
地址 2 字符 十六进制 ASCII
PDU N × 2 字符 每个字节展开为 2 个 HEX 字符
LRC 2 字符 纵向冗余校验
结束 2 字符 CR (0x0D) + LF (0x0A)
最大总长 513 字符

Modbus TCP ADU

字段 大小 说明
MBAP 头 7 字节 事务ID(2) + 协议ID(2) + 长度(2) + 单元ID(1)
PDU N 字节 功能码 + 数据
最大总长 260 字节 无 CRC (TCP 层负责)

11.3 CRC-16 (Modbus) 计算伪代码

复制代码
function crc16_modbus(data: byte[]): uint16 {
    crc = 0xFFFF
    for each byte in data {
        crc ^= byte
        for i in 0..7 {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001   // 0xA001 = reverse of 0x8005
            } else {
                crc = crc >> 1
            }
        }
    }
    return crc
}

发送顺序:低字节在前,高字节在后。

复制代码
例:CRC = 0x374B → 发送顺序 0x4B 0x37

11.4 LRC 计算伪代码

复制代码
function lrc(data: byte[]): byte {
    sum = 0
    for each byte in data {
        sum += byte
    }
    return ((sum ^ 0xFF) + 1) & 0xFF   // 补码
}

参考


版权声明:本文档中的功能码、异常码、帧结构定义均来自 Modbus.org 公开标准文档。文中所有位级分析和示例均为原创整理,转载请注明出处。