ModbusRTU通信报文分析—功能码02读取输入线圈笔记
写给初学者的话:
你是不是在调试Modbus设备时,看到一串十六进制数据(比如02 02 00 0A 00 10 59 F7)就一头雾水?是不是搞不清“功能码02”、“起始地址”、“字节计数”这些词是什么意思?别担心!这篇笔记会从最基础的概念讲起,用生活化的比喻、清晰的表格和详细的步骤,带你彻底搞懂Modbus RTU协议中功能码02——读取输入线圈的完整过程。记住,功能码02就像一个“传感器状态查询器”,专门用来查看哪些传感器是触发的、哪些是没有触发的。这篇笔记内容超详细,字数很多,但每一句都是为你量身打造,慢慢看,一定能学会!
1️⃣ 核心思想:功能码02 = 查询传感器状态
在上一篇笔记中,我们已经知道:
✅ 功能码02 是用来 “读取输入线圈” 的。
那什么是“输入线圈”呢?
- 输入线圈 (Input Coil):其实就是“传感器”或“按钮”的状态。
- 它只有两种状态:触发 (ON) = 1 或 未触发 (OFF) = 0。
- 在Modbus协议中,它对应的是 1区(输入线圈区)。
所以,功能码02的作用就是:
“请问从站,从某个地址开始,连续N个传感器的状态分别是触发还是未触发?”
2️⃣ 功能码02的发送报文结构
图中给出了发送报文的结构:
✅ 发送报文 = 从站地址 + 02 + 起始线圈地址 + 线圈数量 + CRC16校验
我们来逐一分解每一个部分。
🔹 第一部分:从站地址(Slave Address)
- 这是报文的第一字节,用来指定这封“电报”是发给哪个设备的。
- 取值范围:1 ~ 255(十进制)
- 实际应用中,常用的地址是
1 ~ 247。
✅ 一句话总结:
从站地址 = 收件人地址,告诉设备“这封信是给你的”。
🔹 第二部分:功能码 02
- 这是报文的第二字节,固定为
02。 - 它告诉从站:“我要读取输入线圈的状态”。
✅ 一句话总结:
功能码02 = 我要查传感器状态。
🔹 第三部分:起始线圈地址(Starting Coil Address)
- 这是报文的第三和第四字节,用来指定从哪个地址开始读取。
- 它是一个16位无符号整数,由高字节+低字节组成。
- 地址是从
0开始计数的(相对地址),而不是从1开始。
🧩 举例说明
你想从地址 00011 开始读取(也就是第11个输入线圈):
- 相对地址 =
10(因为地址从1开始,偏移量从0开始) - 所以,起始地址 =
00 0A(高字节=00,低字节=0A)
💡 记忆口诀:
“绝对地址 - 1 = 相对地址”
比如:00001→0;00010→9;00011→10
🔹 第四部分:线圈数量(Coil Quantity)
- 这是报文的第五和第六字节,用来指定你要读取多少个线圈。
- 它也是一个16位无符号整数,由高字节+低字节组成。
- 最大可以读取
2000个线圈(协议限制)。
🧩 举例说明
你想读取16个线圈:
- 线圈数量 =
16(十进制) - 十六进制 =
00 10(高字节=00,低字节=10)
✅ 一句话总结:
线圈数量 = 我要查几个传感器。
🔹 第五部分:CRC16校验(Cyclic Redundancy Check)
- 这是报文的最后两个字节,用来保证报文的准确性和完整性。
- 如果在传输过程中数据被干扰或篡改,校验码就会不匹配,接收方会丢弃这个报文。
- CRC16校验码是根据前面所有字节(从站地址到线圈数量)计算出来的。
✅ 一句话总结:
CRC16校验 = 数据的“验证码”,确保报文在传输过程中没有出错。
3️⃣ 功能码02的接收报文结构
图中也给出了接收报文的结构:
✅ 接收报文 = 从站地址 + 02 + 字节计数 + 具体数据 + CRC16校验
我们来逐一分解每一个部分。
🔹 第一部分:从站地址(Slave Address)
- 这是报文的第一字节,与发送报文中的从站地址相同。
- 它告诉主站:“这是谁回复的”。
✅ 一句话总结:
从站地址 = 回复人地址,告诉主站“这是我回复的”。
🔹 第二部分:功能码 02
- 这是报文的第二字节,固定为
02。 - 它告诉主站:“我正在回复你关于输入线圈的查询”。
✅ 一句话总结:
功能码02 = 我在回复传感器状态。
🔹 第三部分:字节计数(Byte Count)
- 这是报文的第三字节,用来指定后面“具体数据”部分有多少个字节。
- 它是一个8位无符号整数,最大值是
255。 - 因为每个字节可以存储8个线圈的状态(1个bit代表1个线圈),所以最多可以返回
255 * 8 = 2040个线圈的状态。
🧩 举例说明
你请求读取16个线圈:
- 16个线圈需要多少字节?
- 每个字节存8个线圈 → 16 / 8 = 2 → 正好2字节
- 所以,字节计数 =
02
✅ 一句话总结:
字节计数 = 数据部分有多少个字节。
🔹 第四部分:具体数据(Data)
- 这是报文的第四字节及以后,用来存放实际的线圈状态。
- 每个字节的每一位(bit)代表一个线圈的状态:
1= 触发(ON)0= 未触发(OFF)
- 数据的顺序是从低位到高位,再从左到右。
🧩 举例说明
假设你读取了16个线圈,它们的状态是:
| 线圈编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 状态 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
那么,具体数据应该如何排列?
- 第1个字节:存储线圈1~8的状态 →
01001101(二进制)→4D(十六进制) - 第2个字节:存储线圈9~16的状态 →
10101010(二进制)→AA(十六进制)
💡 重要提醒:
在Modbus协议中,数据的位序是从低位到高位排列的!也就是说,第0位是最低位,第7位是最高位。
所以,正确的排列应该是:
- 线圈1 → bit0
- 线圈2 → bit1
- ...
- 线圈8 → bit7
- 线圈9 → bit0(下一个字节)
- 线圈10 → bit1(下一个字节)
- ...
- 线圈16 → bit7(下一个字节)
因此,具体数据 = 4D AA
🔹 第五部分:CRC16校验
- 这是报文的最后两个字节,用来保证报文的准确性和完整性。
- CRC16校验码是根据前面所有字节(从站地址到具体数据)计算出来的。
✅ 一句话总结:
CRC16校验 = 数据的“验证码”,确保报文在传输过程中没有出错。
4️⃣ 完整案例解析:读取2号从站从10开始的16个输入线圈
图中给出了一个完整的案例:
发送报文:
02 02 00 0A 00 10 59 F7
接收报文:02 02 02 15 00 F3 28
下面我们来一步一步解析这个案例。
📌 步骤1:发送报文解析
发送报文 = 02 02 00 0A 00 10 59 F7
| 部分 | 字节位置 | 内容 | 说明 |
|---|---|---|---|
| 从站地址 | 第1字节 | 02 |
发送给设备2 |
| 功能码 | 第2字节 | 02 |
读取输入线圈 |
| 起始地址 | 第3-4字节 | 00 0A |
从地址10开始(即00011) |
| 线圈数量 | 第5-6字节 | 00 10 |
读取16个线圈 |
| CRC16校验 | 第7-8字节 | 59 F7 |
CRC16校验码 |
✅ 发送报文含义:
“设备2,请告诉我从地址10开始的16个输入线圈的状态。”
📌 步骤2:接收报文解析
接收报文 = 02 02 02 15 00 F3 28
| 部分 | 字节位置 | 内容 | 说明 |
|---|---|---|---|
| 从站地址 | 第1字节 | 02 |
设备2回复的 |
| 功能码 | 第2字节 | 02 |
回复的是输入线圈状态 |
| 字节计数 | 第3字节 | 02 |
数据部分有2个字节 |
| 具体数据 | 第4-5字节 | 15 00 |
线圈状态数据 |
| CRC16校验 | 第6-7字节 | F3 28 |
CRC16校验码 |
✅ 接收报文含义:
“设备2回复:从地址10开始的16个输入线圈的状态是15 00。”
📌 步骤3:解析具体数据 15 00
现在,我们要把 15 00 转换成16个线圈的状态。
🧮 第一步:将十六进制转换成二进制
15→0001010100→00000000
🧮 第二步:按位解析
| 字节 | 二进制 | 对应线圈编号 | 状态 |
|---|---|---|---|
| 15 | 0 0 0 1 0 1 0 1 | 1 2 3 4 5 6 7 8 | 0 0 0 1 0 1 0 1 |
| 00 | 0 0 0 0 0 0 0 0 | 9 10 ... | 0 0 0 0 0 0 0 0 |
💡 注意:
在Modbus协议中,位序是从低位到高位排列的!也就是说,第0位是最低位,第7位是最高位。
所以,正确的解析应该是:
-
第1个字节
15→00010101→ 从右到左:- bit0 = 1 → 线圈1 = 触发
- bit1 = 0 → 线圈2 = 未触发
- bit2 = 1 → 线圈3 = 触发
- bit3 = 0 → 线圈4 = 未触发
- bit4 = 1 → 线圈5 = 触发
- bit5 = 0 → 线圈6 = 未触发
- bit6 = 0 → 线圈7 = 未触发
- bit7 = 0 → 线圈8 = 未触发
-
第2个字节
00→00000000→ 从右到左:- bit0 = 0 → 线圈9 = 未触发
- bit1 = 0 → 线圈10 = 未触发
- bit2 = 0 → 线圈11 = 未触发
- bit3 = 0 → 线圈12 = 未触发
- bit4 = 0 → 线圈13 = 未触发
- bit5 = 0 → 线圈14 = 未触发
- bit6 = 0 → 线圈15 = 未触发
- bit7 = 0 → 线圈16 = 未触发
✅ 最终结果:
| 线圈编号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 状态 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
🎉 恭喜你!你已经成功解析了功能码02的接收报文!
5️⃣ 常见问题与避坑指南
❓ 问题1:为什么我发送的报文没有响应?
- 可能原因:
- 从站地址错误 → 检查设备地址是否正确。
- 功能码错误 → 检查功能码是否为
02。 - 起始地址错误 → 检查地址是否从
0开始。 - 线圈数量错误 → 检查数量是否在
1 ~ 2000之间。 - 校验码错误 → 使用正确的CRC16算法计算校验码。
- 通信参数错误 → 检查波特率、数据位、停止位、校验位是否一致。
❓ 问题2:为什么接收报文中的字节计数是2,而我只读了16个线圈?
- 原因:因为16个线圈需要2个字节来存储(每个字节存8个线圈)。
- 16 / 8 = 2 → 正好2字节。
✅ 一句话总结:
字节计数 = ceil(线圈数量 / 8)
❓ 问题3:如何手动计算CRC16校验码?
- 不要手动计算! 使用Modbus调试软件或编程库(如Python的
pymodbus)自动生成。 - 如果你一定要手动计算,可以使用在线CRC计算器,选择“Modbus RTU CRC16”。
6️⃣ 实战模拟:手把手教你构造和解析报文
假设你现在有一个任务:
“我要读取设备
01的输入线圈,从地址00001开始,读取8个线圈。”
📌 步骤1:构造发送报文
- 从站地址 =
01 - 功能码 =
02 - 起始地址 =
00 00(相对地址0) - 线圈数量 =
00 08(读取8个线圈) - CRC16校验 =
B2 0D(由软件自动计算)
发送报文 = 01 02 00 00 00 08 B2 0D
📌 步骤2:假设接收报文
假设设备回复:
接收报文 = 01 02 01 FF 8C 4B
📌 步骤3:解析接收报文
- 从站地址 =
01→ 设备1回复的 - 功能码 =
02→ 回复的是输入线圈状态 - 字节计数 =
01→ 数据部分有1个字节 - 具体数据 =
FF→ 二进制11111111 - CRC16校验 =
8C 4B→ 校验通过
📌 步骤4:解析具体数据 FF
FF→11111111→ 从右到左:- bit0 = 1 → 线圈1 = 触发
- bit1 = 1 → 线圈2 = 触发
- bit2 = 1 → 线圈3 = 触发
- bit3 = 1 → 线圈4 = 触发
- bit4 = 1 → 线圈5 = 触发
- bit5 = 1 → 线圈6 = 触发
- bit6 = 1 → 线圈7 = 触发
- bit7 = 1 → 线圈8 = 触发
✅ 最终结果:8个线圈全部为“触发”状态!
🎯 下一步学习建议
- 动手实践:使用Modbus调试软件(如Modbus Poll, QModBus),输入不同的地址、线圈数量,观察返回结果。
- 学习其他功能码:功能码01(读取输出线圈)、03(读取输出寄存器)、04(读取输入寄存器)等,它们的结构类似,只是操作对象不同。
- 阅读设备手册:任何Modbus设备都会在手册中列出支持的功能码、地址范围和通信参数,一定要学会看手册!
- 学习CRC16校验:虽然不需要手动计算,但了解其原理有助于深入理解协议。

我心里有一簇迎着烈日而生的花,比一切美酒都要芬芳,滚烫的馨香淹没过稻草人的胸膛,草扎的精神,从此万寿无疆。——《默读》
浙公网安备 33010602011771号