Modbus 功能码完全解析:从基础到进阶的 21 个标准功能码实战指南

本文目录
  1. 1. 一、Modbus 数据模型与功能码的关系
  2. 2. 二、功能码分类体系
  3. 3. 三、位操作功能码(Bit Access)深度解析
  4. 4. 3.1 功能码 0x01 — 读线圈(Read Coils)
  5. 5. 3.2 功能码 0x02 — 读离散输入(Read Discrete Inputs)
  6. 6. 3.3 功能码 0x05 — 写单个线圈(Write Single Coil)
  7. 7. 3.4 功能码 0x0F — 写多个线圈(Write Multiple Coils)
  8. 8. 四、寄存器操作功能码(16-bit Access)深度解析
  9. 9. 4.1 功能码 0x03 — 读保持寄存器(Read Holding Registers)
  10. 10. 4.2 功能码 0x04 — 读输入寄存器(Read Input Registers)
  11. 11. 4.3 功能码 0x06 — 写单个寄存器(Write Single Register)
  12. 12. 4.4 功能码 0x10 — 写多个寄存器(Write Multiple Registers)
  13. 13. 五、高级功能码详解
  14. 14. 5.1 功能码 0x07 — 读异常状态(Read Exception Status)
  15. 15. 5.2 功能码 0x08 — 诊断(Diagnostics)
  16. 16. 5.3 功能码 0x0B — 读通信事件计数器
  17. 17. 5.4 功能码 0x16 — 掩码写寄存器(Mask Write Register)
  18. 18. 六、功能码对比速查表
  19. 19. 七、功能码选择决策树
  20. 20. 八、常见陷阱与最佳实践
  21. 21. 8.1 字节序陷阱
  22. 22. 8.2 广播地址(地址 0)
  23. 23. 8.3 一次请求勿贪多
  24. 24. 九、实际案例:构建 Modbus 数据采集系统
  25. 25. 十、总结

Modbus 功能码完全解析:从基础到进阶的 21 个标准功能码实战指南

关键词:Modbus 功能码、Modbus Function Code、Modbus 01 02 03 04 05 06 15 16、Modbus 读寄存器、Modbus 写线圈

功能码(Function Code)是 Modbus 协议的灵魂。它定义了主站要对从站执行的操作——是读取数据还是写入数据,操作的对象是线圈、离散输入、保持寄存器还是输入寄存器。理解每一个功能码的用途、报文格式和适用场景,是精通 Modbus 通信的基石。

本文以 Modbus 协议规范 V1.1b3 为蓝本,系统化地解析全部 21 个标准功能码(包括公共功能码、用户定义功能码和保留功能码),并配以完整的报文示例和编程实现,帮助您从「会用的工程师」进阶为「真正理解的专家」。

一、Modbus 数据模型与功能码的关系

Modbus 功能码完全解析:从基础到进阶的 21 个标准功能码实战指南插图
▲ 图1:Modbus 功能码体系架构 — 位操作类与寄存器操作类完整分类。

在深入功能码之前,必须先理解 Modbus 的数据模型。Modbus 定义了四种数据区域:

数据区域访问类型大小地址范围典型用途
线圈(Coils)读写1 bit00001-09999数字量输出(DO)
离散输入(Discrete Inputs)只读1 bit10001-19999数字量输入(DI)
保持寄存器(Holding Registers)读写16 bit40001-49999模拟量输出/参数
输入寄存器(Input Registers)只读16 bit30001-39999模拟量输入(AI)

重要提示:在实际协议报文中,使用的是从 0 开始的「协议地址」。例如,保持寄存器 40001 对应的协议地址是 0x0000。这个「偏移 1」的设计是初学者最容易混淆的地方。

二、功能码分类体系

Modbus 功能码分为三大类:

  • 公共功能码(1-64, 73-99, 111-127):由 Modbus 组织标准化定义,确保不同厂商设备之间的互操作性
  • 用户定义功能码(65-72, 100-110):留给设备厂商自定义实现,不具备通用性
  • 保留功能码:已被某些公司用于传统产品(如 8-11, 12-15 等)

