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 = 相对地址”
比如:000010000109

🔹 第四部分:线圈数量(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个线圈的状态。

🧮 第一步:将十六进制转换成二进制

  • 5501010101
  • 0100000001

🧮 第二步:按位解析

字节 二进制 对应线圈编号 状态
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个字节 5501010101 → 从右到左:

    • 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个字节 0100000001 → 从右到左:

    • 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:为什么我发送的报文没有响应?

  • 可能原因
    1. 从站地址错误 → 检查设备地址是否正确。
    2. 功能码错误 → 检查功能码是否为 01
    3. 起始地址错误 → 检查地址是否从 0 开始。
    4. 线圈数量错误 → 检查数量是否在 1 ~ 2000 之间。
    5. 校验码错误 → 使用正确的CRC算法计算校验码。
    6. 通信参数错误 → 检查波特率、数据位、停止位、校验位是否一致。

❓ 问题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:构造发送报文

  1. 从站地址 = 02
  2. 功能码 = 01
  3. 起始地址 = 00 00(相对地址0)
  4. 线圈数量 = 00 08(读取8个线圈)
  5. CRC校验 = B2 0D(由软件自动计算)

发送报文 = 02 01 00 00 00 08 B2 0D

📌 步骤2:假设接收报文

假设设备回复:

接收报文 = 02 01 01 FF 8C 4B

📌 步骤3:解析接收报文

  1. 从站地址 = 02 → 设备2回复的
  2. 功能码 = 01 → 回复的是输出线圈状态
  3. 字节计数 = 01 → 数据部分有1个字节
  4. 具体数据 = FF → 二进制 11111111
  5. CRC校验 = 8C 4B → 校验通过

📌 步骤4:解析具体数据 FF

  • FF11111111 → 从右到左:
    • 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个线圈全部为“开”状态!


🎯 下一步学习建议

  1. 动手实践:使用Modbus调试软件(如Modbus Poll, QModBus),输入不同的地址、线圈数量,观察返回结果。
  2. 学习其他功能码:功能码02(读取输入线圈)、03(读取输出寄存器)、04(读取输入寄存器)等,它们的结构类似,只是操作对象不同。
  3. 阅读设备手册:任何Modbus设备都会在手册中列出支持的功能码、地址范围和通信参数,一定要学会看手册!
  4. 学习CRC校验:虽然不需要手动计算,但了解其原理有助于深入理解协议。
posted @ 2025-11-04 11:25  恨水长秋  阅读(107)  评论(0)    收藏  举报