
1.1 Modbus 协议简介
Modbus 是一种串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年发布,用于可编程逻辑控制器(PLC)之间的通信。经过四十多年的发展,Modbus 已成为工业领域最广泛使用的通信协议之一。
主要特点:
特性 | 说明 |
|---|---|
开放性 | 协议规范完全公开,无版权限制 |
简单性 | 协议结构简洁,易于实现和调试 |
兼容性 | 支持多种物理层和传输模式 |
可靠性 | 成熟稳定,工业现场广泛验证 |
通用性 | 几乎所有工业设备都支持 |
版本 | 说明 | 应用场景 |
|---|---|---|
Modbus RTU | 二进制编码,CRC 校验 | 串口通信,工业现场总线 |
Modbus ASCII | ASCII 字符编码,LRC 校验 | 调试环境,低速通信 |
Modbus TCP | 基于 TCP/IP,以太网传输 | 工业以太网,远程监控 |
Modbus 采用**主从(Master-Slave)**架构:
┌─────────────┐ ┌─────────────┐
│ 主站 │ ──────────────────▶│ 从站 1 │
│ (Master) │ │ (Slave) │
│ │ ◀──────────────────│ │
└─────────────┘ └─────────────┘
│
│ ┌─────────────┐
├─────────▶│ 从站 2 │
│ │ (Slave) │
│ └─────────────┘
│
│ ┌─────────────┐
└─────────▶│ 从站 N │
│ (Slave) │
└─────────────┘通信规则:
主站主动发起请求,从站被动响应
从站只能响应主站的请求,不能主动发送数据
每个从站有唯一的地址(1-247)
地址 0 为广播地址,所有从站接收但不响应
角色 | 说明 | 典型设备 |
|---|---|---|
主站 (Master/Client) | 发起通信请求的一方 | HMI、SCADA、PLC、工控机 |
从站 (Slave/Server) | 响应通信请求的一方 | PLC、传感器、变频器、仪表 |
主站职责:
发起查询请求
管理通信时序
处理从站响应
异常检测与重试
从站职责:
监听并接收请求
解析请求内容
执行相应操作
返回响应数据
主站 从站
│ │
│─────── 请求帧 (Request) ─────────────▶│
│ │ 解析请求
│ │ 执行操作
│ │ 组装响应
│◀─────── 响应帧 (Response) ────────────│
│ │
│ 处理响应数据 │
│ │参数 | 建议值 | 说明 |
|---|---|---|
请求超时 | 1000-5000ms | 等待从站响应的最长时间 |
帧间隔 | 3.5 字符时间 | RTU 模式下帧与帧之间的静默间隔 |
字符间隔 | 1.5 字符时间 | RTU 模式下帧内字符的最大间隔 |
RTU 模式字符时间计算:
字符时间 = 11 位 / 波特率 (秒)
示例 (9600 bps):
字符时间 = 11 / 9600 = 1.146 ms
帧间隔 = 3.5 × 1.146 ≈ 4 ms
Modbus 定义了四种基本数据类型:
数据类型 | 访问方式 | 地址范围 | 数据格式 | 典型应用 |
|---|---|---|---|---|
线圈 (Coil) | 读写 | 00001-09999 | 单个位 (0/1) | 继电器、开关量输出 |
离散输入 (Discrete Input) | 只读 | 10001-19999 | 单个位 (0/1) | 按钮状态、开关量输入 |
输入寄存器 (Input Register) | 只读 | 30001-39999 | 16位字 | 模拟量输入、传感器数据 |
保持寄存器 (Holding Register) | 读写 | 40001-49999 | 16位字 | 参数设置、模拟量输出 |
五位数表示法(常用):
地址格式: XNNNN
X = 数据类型标识
0 = 线圈 (0x)
1 = 离散输入 (1x)
3 = 输入寄存器 (3x)
4 = 保持寄存器 (4x)
NNNN = 地址编号 (0001-9999)
示例:
00001 = 线圈地址 0
10001 = 离散输入地址 0
30001 = 输入寄存器地址 0
40001 = 保持寄存器地址 0
协议地址与逻辑地址对照:
逻辑地址 | 协议地址 (PDU) | 数据类型 |
|---|---|---|
00001 | 0x0000 | 线圈 |
00002 | 0x0001 | 线圈 |
10001 | 0x0000 | 离散输入 |
30001 | 0x0000 | 输入寄存器 |
40001 | 0x0000 | 保持寄存器 |
40002 | 0x0001 | 保持寄存器 |
注意: 协议地址从 0 开始,逻辑地址从 1 开始,两者相差 1。
位数据(线圈/离散输入):
字节排列: [Byte0][Byte1][Byte2]...
位排列: [76543210]... (LSB 在前)
示例: 读取 12 个线圈,值为 1,0,1,1,0,0,1,0,1,1,0,0
存储: Byte0 = 0b01001101 = 0x4D (线圈 0-7)
Byte1 = 0b00000110 = 0x06 (线圈 8-11,高位填充 0)
字数据(寄存器):
大端模式 (Big-Endian):
高字节在前,低字节在后
示例: 寄存器值 0x1234
传输顺序: [0x12][0x34]
帧结构:
┌────────┬────────┬────────┬────────┬────────┐
│ 地址 │ 功能码 │ 数据 │ CRC低 │ CRC高 │
│ 1字节 │ 1字节 │ N字节 │ 1字节 │ 1字节 │
└────────┴────────┴────────┴────────┴────────┘特点:
二进制编码,传输效率高
CRC-16 校验,可靠性高
帧间隔 3.5 字符时间
串口参数:8N1(8数据位,无校验,1停止位)
CRC-16 计算:
多项式: 0xA001
初始值: 0xFFFF
示例: 地址=0x01, 功能码=0x03, 数据=0x00 0x00 0x00 0x0A
CRC = 0xC5CD (低字节在前: 0xCD 0xC5)帧结构:
┌────┬────────┬────────┬────────┬─────┬─────┐
│起始│ 地址 │ 功能码 │ 数据 │ LRC │结束 │
│ ':'│ 2字符 │ 2字符 │ 2N字符 │2字符│CRLF │
└────┴────────┴────────┴────────┴─────┴─────┘特点:
ASCII 字符编码,可读性好
LRC 校验,计算简单
起始符 ':',结束符 CR LF
适合调试和低速通信
LRC 计算:
方法: 所有字节求和取反加 1
示例: 地址=0x01, 功能码=0x03, 数据=0x00 0x00 0x00 0x0A
和 = 0x01 + 0x03 + 0x00 + 0x00 + 0x00 + 0x0A = 0x0E
LRC = (0x100 - 0x0E) = 0xF2
帧结构(MBAP 头 + PDU):
┌────────────────────────── MBAP 头 ──────────────────────────┬──────── PDU ────────┐
│ 事务标识 │ 协议标识 │ 长度 │ 单元标识 │ 功能码 │ 数据 │
│ 2字节 │ 2字节 │ 2字节 │ 1字节 │ 1字节 │ N字节 │
└──────────┴──────────┴────────┴──────────┴────────┴────────────┘MBAP 头字段说明:
字段 | 长度 | 说明 |
|---|---|---|
事务标识 | 2字节 | 请求/响应匹配标识,递增计数 |
协议标识 | 2字节 | Modbus 协议 = 0x0000 |
长度 | 2字节 | 后续字节数(单元标识 + PDU) |
单元标识 | 1字节 | 从站地址(RTU 兼容) |
特点:
基于 TCP/IP,端口 502
无需校验(TCP 保证可靠性)
支持多连接并发
适合工业以太网
TCP 与 RTU 帧对照:
RTU 帧: [01][03][00 00 00 0A][CD C5]
TCP 帧: [00 01][00 00][00 06][01][03][00 00][00 0A]
───── ───── ───── ── ────────────────
事务ID 协议ID 长度 地址 PDU 部分功能码: 0x01 (1)
功能描述: 读取从站中连续的线圈(输出位)状态,返回 ON/OFF 状态。
适用范围:
读取继电器输出状态
读取开关量输出
读取可写位状态
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x01 | 固定值 |
2-3 | 起始地址 | 读取起始地址 | 0x0000-0xFFFF |
4-5 | 数量 | 读取线圈数量 | 0x0001-0x07D0 (1-2000) |
6-7 | CRC | 校验码 (RTU) | - |
请求帧示例: 读取从站 1,地址 0 开始的 10 个线圈
RTU 格式:
┌────┬────┬─────────┬────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │ CRC │
├────┼────┼─────────┼────────┼─────────┤
│ 01 │ 01 │ 00 00 │ 00 0A │ ED CE │
└────┴────┴─────────┴────────┴─────────┘
TCP 格式:
┌────────┬────────┬────────┬────┬────┬─────────┬────────┐
│事务标识│协议标识│ 长度 │地址│功能码│起始地址 │ 数量 │
├────────┼────────┼────────┼────┼────┼─────────┼────────┤
│ 00 01 │ 00 00 │ 00 06 │ 01 │ 01 │ 00 00 │ 00 0A │
└────────┴────────┴────────┴────┴────┴─────────┴────────┘响应帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x01 | 固定值 |
2 | 字节数 | 返回数据字节数 | N = ceil(数量/8) |
3-N+2 | 数据 | 线圈状态数据 | 每字节8个线圈 |
N+3-N+4 | CRC | 校验码 (RTU) | - |
响应帧示例: 10 个线圈状态为 1,0,1,1,0,0,1,0,1,1
RTU 格式:
┌────┬────┬──────┬─────────┬─────────┐
│地址│功能码│字节数│ 数据 │ CRC │
├────┼────┼──────┼─────────┼─────────┤
│ 01 │ 01 │ 02 │ 4D 06 │ 89 87 │
└────┴────┴──────┴─────────┴─────────┘
数据解析:
Byte0 = 0x4D = 0b01001101
位: 7 6 5 4 3 2 1 0
值: 0 1 0 0 1 1 0 1
线圈: - - - - 4 3 2 1 (实际: 1,0,1,1,0,0,1,0)
Byte1 = 0x06 = 0b00000110
位: 7 6 5 4 3 2 1 0
值: 0 0 0 0 0 1 1 0
线圈: - - - - - 10 9 8 (实际: 1,1,0,-,-,-,-,-)典型应用案例:
场景: 读取 PLC 的 8 路继电器输出状态
请求: 读取从站 1,地址 0 开始的 8 个线圈
帧: 01 01 00 00 00 08 FD CC
响应: 8 路继电器状态为 ON,OFF,ON,ON,OFF,OFF,ON,OFF
帧: 01 01 01 4D 60 49
解析: 0x4D = 0b01001101
继电器: 8 7 6 5 4 3 2 1
状态: 0 1 0 0 1 1 0 1
即: R1=ON, R2=OFF, R3=ON, R4=ON, R5=OFF, R6=OFF, R7=ON, R8=OFF
功能码: 0x02 (2)
功能描述: 读取从站中连续的离散输入(输入位)状态,返回 ON/OFF 状态。
适用范围:
读取按钮状态
读取开关量输入
读取传感器数字信号
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x02 | 固定值 |
2-3 | 起始地址 | 读取起始地址 | 0x0000-0xFFFF |
4-5 | 数量 | 读取输入数量 | 0x0001-0x07D0 (1-2000) |
6-7 | CRC | 校验码 (RTU) | - |
请求帧示例: 读取从站 1,地址 0 开始的 8 个离散输入
RTU 格式:
┌────┬────┬─────────┬────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │ CRC │
├────┼────┼─────────┼────────┼─────────┤
│ 01 │ 02 │ 00 00 │ 00 08 │ 79 C9 │
└────┴────┴─────────┴────────┴─────────┘响应帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x02 | 固定值 |
2 | 字节数 | 返回数据字节数 | N = ceil(数量/8) |
3-N+2 | 数据 | 离散输入状态 | 每字节8个输入 |
N+3-N+4 | CRC | 校验码 (RTU) | - |
响应帧示例:
RTU 格式:
┌────┬────┬──────┬─────────┬─────────┐
│地址│功能码│字节数│ 数据 │ CRC │
├────┼────┼──────┼─────────┼─────────┤
│ 01 │ 02 │ 01 │ AC │ 61 81 │
└────┴────┴──────┴─────────┴─────────┘数据解析:
0xAC = 0b10101100
输入: 8 7 6 5 4 3 2 1
状态: 1 0 1 0 1 1 0 0
典型应用案例:
场景: 读取 8 个限位开关状态
请求: 读取从站 1,地址 0 开始的 8 个离散输入
帧: 01 02 00 00 00 08 79 C9
响应: 限位开关状态
帧: 01 02 01 AC 61 81
解析: 0xAC = 10101100
开关: S1=OFF, S2=OFF, S3=ON, S4=ON, S5=OFF, S6=ON, S7=OFF, S8=ON
功能码: 0x03 (3)
功能描述: 读取从站中连续的保持寄存器值,每个寄存器为 16 位无符号整数。
适用范围:
读取参数设置
读取配置数据
读取模拟量输出
读取内部寄存器
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x03 | 固定值 |
2-3 | 起始地址 | 读取起始地址 | 0x0000-0xFFFF |
4-5 | 数量 | 读取寄存器数量 | 0x0001-0x007D (1-125) |
6-7 | CRC | 校验码 (RTU) | - |
请求帧示例: 读取从站 1,地址 0 开始的 3 个保持寄存器
RTU 格式:
┌────┬────┬─────────┬────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │ CRC │
├────┼────┼─────────┼────────┼─────────┤
│ 01 │ 03 │ 00 00 │ 00 03 │ 84 0A │
└────┴────┴─────────┴────────┴─────────┘
TCP 格式:
┌────────┬────────┬────────┬────┬────┬─────────┬────────┐
│事务标识│协议标识│ 长度 │地址│功能码│起始地址 │ 数量 │
├────────┼────────┼────────┼────┼────┼─────────┼────────┤
│ 00 01 │ 00 00 │ 00 06 │ 01 │ 03 │ 00 00 │ 00 03 │
└────────┴────────┴────────┴────┴────┴─────────┴────────┘响应帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x03 | 固定值 |
2 | 字节数 | 返回数据字节数 | 数量 × 2 |
3-N+2 | 数据 | 寄存器值 | 每寄存器2字节 |
N+3-N+4 | CRC | 校验码 (RTU) | - |
响应帧示例: 3 个寄存器值为 100, 200, 300
RTU 格式:
┌────┬────┬──────┬───────────────────┬─────────┐
│地址│功能码│字节数│ 数据 │ CRC │
├────┼────┼──────┼───────────────────┼─────────┤
│ 01 │ 03 │ 06 │ 00 64 00 C8 01 2C │ 85 D8 │
└────┴────┴──────┴───────────────────┴─────────┘数据解析:
寄存器 0: 0x0064 = 100
寄存器 1: 0x00C8 = 200
寄存器 2: 0x012C = 300
典型应用案例:
场景: 读取变频器频率设定值、输出频率、输出电流
请求: 读取从站 1,地址 0 开始的 3 个寄存器
帧: 01 03 00 00 00 03 84 0A
响应: 频率设定=50.0Hz, 输出频率=49.8Hz, 电流=5.2A
帧: 01 03 06 01 F4 01 F2 00 34 XX XX
解析:
寄存器 0: 0x01F4 = 500 → 50.0Hz (精度0.1)
寄存器 1: 0x01F2 = 498 → 49.8Hz (精度0.1)
寄存器 2: 0x0034 = 52 → 5.2A (精度0.1)
功能码: 0x04 (4)
功能描述: 读取从站中连续的输入寄存器值,每个寄存器为 16 位无符号整数。与保持寄存器不同,输入寄存器为只读。
适用范围:
读取模拟量输入
读取传感器数据
读取测量值
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x04 | 固定值 |
2-3 | 起始地址 | 读取起始地址 | 0x0000-0xFFFF |
4-5 | 数量 | 读取寄存器数量 | 0x0001-0x007D (1-125) |
6-7 | CRC | 校验码 (RTU) | - |
请求帧示例: 读取从站 1,地址 0 开始的 2 个输入寄存器
RTU 格式:
┌────┬────┬─────────┬────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │ CRC │
├────┼────┼─────────┼────────┼─────────┤
│ 01 │ 04 │ 00 00 │ 00 02 │ 71 0B │
└────┴────┴─────────┴────────┴─────────┘响应帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x04 | 固定值 |
2 | 字节数 | 返回数据字节数 | 数量 × 2 |
3-N+2 | 数据 | 寄存器值 | 每寄存器2字节 |
N+3-N+4 | CRC | 校验码 (RTU) | - |
响应帧示例:
RTU 格式:
┌────┬────┬──────┬─────────────┬─────────┐
│地址│功能码│字节数│ 数据 │ CRC │
├────┼────┼──────┼─────────────┼─────────┤
│ 01 │ 04 │ 04 │ 03 E8 07 D0 │ FB 88 │
└────┴────┴──────┴─────────────┴─────────┘数据解析:
寄存器 0: 0x03E8 = 1000 (温度: 100.0°C)
寄存器 1: 0x07D0 = 2000 (压力: 200.0kPa)
典型应用案例:
场景: 读取温度传感器和压力传感器数据
请求: 读取从站 1,地址 0 开始的 2 个输入寄存器
帧: 01 04 00 00 00 02 71 0B
响应: 温度=25.6°C, 压力=101.3kPa
帧: 01 04 04 01 00 03 F5 XX XX
解析:
寄存器 0: 0x0100 = 256 → 25.6°C (精度0.1)
寄存器 1: 0x03F5 = 1013 → 101.3kPa (精度0.1)
功能码: 0x05 (5)
功能描述: 将单个线圈设置为 ON 或 OFF 状态。
适用范围:
控制继电器输出
控制开关量输出
远程控制设备启停
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x05 | 固定值 |
2-3 | 输出地址 | 线圈地址 | 0x0000-0xFFFF |
4-5 | 输出值 | 线圈状态 | 0xFF00=ON, 0x0000=OFF |
6-7 | CRC | 校验码 (RTU) | - |
请求帧示例: 设置从站 1,地址 0 的线圈为 ON
RTU 格式:
┌────┬────┬─────────┬─────────┬─────────┐
│地址│功能码│输出地址 │ 输出值 │ CRC │
├────┼────┼─────────┼─────────┼─────────┤
│ 01 │ 05 │ 00 00 │ FF 00 │ 8C 3A │
└────┴────┴─────────┴─────────┴─────────┘
设置 OFF:
│ 01 │ 05 │ 00 00 │ 00 00 │ CD CA │响应帧格式:
正常响应为原样返回请求帧:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x05 |
2-3 | 输出地址 | 线圈地址 |
4-5 | 输出值 | 线圈状态 |
6-7 | CRC | 校验码 |
响应帧示例:
RTU 格式 (成功):
┌────┬────┬─────────┬─────────┬─────────┐
│地址│功能码│输出地址 │ 输出值 │ CRC │
├────┼────┼─────────┼─────────┼─────────┤
│ 01 │ 05 │ 00 00 │ FF 00 │ 8C 3A │
└────┴────┴─────────┴─────────┴─────────┘典型应用案例:
场景: 远程启动电机
请求: 设置从站 1,地址 0 的线圈为 ON (启动电机)
帧: 01 05 00 00 FF 00 8C 3A
响应: 成功
帧: 01 05 00 00 FF 00 8C 3A
场景: 远程停止电机
请求: 设置从站 1,地址 0 的线圈为 OFF (停止电机)
帧: 01 05 00 00 00 00 CD CA
响应: 成功
帧: 01 05 00 00 00 00 CD CA
功能码: 0x06 (6)
功能描述: 将单个保持寄存器设置为指定值。
适用范围:
设置参数值
设置频率/速度
设置控制字
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x06 | 固定值 |
2-3 | 寄存器地址 | 目标寄存器地址 | 0x0000-0xFFFF |
4-5 | 寄存器值 | 写入值 | 0x0000-0xFFFF |
6-7 | CRC | 校验码 (RTU) | - |
请求帧示例: 设置从站 1,地址 0 的寄存器为 100
RTU 格式:
┌────┬────┬──────────┬─────────┬─────────┐
│地址│功能码│寄存器地址│ 寄存器值│ CRC │
├────┼────┼──────────┼─────────┼─────────┤
│ 01 │ 06 │ 00 00 │ 00 64 │ 88 1E │
└────┴────┴──────────┴─────────┴─────────┘响应帧格式:
正常响应为原样返回请求帧:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x06 |
2-3 | 寄存器地址 | 目标寄存器地址 |
4-5 | 寄存器值 | 写入值 |
6-7 | CRC | 校验码 |
响应帧示例:
RTU 格式 (成功):
┌────┬────┬──────────┬─────────┬─────────┐
│地址│功能码│寄存器地址│ 寄存器值│ CRC │
├────┼────┼──────────┼─────────┼─────────┤
│ 01 │ 06 │ 00 00 │ 00 64 │ 88 1E │
└────┴────┴──────────┴─────────┴─────────┘典型应用案例:
场景: 设置变频器频率为 50.0Hz
请求: 设置从站 1,地址 0 的寄存器为 500 (50.0Hz)
帧: 01 06 00 00 01 F4 88 30
响应: 成功
帧: 01 06 00 00 01 F4 88 30
解析:
寄存器值: 0x01F4 = 500
实际频率: 500 × 0.1 = 50.0Hz
功能码: 0x0F (15)
功能描述: 将多个连续的线圈设置为 ON 或 OFF 状态。
适用范围:
批量控制继电器
批量设置输出状态
模式切换
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x0F | 固定值 |
2-3 | 起始地址 | 写入起始地址 | 0x0000-0xFFFF |
4-5 | 数量 | 写入线圈数量 | 0x0001-0x07B0 (1-1968) |
6 | 字节数 | 数据字节数 | N = ceil(数量/8) |
7-N+6 | 数据 | 线圈状态数据 | 每字节8个线圈 |
N+7-N+8 | CRC | 校验码 (RTU) | - |
请求帧示例: 设置从站 1,地址 0 开始的 10 个线圈
设置值: 1,0,1,1,0,0,1,0,1,1
RTU 格式:
┌────┬────┬─────────┬────────┬──────┬─────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │字节数│ 数据 │ CRC │
├────┼────┼─────────┼────────┼──────┼─────────┼─────────┤
│ 01 │ 0F │ 00 00 │ 00 0A │ 02 │ 4D 06 │ 26 99 │
└────┴────┴─────────┴────────┴──────┴─────────┴─────────┘数据解析:
Byte0 = 0x4D = 0b01001101 (线圈 0-7)
Byte1 = 0x06 = 0b00000110 (线圈 8-9)
响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x0F |
2-3 | 起始地址 | 写入起始地址 |
4-5 | 数量 | 写入线圈数量 |
6-7 | CRC | 校验码 |
响应帧示例:
RTU 格式:
┌────┬────┬─────────┬────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │ CRC │
├────┼────┼─────────┼────────┼─────────┤
│ 01 │ 0F │ 00 00 │ 00 0A │ 26 99 │
└────┴────┴─────────┴────────┴─────────┘典型应用案例:
场景: 控制 8 路继电器,设置 1,3,5,7 为 ON,2,4,6,8 为 OFF
请求: 设置从站 1,地址 0 开始的 8 个线圈
数据: 0b01010101 = 0x55
帧: 01 0F 00 00 00 08 01 55 DF 8B
响应: 成功
帧: 01 0F 00 00 00 08 A6 8B
解析:
继电器: 8 7 6 5 4 3 2 1
状态: 0 1 0 1 0 1 0 1
OFF ON OFF ON OFF ON OFF ON
功能码: 0x10 (16)
功能描述: 将多个连续的保持寄存器设置为指定值。
适用范围:
批量设置参数
写入配置数据
多参数联动设置
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x10 | 固定值 |
2-3 | 起始地址 | 写入起始地址 | 0x0000-0xFFFF |
4-5 | 数量 | 写入寄存器数量 | 0x0001-0x007B (1-123) |
6 | 字节数 | 数据字节数 | 数量 × 2 |
7-N+6 | 数据 | 寄存器值 | 每寄存器2字节 |
N+7-N+8 | CRC | 校验码 (RTU) | - |
请求帧示例: 设置从站 1,地址 0 开始的 3 个寄存器
设置值: 100, 200, 300
RTU 格式:
┌────┬────┬─────────┬────────┬──────┬───────────────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │字节数│ 数据 │ CRC │
├────┼────┼─────────┼────────┼──────┼───────────────────┼─────────┤
│ 01 │ 10 │ 00 00 │ 00 03 │ 06 │ 00 64 00 C8 01 2C │ 89 8F │
└────┴────┴─────────┴────────┴──────┴───────────────────┴─────────┘数据解析:
寄存器 0: 0x0064 = 100
寄存器 1: 0x00C8 = 200
寄存器 2: 0x012C = 300
响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x10 |
2-3 | 起始地址 | 写入起始地址 |
4-5 | 数量 | 写入寄存器数量 |
6-7 | CRC | 校验码 |
响应帧示例:
RTU 格式:
┌────┬────┬─────────┬────────┬─────────┐
│地址│功能码│起始地址 │ 数量 │ CRC │
├────┼────┼─────────┼────────┼─────────┤
│ 01 │ 10 │ 00 00 │ 00 03 │ 40 08 │
└────┴────┴─────────┴────────┴─────────┘典型应用案例:
场景: 设置变频器参数 (频率=50Hz, 加速时间=10s, 减速时间=15s)
请求: 设置从站 1,地址 0 开始的 3 个寄存器
值: 500, 100, 150
帧: 01 10 00 00 00 03 06 01 F4 00 64 00 96 XX XX
响应: 成功
帧: 01 10 00 00 00 03 40 08
解析:
寄存器 0: 0x01F4 = 500 → 50.0Hz
寄存器 1: 0x0064 = 100 → 10.0s (精度0.1)
寄存器 2: 0x0096 = 150 → 15.0s (精度0.1)
功能码: 0x16 (22)
功能描述: 使用屏蔽算法修改单个保持寄存器的特定位,不影响其他位。
适用范围:
修改控制字的特定位
状态标志位操作
位级参数设置
算法原理:
结果 = (当前值 AND andMask) OR (orMask AND (NOT andMask))
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x16 | 固定值 |
2-3 | 寄存器地址 | 目标寄存器地址 | 0x0000-0xFFFF |
4-5 | AND 屏蔽 | AND 掩码 | 0x0000-0xFFFF |
6-7 | OR 屏蔽 | OR 掩码 | 0x0000-0xFFFF |
8-9 | CRC | 校验码 (RTU) | - |
请求帧示例:
场景: 将寄存器地址 0 的第 3 位设置为 1,其他位不变
AND 屏蔽 = 0xFFF7 (第3位为0,其他位为1)
OR 屏蔽 = 0x0008 (第3位为1,其他位为0)
RTU 格式:
┌────┬────┬──────────┬─────────┬─────────┬─────────┐
│地址│功能码│寄存器地址│AND屏蔽 │ OR屏蔽 │ CRC │
├────┼────┼──────────┼─────────┼─────────┼─────────┤
│ 01 │ 16 │ 00 00 │ FF F7 │ 00 08 │ 98 16 │
└────┴────┴──────────┴─────────┴─────────┴─────────┘响应帧格式:
正常响应为原样返回请求帧。
典型应用案例:
场景: 修改控制字的第 0 位(启动位)
当前值: 0x0000 (停止状态)
目标: 设置第 0 位为 1 (启动)
AND 屏蔽 = 0xFFFE (保持其他位不变)
OR 屏蔽 = 0x0001 (设置第 0 位)
请求: 01 16 00 00 FF FE 00 01 98 16
执行过程:
结果 = (0x0000 AND 0xFFFE) OR (0x0001 AND 0x0001)
= 0x0000 OR 0x0001
= 0x0001
响应: 01 16 00 00 FF FE 00 01 98 16
功能码: 0x17 (23)
功能描述: 在一个请求中完成读取和写入操作,原子性操作。
适用范围:
读取状态同时设置参数
减少通信次数
需要原子操作的场景
请求帧格式:
字节 | 字段 | 说明 | 取值范围 |
|---|---|---|---|
0 | 从站地址 | 目标从站地址 | 0x01-0xF7 |
1 | 功能码 | 0x17 | 固定值 |
2-3 | 读取起始地址 | 读取起始地址 | 0x0000-0xFFFF |
4-5 | 读取数量 | 读取寄存器数量 | 0x0001-0x007D |
6-7 | 写入起始地址 | 写入起始地址 | 0x0000-0xFFFF |
8-9 | 写入数量 | 写入寄存器数量 | 0x0001-0x0079 |
10 | 写入字节数 | 写入数据字节数 | 写入数量 × 2 |
11-N+10 | 写入数据 | 写入寄存器值 | 每寄存器2字节 |
N+11-N+12 | CRC | 校验码 (RTU) | - |
请求帧示例:
场景: 读取地址 0 开始的 3 个寄存器,同时写入地址 10 开始的 2 个寄存器
写入值: 100, 200
RTU 格式:
┌────┬────┬───────────┬──────────┬───────────┬──────────┬──────┬───────────┬─────────┐
│地址│功能码│读起始地址 │ 读数量 │写起始地址 │ 写数量 │字节数│ 写入数据 │ CRC │
├────┼────┼───────────┼──────────┼───────────┼──────────┼──────┼───────────┼─────────┤
│ 01 │ 17 │ 00 00 │ 00 03 │ 00 0A │ 00 02 │ 04 │ 00 64 00 C8│ XX XX │
└────┴────┴───────────┴──────────┴───────────┴──────────┴──────┴───────────┴─────────┘响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x17 |
2 | 字节数 | 读取数据字节数 |
3-N+2 | 读取数据 | 读取的寄存器值 |
N+3-N+4 | CRC | 校验码 |
典型应用案例:
场景: 设置变频器频率并读取当前状态
请求:
- 写入: 地址 0 (频率设定) = 500 (50.0Hz)
- 读取: 地址 10 开始的 3 个寄存器 (状态、电流、电压)
帧: 01 17 00 0A 00 03 00 00 00 01 02 01 F4 XX XX
响应:
寄存器 10: 0x0001 = 运行中
寄存器 11: 0x0034 = 5.2A
寄存器 12: 0x0258 = 380V
帧: 01 17 06 00 01 00 34 02 58 XX XX
功能码: 0x07 (7)
功能描述: 读取从站的异常状态字节,用于快速诊断设备状态。
请求帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 目标从站地址 |
1 | 功能码 | 0x07 |
请求帧示例:
RTU: 01 07 41 0E
响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x07 |
2 | 异常状态 | 1字节状态值 |
响应帧示例:
RTU: 01 07 55 41 0E
状态: 0x55 = 0b01010101
功能码: 0x08 (8)
功能描述: 提供一系列诊断测试功能,用于通信链路测试和故障诊断。
子功能码:
子功能码 | 名称 | 说明 |
|---|---|---|
0x0000 | 返回查询数据 | 原样返回请求数据 |
0x0001 | 重启通信选项 | 重置通信参数 |
0x0002 | 返回诊断寄存器 | 返回诊断寄存器值 |
0x0003 | 更改 ASCII 输入分隔符 | 设置分隔符 |
0x0004 | 强制监听模式 | 进入监听模式 |
0x000A | 清除计数器 | 清除所有计数器 |
0x000B | 返回总线消息计数 | 返回消息计数 |
0x000C | 返回总线通信错误计数 | 返回错误计数 |
0x000D | 返回总线异常错误计数 | 返回异常计数 |
0x000E | 返回从站消息计数 | 返回从站消息计数 |
0x000F | 返回从站无响应计数 | 返回无响应计数 |
0x0010 | 返回从站 NAK 计数 | 返回 NAK 计数 |
0x0011 | 返回从站忙计数 | 返回忙计数 |
0x0012 | 返回总线字符溢出计数 | 返回溢出计数 |
请求帧示例(返回查询数据):
RTU: 01 08 00 00 A5 37 XX XX
└──┴─ 子功能码
└──┴─ 数据字段功能码: 0x11 (17)
功能描述: 读取从站的服务器标识信息,包括设备类型、版本等。
请求帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 目标从站地址 |
1 | 功能码 | 0x11 |
响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x11 |
2 | 字节数 | 数据长度 |
3-N+2 | 数据 | 服务器 ID 数据 |
响应数据格式(典型):
字节 0: 设备类型
字节 1: 运行状态 (0x00=OFF, 0xFF=ON)
字节 2-N: 附加数据 (厂商自定义)
功能码: 0x14 (20)
功能描述: 读取设备文件中的记录数据,支持多个子请求。
请求帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 目标从站地址 |
1 | 功能码 | 0x14 |
2 | 字节数 | 请求数据长度 |
3 | 参考类型 | 固定值 0x06 |
4-5 | 文件号 | 文件编号 |
6-7 | 记录号 | 记录编号 |
8-9 | 记录长度 | 记录长度 (寄存器数) |
功能码: 0x15 (21)
功能描述: 将数据写入设备文件中的记录。
请求帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 目标从站地址 |
1 | 功能码 | 0x15 |
2 | 字节数 | 请求数据长度 |
3 | 参考类型 | 固定值 0x06 |
4-5 | 文件号 | 文件编号 |
6-7 | 记录号 | 记录编号 |
8-9 | 记录长度 | 记录长度 (寄存器数) |
10-N | 数据 | 写入数据 |
功能码: 0x18 (24)
功能描述: 读取先进先出 (FIFO) 队列中的数据。
请求帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 目标从站地址 |
1 | 功能码 | 0x18 |
2-3 | FIFO 地址 | FIFO 队列地址 |
响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x18 |
2-3 | 字节数 | 数据字节数 + 2 |
4-5 | FIFO 计数 | FIFO 数据个数 |
6-N | 数据 | FIFO 数据 |
功能码: 0x2B (43)
功能描述: 读取设备的标识信息,支持基本、常规、扩展和特定对象读取。
MEI 类型: 0x0E (14)
读取模式:
模式 | 代码 | 说明 |
|---|---|---|
Basic | 0x01 | 基本标识 (厂商、产品、版本) |
Regular | 0x02 | 常规标识 |
Extended | 0x03 | 扩展标识 |
Specific | 0x04 | 特定对象标识 |
对象类型:
代码 | 对象 | 说明 |
|---|---|---|
0x00 | VendorName | 厂商名称 |
0x01 | ProductCode | 产品代码 |
0x02 | MajorMinorRevision | 版本号 |
0x03 | VendorUrl | 厂商 URL |
0x04 | ProductName | 产品名称 |
0x05 | ModelName | 型号名称 |
0x06 | UserApplicationName | 用户应用名称 |
请求帧示例(读取基本标识):
RTU: 01 2B 0E 01 00 XX XX
└─ MEI类型
└─ 读取模式 (Basic)
└─ 起始对象ID响应帧格式:
字节 | 字段 | 说明 |
|---|---|---|
0 | 从站地址 | 从站地址 |
1 | 功能码 | 0x2B |
2 | MEI 类型 | 0x0E |
3 | 一致性等级 | 设备支持级别 |
4 | 更多数据 | 0x00=无更多, 0xFF=有更多 |
5 | 下一对象ID | 下一个对象标识 |
6 | 对象数量 | 返回对象个数 |
7-N | 对象数据 | 对象ID + 长度 + 值 |
当从站无法正常处理请求时,返回异常响应:
┌────┬────────────┬──────────┐
│地址│ 功能码 │ 异常码 │
│1字节│ 1字节 │ 1字节 │
└────┴────────────┴──────────┘功能码 = 请求功能码 | 0x80
异常响应示例:
请求: 01 03 00 00 00 01 84 0A (读取1个寄存器)
响应: 01 83 02 C0 F1 (非法数据地址)
解析:
地址: 0x01
功能码: 0x83 = 0x03 | 0x80 (异常响应)
异常码: 0x02 = 非法数据地址
异常码 | 名称 | 说明 | 常见原因 |
|---|---|---|---|
0x01 | ILLEGAL FUNCTION | 非法功能码 | 设备不支持该功能码 |
0x02 | ILLEGAL DATA ADDRESS | 非法数据地址 | 地址超出范围或不存在 |
0x03 | ILLEGAL DATA VALUE | 非法数据值 | 数据值超出范围或无效 |
0x04 | SLAVE DEVICE FAILURE | 从站设备故障 | 设备内部错误 |
0x05 | ACKNOWLEDGE | 确认 | 请求已接受,处理中 |
0x06 | SLAVE DEVICE BUSY | 从站忙 | 设备正在处理其他请求 |
0x07 | NEGATIVE ACKNOWLEDGE | 否定确认 | 设备拒绝执行 |
0x08 | MEMORY PARITY ERROR | 内存奇偶错误 | 内存校验失败 |
0x0A | GATEWAY PATH UNAVAILABLE | 网关路径不可用 | 网关配置错误 |
0x0B | GATEWAY TARGET DEVICE FAILED TO RESPOND | 网关目标设备无响应 | 目标设备离线 |
超时处理:
1. 设置合理的超时时间 (建议 1-5 秒)
2. 超时后重试 (建议最多 3 次)
3. 重试间隔递增 (如 100ms, 200ms, 400ms)
4. 记录超时日志便于排查
异常码处理流程:
收到异常响应
│
├─ 0x01 (非法功能码) → 检查设备是否支持该功能
│
├─ 0x02 (非法地址) → 检查地址范围是否正确
│
├─ 0x03 (非法值) → 检查数据格式和范围
│
├─ 0x04 (设备故障) → 检查设备状态,稍后重试
│
├─ 0x05 (确认) → 等待处理完成,轮询结果
│
├─ 0x06 (从站忙) → 等待后重试
│
└─ 其他 → 记录日志,人工排查
public static class ModbusCrc
{
public static ushort Calculate(byte[] data, int length)
{
ushort crc = 0xFFFF;
for (int i = 0; i < length; i++)
{
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
}public static byte CalculateLrc(byte[] data, int length)
{
byte lrc = 0;
for (int i = 0; i < length; i++)
{
lrc += data[i];
}
return (byte)((~lrc + 1) & 0xFF);
}波特率 | 1字符时间 | 3.5字符时间 | 1.5字符时间 |
|---|---|---|---|
1200 | 9.17 ms | 32.08 ms | 13.75 ms |
2400 | 4.58 ms | 16.04 ms | 6.88 ms |
4800 | 2.29 ms | 8.02 ms | 3.44 ms |
9600 | 1.15 ms | 4.01 ms | 1.72 ms |
19200 | 0.57 ms | 2.01 ms | 0.86 ms |
38400 | 0.29 ms | 1.00 ms | 0.43 ms |
57600 | 0.19 ms | 0.67 ms | 0.29 ms |
115200 | 0.10 ms | 0.33 ms | 0.14 ms |