三、位操作功能码(Bit Access)深度解析

Modbus 功能码完全解析:从基础到进阶的 21 个标准功能码实战指南插图1
▲ 图2:四大数据模型(线圈/离散输入/保持寄存器/输入寄存器)与功能码的完整映射关系。

3.1 功能码 0x01 — 读线圈(Read Coils)

功能:读取从站中连续线圈(DO)的 ON/OFF 状态。这是最基本的位读取操作。

请求报文(RTU):

从站地址 | 0x01 | 起始地址高 | 起始地址低 | 数量高 | 数量低 | CRC
示例:读取从站 1 的线圈 0x0013~0x0037(20~56 号线圈,共 37 个)
01 01 00 13 00 25 [CRC]
→ 从站地址=1, 功能码=0x01, 起始地址=0x0013(19), 数量=0x0025(37)

响应报文:

从站地址 | 0x01 | 字节数 | 数据... | CRC
01 01 05 CD 6B B2 0E 1B [CRC]
→ 5 个字节数据, CD=1100 1101 (线圈 27-20 的状态)

关键约束:

  • 单次最多读取 2000 个线圈(0x07D0)
  • 响应中的位打包规则:第一个字节的 LSB 对应起始地址线圈
  • 如果请求的数量不是 8 的倍数,最后一个字节的高位用 0 填充

3.2 功能码 0x02 — 读离散输入(Read Discrete Inputs)

功能:读取从站中连续离散输入(DI)的状态。报文格式与 0x01 完全相同,但操作的是只读的离散输入区。

与 0x01 的区别:0x01 读取的是可读写的线圈(通常对应物理 DO),而 0x02 读取的是只读的离散输入(通常对应物理 DI 如限位开关、传感器状态等)。

示例:读取从站 1 的离散输入 0x00C4~0x00DB(197~220,共 24 个)
请求: 01 02 00 C4 00 18 [CRC]
响应: 01 02 03 AC DB 35 [CRC]
→ 3 个字节, AC=1010 1100, DB=1101 1011, 35=0011 0101

3.3 功能码 0x05 — 写单个线圈(Write Single Coil)

功能:将单个线圈设置为 ON 或 OFF。这是最简单的写操作。

请求报文:

从站地址 | 0x05 | 线圈地址高 | 线圈地址低 | 输出值高 | 输出值低 | CRC
输出值: 0xFF00 表示 ON, 0x0000 表示 OFF

示例:将从站 1 的线圈 0x00AC(173 号)置为 ON
01 05 00 AC FF 00 [CRC]

响应报文:正常响应与请求完全一致(回显)。

编程示例(Python):

import minimalmodbus
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1)
instrument.serial.baudrate = 9600
# 写单个线圈: 地址 0x00AC = ON
instrument.write_bit(0x00AC, 1)

3.4 功能码 0x0F — 写多个线圈(Write Multiple Coils)

功能:一次性写入多个连续线圈。当需要同时控制多个 DO 时,0x0F 比多次调用 0x05 高效得多。

请求报文格式:
从站地址 | 0x0F | 起始地址高 | 起始地址低 | 数量高 | 数量低 | 字节数 | 数据... | CRC

示例:将从站 1 的线圈 0x0013~0x001C(20~29,共 10 个线圈)分别置为:
20: ON, 21: OFF, 22: ON, 23: ON, 24: OFF, 25: ON, 26: ON, 27: OFF, 28: ON, 29: OFF
二进制: 01 0110 1101 → 高位补0 → 0xCD01... 等等这里需要仔细计算
实际报文: 01 0F 00 13 00 0A 02 CD 01 [CRC]

四、寄存器操作功能码(16-bit Access)深度解析

4.1 功能码 0x03 — 读保持寄存器(Read Holding Registers)

