ModbusRTU通信报文分析—功能码03读取保持型寄存器学习笔记
写给初学者的话:
你是不是在调试Modbus设备时,看到一串十六进制数据(比如03 03 00 05 00 02 D5 E8)就一头雾水?是不是搞不清“功能码03”、“起始地址”、“字节计数”这些词是什么意思?别担心!这篇笔记会从最基础的概念讲起,用生活化的比喻、清晰的表格和详细的步骤,带你彻底搞懂Modbus RTU协议中功能码03——读取保持型寄存器的完整过程。记住,功能码03就像一个“数字值查询器”,专门用来查看设备里存储的温度、压力、速度等具体数值。这篇笔记内容超详细,字数很多,但每一句都是为你量身打造,慢慢看,一定能学会!
1️⃣ 核心思想:功能码03 = 查询数字值
在前面的笔记中,我们已经学习了:
- 功能码01:读取输出线圈 → 查询开关状态(开/关)
- 功能码02:读取输入线圈 → 查询传感器状态(触发/未触发)
现在,我们进入更高级的数据类型——寄存器 (Register)。
🔹 什么是“保持型寄存器”?
- 保持型寄存器 (Holding Register):是设备内部用于存储“数字值”的内存区域。
- 它可以存储16位整数(范围:0 ~ 65535),也可以存储有符号整数(范围:-32768 ~ 32767),甚至可以组合成32位浮点数。
- 在Modbus协议中,它对应的是 4区(输出寄存器区),但功能码03是用来“读取”它的,所以也叫“保持型寄存器”。
✅ 一句话总结:
功能码03 = 我要查一个或多个数字值,比如温度、压力、速度、计数值等。
2️⃣ 功能码03的发送报文结构
图中给出了发送报文的结构:
✅ 发送报文 = 从站地址 + 03 + 起始寄存器地址 + 寄存器数量 + CRC16校验
我们来逐一分解每一个部分。
🔹 第一部分:从站地址(Slave Address)
- 这是报文的第一字节,用来指定这封“电报”是发给哪个设备的。
- 取值范围:1 ~ 255(十进制)
- 实际应用中,常用的地址是
1 ~ 247。
✅ 一句话总结:
从站地址 = 收件人地址,告诉设备“这封信是给你的”。
🔹 第二部分:功能码 03
- 这是报文的第二字节,固定为
03。 - 它告诉从站:“我要读取保持型寄存器的值”。
✅ 一句话总结:
功能码03 = 我要查数字值。
🔹 第三部分:起始寄存器地址(Starting Register Address)
- 这是报文的第三和第四字节,用来指定从哪个地址开始读取。
- 它是一个16位无符号整数,由高字节+低字节组成。
- 地址是从
0开始计数的(相对地址),而不是从1开始。
🧩 举例说明
你想从地址 40006 开始读取(也就是第6个保持型寄存器):
- 相对地址 =
5(因为地址从1开始,偏移量从0开始) - 所以,起始地址 =
00 05(高字节=00,低字节=05)
💡 记忆口诀:
“绝对地址 - 1 = 相对地址”
比如:40001→0;40005→4;40006→5
🔹 第四部分:寄存器数量(Register Quantity)
- 这是报文的第五和第六字节,用来指定你要读取多少个寄存器。
- 它也是一个16位无符号整数,由高字节+低字节组成。
- 最大可以读取
125个寄存器(协议限制)。
🧩 举例说明
你想读取2个寄存器:
- 寄存器数量 =
2(十进制) - 十六进制 =
00 02(高字节=00,低字节=02)
✅ 一句话总结:
寄存器数量 = 我要查几个数字值。
🔹 第五部分:CRC16校验(Cyclic Redundancy Check)
- 这是报文的最后两个字节,用来保证报文的准确性和完整性。
- 如果在传输过程中数据被干扰或篡改,校验码就会不匹配,接收方会丢弃这个报文。
- CRC16校验码是根据前面所有字节(从站地址到寄存器数量)计算出来的。
✅ 一句话总结:
CRC16校验 = 数据的“验证码”,确保报文在传输过程中没有出错。
3️⃣ 功能码03的接收报文结构
图中也给出了接收报文的结构:
✅ 接收报文 = 从站地址 + 03 + 字节计数 + 具体数据 + CRC16校验
我们来逐一分解每一个部分。
🔹 第一部分:从站地址(Slave Address)
- 这是报文的第一字节,与发送报文中的从站地址相同。
- 它告诉主站:“这是谁回复的”。
✅ 一句话总结:
从站地址 = 回复人地址,告诉主站“这是我回复的”。
🔹 第二部分:功能码 03
- 这是报文的第二字节,固定为
03。 - 它告诉主站:“我正在回复你关于保持型寄存器的查询”。
✅ 一句话总结:
功能码03 = 我在回复数字值。
🔹 第三部分:字节计数(Byte Count)
- 这是报文的第三字节,用来指定后面“具体数据”部分有多少个字节。
- 它是一个8位无符号整数,最大值是
255。 - 因为每个寄存器占2个字节(16位),所以字节计数 =
寄存器数量 * 2。
🧩 举例说明
你请求读取2个寄存器:
- 2个寄存器需要多少字节?
- 每个寄存器占2个字节 → 2 * 2 = 4字节
- 所以,字节计数 =
04
✅ 一句话总结:
字节计数 = 寄存器数量 * 2
🔹 第四部分:具体数据(Data)
- 这是报文的第四字节及以后,用来存放实际的寄存器值。
- 每个寄存器占2个字节(16位),由高字节+低字节组成。
- 数据的顺序是从左到右,第一个寄存器在前,第二个寄存器在后。
🧩 举例说明
假设你读取了2个寄存器,它们的值分别是:
| 寄存器编号 | 1 | 2 |
|---|---|---|
| 值 | 25000 | 100 |
那么,具体数据应该如何排列?
- 第1个寄存器:
25000→ 十六进制61 A8(高字节=61,低字节=A8) - 第2个寄存器:
100→ 十六进制00 64(高字节=00,低字节=64)
因此,具体数据 = 61 A8 00 64
💡 重要提醒:
在Modbus协议中,数据的字节序是 大端序 (Big-Endian)!也就是说,高字节在前,低字节在后。
所以,正确的排列应该是:
- 寄存器1 → 高字节
61+ 低字节A8 - 寄存器2 → 高字节
00+ 低字节64
🔹 第五部分:CRC16校验
- 这是报文的最后两个字节,用来保证报文的准确性和完整性。
- CRC16校验码是根据前面所有字节(从站地址到具体数据)计算出来的。
✅ 一句话总结:
CRC16校验 = 数据的“验证码”,确保报文在传输过程中没有出错。
4️⃣ 完整案例解析:读取3号从站从5开始的2个寄存器
图中给出了一个完整的案例:
发送报文:
03 03 00 05 00 02 D5 E8
接收报文:03 03 04 00 64 00 C8 99 BA
下面我们来一步一步解析这个案例。
📌 步骤1:发送报文解析
发送报文 = 03 03 00 05 00 02 D5 E8
| 部分 | 字节位置 | 内容 | 说明 |
|---|---|---|---|
| 从站地址 | 第1字节 | 03 |
发送给设备3 |
| 功能码 | 第2字节 | 03 |
读取保持型寄存器 |
| 起始地址 | 第3-4字节 | 00 05 |
从地址5开始(即40006) |
| 寄存器数量 | 第5-6字节 | 00 02 |
读取2个寄存器 |
| CRC16校验 | 第7-8字节 | D5 E8 |
CRC16校验码 |
✅ 发送报文含义:
“设备3,请告诉我从地址5开始的2个保持型寄存器的值。”
📌 步骤2:接收报文解析
接收报文 = 03 03 04 00 64 00 C8 99 BA
| 部分 | 字节位置 | 内容 | 说明 |
|---|---|---|---|
| 从站地址 | 第1字节 | 03 |
设备3回复的 |
| 功能码 | 第2字节 | 03 |
回复的是保持型寄存器值 |
| 字节计数 | 第3字节 | 04 |
数据部分有4个字节 |
| 具体数据 | 第4-7字节 | 00 64 00 C8 |
寄存器值数据 |
| CRC16校验 | 第8-9字节 | 99 BA |
CRC16校验码 |
✅ 接收报文含义:
“设备3回复:从地址5开始的2个保持型寄存器的值是00 64 00 C8。”
📌 步骤3:解析具体数据 00 64 00 C8
现在,我们要把 00 64 00 C8 转换成2个寄存器的值。
🧮 第一步:按寄存器拆分数据
- 第1个寄存器:
00 64→ 高字节=00,低字节=64 - 第2个寄存器:
00 C8→ 高字节=00,低字节=C8
🧮 第二步:将十六进制转换成十进制
- 第1个寄存器:
00 64→ 十进制 =0 * 256 + 100=100 - 第2个寄存器:
00 C8→ 十进制 =0 * 256 + 200=200
✅ 最终结果:
| 寄存器编号 | 1 | 2 |
|---|---|---|
| 值 | 100 | 200 |
🎉 恭喜你!你已经成功解析了功能码03的接收报文!
5️⃣ 常见问题与避坑指南
❓ 问题1:为什么我发送的报文没有响应?
- 可能原因:
- 从站地址错误 → 检查设备地址是否正确。
- 功能码错误 → 检查功能码是否为
03。 - 起始地址错误 → 检查地址是否从
0开始。 - 寄存器数量错误 → 检查数量是否在
1 ~ 125之间。 - 校验码错误 → 使用正确的CRC16算法计算校验码。
- 通信参数错误 → 检查波特率、数据位、停止位、校验位是否一致。
❓ 问题2:为什么接收报文中的字节计数是4,而我只读了2个寄存器?
- 原因:因为每个寄存器占2个字节,2个寄存器就是4个字节。
- 字节计数 =
寄存器数量 * 2=2 * 2=4
✅ 一句话总结:
字节计数 = 寄存器数量 * 2
❓ 问题3:如何手动计算CRC16校验码?
- 不要手动计算! 使用Modbus调试软件或编程库(如Python的
pymodbus)自动生成。 - 如果你一定要手动计算,可以使用在线CRC计算器,选择“Modbus RTU CRC16”。
❓ 问题4:寄存器值是负数怎么办?
- Modbus寄存器默认是无符号整数(0 ~ 65535)。
- 如果你需要表示负数,通常使用有符号整数(补码表示)。
- 例如:
65535(无符号) =-1(有符号) - 你需要在软件中设置“数据类型”为“有符号16位整数”。
✅ 一句话总结:
寄存器值可以是有符号或无符号,取决于你的应用需求。
6️⃣ 实战模拟:手把手教你构造和解析报文
假设你现在有一个任务:
“我要读取设备
01的保持型寄存器,从地址40001开始,读取1个寄存器。”
📌 步骤1:构造发送报文
- 从站地址 =
01 - 功能码 =
03 - 起始地址 =
00 00(相对地址0) - 寄存器数量 =
00 01(读取1个寄存器) - CRC16校验 =
84 0A(由软件自动计算)
发送报文 = 01 03 00 00 00 01 84 0A
📌 步骤2:假设接收报文
假设设备回复:
接收报文 = 01 03 02 00 64 8C 4B
📌 步骤3:解析接收报文
- 从站地址 =
01→ 设备1回复的 - 功能码 =
03→ 回复的是保持型寄存器值 - 字节计数 =
02→ 数据部分有2个字节 - 具体数据 =
00 64→ 第1个寄存器值 - CRC16校验 =
8C 4B→ 校验通过
📌 步骤4:解析具体数据 00 64
00 64→ 高字节=00,低字节=64- 十进制 =
0 * 256 + 100=100
✅ 最终结果:第1个寄存器的值是
100!
🎯 下一步学习建议
- 动手实践:使用Modbus调试软件(如Modbus Poll, QModBus),输入不同的地址、寄存器数量,观察返回结果。
- 学习其他功能码:功能码04(读取输入寄存器)、06(写入单个寄存器)、10(写入多个寄存器)等,它们的结构类似,只是操作对象不同。
- 阅读设备手册:任何Modbus设备都会在手册中列出支持的功能码、地址范围和通信参数,一定要学会看手册!
- 学习数据类型转换:了解如何将16位寄存器值转换成32位浮点数、有符号整数等。

我越过四十万英里 越过昼夜与星辰 越过硝烟与战争 你在哪里 我去见你 ——《致岁月迢迢》
浙公网安备 33010602011771号