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

有时候人只需要一只温暖的手的触摸,就像是拥有了整个世界。我一直在等,等到绝望。
——《龙族》
浙公网安备 33010602011771号