功能:读取连续保持寄存器的值。这是 Modbus 中使用频率最高的功能码,没有之一。几乎所有数据采集场景都使用 0x03。

请求报文格式:
从站地址 | 0x03 | 起始地址高 | 起始地址低 | 寄存器数量高 | 寄存器数量低 | CRC

示例:读取从站 1 的保持寄存器 0x006B~0x006D(108~110,共 3 个寄存器)
请求: 01 03 00 6B 00 03 [CRC]
响应: 01 03 06 02 2B 00 00 00 64 [CRC]
→ 6 个字节 = 3 个寄存器 × 2
→ 寄存器 108: 0x022B = 555
→ 寄存器 109: 0x0000 = 0
→ 寄存器 110: 0x0064 = 100

关键约束:

  • 单次最多读取 125 个寄存器(0x007D)
  • 如果读取 32 位数据(float32/int32),确保寄存器数量为偶数
  • 字节序问题:不同设备可能使用大端或小端存储多字节数据

4.2 功能码 0x04 — 读输入寄存器(Read Input Registers)

功能:读取连续输入寄存器的值。报文格式与 0x03 完全相同,但操作的是只读的输入寄存器区(如 ADC 采样值、传感器原始值等)。

示例:读取从站 1 的输入寄存器 0x0008(地址 9,AI 通道 1)
请求: 01 04 00 08 00 01 [CRC]
响应: 01 04 02 00 0A [CRC]
→ 输入寄存器 9 的值 = 0x000A = 10

4.3 功能码 0x06 — 写单个寄存器(Write Single Register)

功能:向单个保持寄存器写入 16 位数值。

请求报文格式:
从站地址 | 0x06 | 寄存器地址高 | 寄存器地址低 | 写入值高 | 写入值低 | CRC

示例:将从站 1 的保持寄存器 0x0001 写入值 0x0003
请求: 01 06 00 01 00 03 [CRC]
响应: 01 06 00 01 00 03 [CRC](回显)

编程示例(C#):

// 使用 NModbus4 库
using Modbus.Device;
using System.IO.Ports;

var port = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
port.Open();
var master = ModbusSerialMaster.CreateRtu(port);
master.WriteSingleRegister(1, 0x0001, 3); // 从站1, 地址1, 值3

4.4 功能码 0x10 — 写多个寄存器(Write Multiple Registers)

功能:一次性写入多个连续保持寄存器。这是批量配置参数的效率利器。

请求报文格式:
从站地址 | 0x10 | 起始地址高 | 起始地址低 | 寄存器数量高 | 寄存器数量低 | 字节数 | 数据... | CRC

示例:向从站 1 的寄存器 0x0001 和 0x0002 分别写入 0x000A 和 0x0102
请求: 01 10 00 01 00 02 04 00 0A 01 02 [CRC]
→ 字节数=4(2个寄存器×2)
响应: 01 10 00 01 00 02 [CRC]
→ 响应仅返回起始地址和寄存器数量

关键约束:

  • 单次最多写入 123 个寄存器(0x007B)
  • 字节数必须等于「寄存器数量 × 2」
  • 写入失败可能导致从站参数混乱,建议先备份

五、高级功能码详解

5.1 功能码 0x07 — 读异常状态(Read Exception Status)

功能:快速读取从站的 8 个异常状态位。这是一个简单但强大的诊断功能码,请求报文只有 2 个字节(地址+功能码),响应也只有 3 个字节。

请求: 01 07 [CRC]
响应: 01 07 6D [CRC]
→ 状态字节 0x6D = 0110 1101,各厂商定义各 bit 含义

5.2 功能码 0x08 — 诊断(Diagnostics)

功能:用于通信链路诊断,共有 16 个子功能码。最常见的用途是「回路测试」(子功能码 0x0000),用来检测从站是否在线并能正常处理报文。

子功能码名称用途
0x0000回路测试回显请求数据,验证通信链路
0x000A清计数器清零通信事件计数器
0x000B读总线消息计数读取从站检测到的总线消息总数
0x000C读通信错误计数读取 CRC 校验错误次数
0x000D读异常错误计数读取从站返回的异常响应次数
0x000E读从站消息计数读取从站已处理的消息数
0x000F读从站无响应计数读取因广播而无需响应的消息数
回路测试示例:
请求: 01 08 00 00 12 34 [CRC]  → 子功能码=0x0000, 数据=0x1234
响应: 01 08 00 00 12 34 [CRC]  → 必须完全回显请求数据

5.3 功能码 0x0B — 读通信事件计数器

返回从站的通信事件计数器和状态字,用于监控通信质量。

5.4 功能码 0x16 — 掩码写寄存器(Mask Write Register)

功能:使用 AND 掩码和 OR 掩码对单个寄存器进行位操作。这是 Modbus 中最巧妙的功能码之一——它允许在不读取当前值的情况下,原子性地修改寄存器的特定位。

请求报文格式:
从站地址 | 0x16 | 地址高 | 地址低 | AND掩码高 | AND掩码低 | OR掩码高 | OR掩码低 | CRC

操作逻辑: Result = (Current_Value AND And_Mask) OR (Or_Mask AND (NOT And_Mask))

示例:将寄存器 0x0004 的 bit 0~3 清零,同时设置 bit 4~7 = 0b1010
AND掩码 = 0xFFF0(清除低4位), OR掩码 = 0x00A0(设置 bit5 和 bit7)
请求: 01 16 00 04 FF F0 00 A0 [CRC]

六、功能码对比速查表

功能码名称操作对象操作类型最多数量使用频率
0x01Read Coils线圈2000★★★★☆
0x02Read Discrete Inputs离散输入2000★★★☆☆
0x03Read Holding Registers保持寄存器125★★★★★
0x04Read Input Registers输入寄存器125★★★★☆
0x05Write Single Coil线圈1★★★☆☆
0x06Write Single Register保持寄存器1★★★★★
0x0FWrite Multiple Coils线圈1968★★★☆☆
0x10Write Multiple Registers保持寄存器123★★★★★
0x16Mask Write Register保持寄存器读写1★★☆☆☆
0x17Read/Write Multiple Regs保持寄存器读写125/121★★☆☆☆

七、功能码选择决策树

在实际开发中,选择合适的功码码是关键决策。以下是选择决策流程:

  • 需要读取数字量输出状态? → 0x01(线圈)/ 0x02(离散输入)
  • 需要读取模拟量或参数? → 0x03(保持寄存器)/ 0x04(输入寄存器)
  • 需要设置单个数字量输出? → 0x05
  • 需要设置单参数? → 0x06
  • 需要批量设置参数? → 0x10
  • 需要批量设置数字量输出? → 0x0F
  • 需要修改参数的某些位而不影响其他位? → 0x16
  • 需要同时读写(如读取旧参数并写入新参数原子操作)? → 0x17

八、常见陷阱与最佳实践

8.1 字节序陷阱

Modbus 协议规定 16 位寄存器使用大端字节序(Big-Endian),但 32 位数据(如 float32)的字节序在协议层面没有规定。常见的有四种排列方式:

排列方式寄存器 1寄存器 2常见厂商
ABCD (Big-Endian)高16位低16位施耐德、ABB
CDAB (Little-Endian)低16位高16位西门子 S7-200
BADC (Word-Swap Big)字节交换部分国产品牌
DCBA (Word-Swap Little)完全颠倒少数特殊设备

8.2 广播地址(地址 0)

在 RTU 模式下,使用从站地址 0 表示广播——所有从站都执行命令,但都不返回响应。只有写操作功能码(0x05, 0x06, 0x0F, 0x10)支持广播。

8.3 一次请求勿贪多

虽然规范允许 0x03 一次读取 125 个寄存器,但不建议一次读取太多。原因:

  • 增大通信延迟(波特率 9600 时,125 个寄存器需要约 270ms)
  • 增加 CRC 校验出错的概率
  • 部分从站的内存限制可能导致缓冲区溢出

建议:单次读取控制在 20~50 个寄存器以内,分为多个小批次读取。

九、实际案例:构建 Modbus 数据采集系统

以下是一个典型的 Modbus 数据采集流程,展示了如何组合使用不同的功能码:

// C 语言伪代码 - 典型 Modbus 采集流程
void modbus_acquisition_task(void) {
    // 步骤 1: 读取设备状态(离散输入)
    uint8_t di_data[2];
    modbus_read_inputs(0x02, &di_data, 16);  // 读16个DI
    
    // 步骤 2: 读取模拟量(输入寄存器)
    uint16_t ai_data[8];
    modbus_read_input_regs(0x00, &ai_data, 8); // 读8个AI通道
    
    // 步骤 3: 读取运行参数(保持寄存器)
    uint16_t params[20];
    modbus_read_holding_regs(0x00, &params, 20);
    
    // 步骤 4: 根据业务逻辑写入控制命令
    if (need_start_motor) {
        modbus_write_coil(0x0005, 1); // 启动电机
    }
    
    // 步骤 5: 如果需要修改参数
    if (need_set_temp) {
        modbus_write_register(0x0010, 250); // 设定温度 25.0°C
    }
}

十、总结

功能码是 Modbus 协议的「动词」——它定义了主站对从站做什么操作。掌握了功能码的报文格式和适用场景,就等于掌握了 Modbus 通信的核心语法。建议将本文的功能码速查表和决策树作为日常开发的参考手册,在实际调试中逐步加深理解。

在下一篇中,我们将深入对比 Modbus RTU 和 Modbus TCP 两种传输模式的差异,从物理层到应用层全面解析两者的适用场景和性能特点。

相关阅读:Modbus 异常响应码与故障排查完全手册 | Modbus RTU 与 TCP 深度对比 | Modbus CRC 校验原理与编程实现

技术术语(共 8 个)—— 点击展开
Modbus RTU基于串行链路的Modbus协议,使用二进制编码和CRC校验
Modbus TCP基于以太网的Modbus协议变体,使用TCP/IP传输
功能码Modbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
寄存器Modbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
波特率串行通信每秒传输符号数,Modbus RTU常用9600/19200
传感器将物理量转换为电信号的检测装置
线圈Modbus位可读写数据,地址从00001开始
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— 点击展开
来源 Modbus中文网(modbus.cn) —— 国内领先的Modbus通信协议技术社区 分类 Modbus通讯协议 字数 6291 字 · 阅读约 16 分钟 更新 2026-06-28 永久链接 https://www.modbus.cn/modbus-function-codes-complete-guide/
推荐工具:Modbus调试助手 微信小程序
Modbus中文网官方推出的Modbus调试工具,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 无需安装,微信搜索「Modbus调试助手」即可使用。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
会员资产

登录后继续管理你的工程权益

登录后可查看订单、下载、钱包积分和会员订阅,适合长期做 Modbus 项目的工程师。

1处 统一管理订单下载
0打扰 不弹窗影响阅读
长期 沉淀调试资产
📝 作者声明
本文由 Modbus中文网技术团队 原创撰写,内容基于实际项目案例与技术文档,力求为读者提供准确、实用的参考信息。
把这篇资料用于真实项目?

进入工具中心进行报文解析、CRC 校验和设备调试,或提交需求获取选型与接入建议。

VIP会员专享

解锁全部Modbus技术资源

5169+工程师已加入VIP,享受无限制工具使用、专属技术文章、优先技术支持等权益

✓ 高级工具无限制使用
✓ VIP专属技术文章+视频教程
✓ 下载资源无限制
✓ 优先工单技术支持
了解VIP权益 低至¥0.3/天 | 支持微信/支付宝 | 随时取消

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注