Modbus RTU协议详解(大白话解释,不让你迷糊)

目录

一、名词解释(基础)

(一) 离散输入寄存器(Discrete Inputs Registers)

(二) 线圈寄存器(Coils Registers)

(三) 输入寄存器(Input Registers)

(四) 保持寄存器(Holding Registers)

(五) Master:

(六) Slave:

二、Modbus RTU 报文模型

1. 设备地址:

2. 功能码:

3. 数据格式:

4. CRC 校验位:

三、不同操作场景下的报文

(一) 主机 --> 从机(读请求)

1. 读离散输入寄存器

一、读 1 个离散输入量

二、读多个离散输入量

2. 读线圈

一、读 1 个线圈

二、读多个线圈

3. 读输入寄存器

一、读 1 个输入寄存器

二、读多个输入寄存器

4. 读保持寄存器

一、读 1 个保持寄存器

二、读多个保持寄存器

(二) 从机 --> 主机(读响应)

1. 读离散量输入反馈

一、读 1 个离散输入量反馈

二、读多个离散输入量反馈

补充知识点:Modbus 多线圈数据的存储规则

2. 读线圈反馈

一、读 1 个线圈反馈

二、读多个线圈反馈

3. 读输入寄存器反馈

一、读 1 个输入寄存器反馈

二、读多个输入寄存器反馈

4. 读保持寄存器反馈

一、读 1 个保持寄存器反馈

二、读多个保持寄存器反馈

(三) 主机 --> 从机(写请求)

1. 写单个线圈寄存器

2. 写单个保持寄存器

3. 写多个线圈寄存器

4. 写多个保持寄存器

(四) 从机 --> 主机(写响应)

1. 写单个线圈寄存器反馈

2. 写单个保持寄存器反馈

3. 写多个线圈寄存器反馈

4. 写多个保持寄存器反馈


一、名词解释(基础)

了解过 modbus 的应该都见过这些词“离散输入量、单个线圈、输入寄存器、多个寄存器...“,软件工程师第一次看这些指定一脸懵,这都是啥呀,和我的代码有毛关系...

其实这和 Modbus 协议的诞生相关,它最初用于可编程逻辑控制器(PLC)之间的通信,其与硬件关联较为密切。没怎么接触过硬件的同学上来看到这些词肯定一个头两个大。

软件工程师可以将这些词理解为代码中的一些数据类型,每种类型对应特定的功能码和访问方式。这些数据类型在物理设备上可能代表不同的硬件组件(如传感器、继电器等),也可能只是存储在控制器中的数据。

在 Modbus RTU 协议中,数据被分为四种基本类型

(一) 离散输入寄存器(Discrete Inputs Registers)

    • 功能码:02 (读取离散输入量)
    • 数据类型:布尔值(ON/OFF,1/0)
    • 访问权限:只读
    • 物理意义:通常代表开关量输入,如按钮、限位开关、接近传感器等
    • 代码表现:boolQBitArray

(二) 线圈寄存器(Coils Registers)

    • 功能码:01(读取线圈)、05(写单个线圈)、15(写多个线圈)
    • 数据类型:布尔值(ON/OFF,1/0)
    • 访问权限:读 / 写
    • 物理意义:通常代表开关量输入,如继电器、指示灯等
    • 代码表现:boolQBitArray

(三) 输入寄存器(Input Registers)

    • 功能码:04(读取输入寄存器)
    • 数据范围:2 个 byte(0~65535)
    • 访问权限:只读
    • 物理意义:通常代表模拟量输入,如温度传感器、压力传感器等
    • 代码表现:unsigned short 数组vector<unsigned shor>

(四) 保持寄存器(Holding Registers)

    • 功能码:03(读取保持寄存器)、06(写单个保持寄存器)、16(写多个保持寄存器)
    • 数据范围:2 个 byte(0~65535)
    • 访问权限:读 / 写
    • 物理意义:通常代表可配置的参数,如设定值、PID 参数等
    • 代码表现:unsigned short 数组vector<unsigned shor>

(五) Master:

主机,做纯软件的同学可以理解为 客户端

(六) Slave:

从机,做纯软件的同学可以理解为 服务端

二、Modbus RTU 报文模型

设备地址

功能码

数据格式

校验位

1 个字节

1 个字节

0~253 个字节(N*8bit)

2 个字节
CRC 低 | CRC 高

注:在报文模型这里,数据格式只是一个比较宽泛的概念,后边还会详细说(第三章节黄色单元格与之对应)

1. 设备地址:

设备地址就是从机地址。放在在信息帧的开头,占 1 个字节。0 是广播地址,1~247是从站设备地址,248~255保留不用。

主机把从机地址放入信息帧的设备地址区,并向从机寻址。从机响应时,把自己的地址放入响应信息帧的设备地址区,让主机识别已作出响应的从机地址。

这段用更直白的大白话说就是:

无论是主机发送请求报文时,还是从机给主机反馈的报文中,设备地址都是从机地址。作用:

    • 主机->从机:用于标识发送给哪个从机;
    • 从机->主机:告诉主机,这条报文是来自哪个从机。

问:为什么这么做?

答:之所以这么设计,是因为 Modbus RTU 是一个一主多从的工业协议。

2. 功能码:

占 1 个字节。由协议明确规定的。常用功能码如下:

功能(D/H)

名称

作用

01 / 0x01

读取线圈状态

取得一组逻辑线圈的当前状态(ON/OFF)

02 / 0x02

读离散输入寄存器

取得一组开关输入的当前状态(ON/OFF)

03 / 0x03

读取保持寄存器

在一个或多个保持寄存器中取得当前的二进制值

04 / 0x04

读取输入寄存器

在一个或多个输入寄存器中取得当前的二进制值

05 / 0x05

写单个线圈寄存器

强置一个逻辑线圈的通断状态

06 / 0x06

写单个保持寄存器

放置一个特定的二进制值到一个单寄存器中

07 / 0x07

读取异常状态

取得8个内部线圈的通断状态

15 / 0x0F

写多个线圈寄存器

强置一串连续逻辑线圈的通断

16 / 0x10

写多个保持寄存器

放置一系列特定的二进制值到一系列多寄存器中

17 / 0x11

报告从机标识

可使主机判断编址从机的类型及该从机运行指示灯的状态

3. 数据格式:

Modbus 报文模型中说的数据格式是一个相对宽泛的概念,在实际应用的不同操作(如主机读、写等)场景下,会有更具体的表现形式。

由于 Modbus信息帧所允许的最大长度为256个字节,减去设备地址、功能码和校验位,所以 N的范围是:N∈[0, 252]

4. CRC 校验位:

2 个字节。低位在前,高位在后。

注意这里有坑:Modbus 字节顺序通常是大端模式(即高位字节排放在内存的低地址端),当这种情况下,CRC 校验与之正好相反,Modbus 硬性规定了 CRC 校验位低位在前,高位在后。

三、不同操作场景下的报文

一般主机与从机之间一个完整的通信包括:

  • 主机给从机发送读请求; (Master -> Slave)
  • 从机给主机发送读响应; (Slave -> Master)
  • 主机给从机发送写请求; (Master -> Slave)
  • 从机给主机发送写响应。 (Slave -> Master)

了解完以上知识,直接举例。

(一) 主机 --> 从机(读请求)

1. 读离散输入寄存器

一、读 1 个离散输入量
  • 请求报文格式

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,要读取从机地址为 1 的设备上,起始地址为 0x0000 的 1 个离散输入量,请求报文如下:

01 02 00 00 00 01 CRC_L CRC_H

其中,01 是设备地址,02是功能码(从前文可知是读取离散输入寄存器),00 00是起始地址,00 01表示要读取的数量为 1 个,CRC_LCRC_H是计算得到的 CRC 校验位。

二、读多个离散输入量

假设要读取从机地址为 1 的设备上,起始地址为 0x000F 的 10 个离散输入量,请求报文如下:

01 02 00 0F 00 0A CRC_L CRC_H

这里00 0A表示要读取的离散寄存器数量为 10 个。

说明:看到表格黄底色的那四个字节了嘛(起始地址、读取数量),对应的就是前文报文模型表格中的”数据格式“。后边表格同理,数据格式相关字段,我也是用黄底色标注。

2. 读线圈

一、读 1 个线圈
  • 请求报文格式:

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

若要读取从机地址为 2 的设备上,起始地址为 0x0001 的 1 个线圈,请求报文如下:

02 01 00 01 00 01 CRC_L CRC_H

二、读多个线圈

若要读取从机地址为 2 的设备上,起始地址为 0x0001 的 5 个线圈,请求报文如下:

02 01 00 01 00 05 CRC_L CRC_H

3. 读输入寄存器

一、读 1 个输入寄存器
  • 请求报文格式:

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,读取从机地址为 3 的设备上,起始地址为 0x0002 的 1 个输入寄存器,请求报文如下:

03 04 00 02 00 01 CRC_L CRC_H

二、读多个输入寄存器

假设要读取从机地址为 3 的设备上,起始地址为 0x0002 的 3 个输入寄存器,请求报文如下:

03 04 00 02 00 03 CRC_L CRC_H

4. 读保持寄存器

一、读 1 个保持寄存器
  • 请求报文格式:

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,读取从机地址为 4 的设备上,起始地址为 0x0003 的 1 个保持寄存器,请求报文如下:

04 03 00 03 00 01 CRC_L CRC_H

二、读多个保持寄存器

若要读取从机地址为 4 的设备上,起始地址为 0x0003 的 4 个保持寄存器,请求报文如下:

04 03 00 03 00 04 CRC_L CRC_H

(二) 从机 --> 主机(读响应)

1. 读离散量输入反馈

一、读 1 个离散输入量反馈
  • 反馈报文格式

设备地址

功能码

字节数

数据

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

假设从机地址为 1 对上述读 1 个离散输入量请求进行反馈,反馈报文可能如下:

01 02 01 01 CRC_L CRC_H

其中,01 是设备地址,02 是功能码,01 表示字节数为 1,01 表示读取到的离散输入量值为 1(ON)。

二、读多个离散输入量反馈

若读取了 10 个离散输入量,反馈报文如下:

01 02 02 [data1] [data2] CRC_L CRC_H

这里 02 表示字节数为 2,因为 10 个布尔值需要 2 个字节来存储,[data1][data2] 是具体的数据字节。

补充知识点:Modbus 多线圈数据的存储规则

按 “线圈顺序” 依次填充字节的 “低位到高位”,不足 8 个的高位补 0。

这句话可能不怎么好理解,假设线圈顺序是从 Coil0~Coil9,则填充到字节位也是 bit0~bit9(bit8~bit9 是第二个字节 Byte2 的 bit0 和 bit1)

大白话就是:线圈 0 对应第一个字节的 bit0,线圈 7 对应第一个字节的 bit7,线圈 8 对应第二个字节的 bit0

举例 1:

十六进制 0xB203(0b1011 0010 0000 0003 与线圈状态的对应关系,见下表:

线圈:

Coil0

Coil1

Coil2

Coil3

Coil4

Coil5

Coil6

Coil7

Coil8

Coil9

线圈状态:

OFF

ON

OFF

OFF

ON

ON

OFF

ON

ON

ON

字节序:

Byte1.bit0

Byte1.bit1

Byte1.bit2

Byte1.bit3

Byte1.bit4

Byte1.bit5

Byte1.bit6

Byte1.bit7

Byte2.bit0

Byte2.bit1

二进制:

0

1

0

0

1

1

0

1

1

1

使用 Modbus 仿真工具,测试结果如下,符合上表计算:

举例 2:

十六进制0xCB02(0b1100 1011 0000 0010与线圈状态对应关系,见下表:

线圈:

Coil0

Coil1

Coil2

Coil3

Coil4

Coil5

Coil6

Coil7

Coil8

Coil9

线圈状态:

ON

ON

OFF

ON

OFF

OFF

ON

ON

OFF

ON

字节序:

Byte1.bit0

Byte1.bit1

Byte1.bit2

Byte1.bit3

Byte1.bit4

Byte1.bit5

Byte1.bit6

Byte1.bit7

Byte2.bit0

Byte2.bit1

二进制:

1

1

0

1

0

0

1

1

0

1

2. 读线圈反馈

一、读 1 个线圈反馈
  • 反馈报文格式

设备地址

功能码

字节数

数据

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

假设从机地址为 2 对读 1 个线圈请求进行反馈,反馈报文可能如下:

02 01 01 00 CRC_L CRC_H

表示读取到的线圈状态为 0(OFF)。

二、读多个线圈反馈

若读取了 5 个线圈,反馈报文如下:

02 01 01 [data] CRC_L CRC_H

这里 01 表示字节数为 1,[data] 存储了 5 个线圈的状态。

3. 读输入寄存器反馈

一、读 1 个输入寄存器反馈
  • 反馈报文格式

设备地址

功能码

字节数

数据(高字节)

数据(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

假设从机地址为 3 对读 1 个输入寄存器的请求进行反馈,反馈报文如下:

03 04 02 01 23 CRC_L CRC_H

02表示字节数为 2 个字节(一个输入寄存器 2 个字节);

0x0123表示读取到的输入寄存器值 。

二、读多个输入寄存器反馈

若读取了 3 个输入寄存器,反馈报文如下:

03 04 06 [data1_H][data1_L] [data2_H][data2_L] [data3_H][data3_L] CRC_L CRC_H

这里 06 表示字节数为 6,因为 3 个 2 字节的寄存器共 6 个字节。

4. 读保持寄存器反馈

一、读 1 个保持寄存器反馈
  • 反馈报文格式

设备地址

功能码

字节数

数据(高字节)

数据(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

假设从机地址为 4 对读 1 个保持寄存器请求进行反馈,反馈报文可能如下:

04 03 02 04 56 CRC_L CRC_H

02表示字节数为 2 个字节(一个保持寄存器 2 个字节);

0x0456表示读取到的保持寄存器值 。

二、读多个保持寄存器反馈

若读取了 4 个保持寄存器,反馈报文如下:

04 03 08 [data1_H][data1_L] [data2_H][data2_L] [data3_H][data3_L] [data4_H][data4_L] CRC_L CRC_H

这里 08 表示字节数为 8,因为 4 个保持寄存器共 8 个字节。

(三) 主机 --> 从机(写请求)

1. 写单个线圈寄存器

  • 请求报文格式

设备地址

功能码

寄存器地址(高字节)

寄存器地址(低字节)

值(高字节)

值(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,要将从机地址为 2 的设备上,地址为 0x0001 的线圈置为 ON(值为 0xFF00),请求报文如下:

02 05 00 01 FF 00 CRC_L CRC_H

若要置为 OFF(值为 0x0000),请求报文如下:

02 05 00 01 00 00 CRC_L CRC_H

补充知识点:

在 Modbus 协议中, ON 使用 0xFF00 表示 OFF 使用 0x0000 表示 ,这是协议的硬性规定的。就像 C++中ture通常是 1,false通常是 0 一样。

2. 写单个保持寄存器

  • 请求报文格式

设备地址

功能码

寄存器地址(高字节)

寄存器地址(低字节)

值(高字节)

值(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,要将从机地址为 3 的设备上,地址为 0x0002 的保持寄存器的值设为 0x1234,请求报文如下:

03 06 00 02 12 34 CRC_L CRC_H

3. 写多个线圈寄存器

  • 请求报文格式

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

bit 数量(高字节)

bit 数量(低字节)

字节数

数据

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

N 字节

1 字节

1 字节

例如,要将从机地址为 2 的设备上,起始地址为 0x0001 的 5 个线圈置为 11101 (bit4~bit0),请求报文如下:

02 0F 00 01 00 05 01 1D CRC_L CRC_H

其中,01 是字节数,1D 转换为二进制是 0001 0111,表示 5 个线圈的状态。

4. 写多个保持寄存器

  • 请求报文格式

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

字节数

数据

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

2*N 字节

1 字节

1 字节

例如,要将从机地址为 3 的设备上,起始地址为 0x0002 的 3 个保持寄存器的值分别设为 0x1234、0x5678、0x9ABC,请求报文如下:

03 10 00 02 00 03 06 12 34 56 78 9A BC CRC_L CRC_H

其中,06 是字节数,后面跟着 3 个 2 字节的数据。

(四) 从机 --> 主机(写响应)

1. 写单个线圈寄存器反馈

  • 反馈报文格式

设备地址

功能码

寄存器地址(高字节)

寄存器地址(低字节)

值(高字节)

值(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

反馈报文与主机的请求报文相同,用于确认写操作成功。例如,对上述写单个线圈的请求反馈如下:

02 05 00 01 FF 00 CRC_L CRC_H

2. 写单个保持寄存器反馈

  • 反馈报文格式

设备地址

功能码

寄存器地址(高字节)

寄存器地址(低字节)

值(高字节)

值(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

反馈报文与主机的请求报文相同,用于确认写操作成功。例如,对上述写单个保持寄存器的请求反馈如下:

03 06 00 02 12 34 CRC_L CRC_H

3. 写多个线圈寄存器反馈

  • 反馈报文格式

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,对上述写多个线圈的请求反馈如下:

02 0F 00 01 00 05 CRC_L CRC_H

起始地址:00 01

线圈数量:00 05

4. 写多个保持寄存器反馈

  • 反馈报文格式

设备地址

功能码

起始地址(高字节)

起始地址(低字节)

数量(高字节)

数量(低字节)

CRC 低

CRC 高

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

1 字节

例如,对上述写多个保持寄存器的请求反馈如下:

03 10 00 02 00 03 CRC_L CRC_H

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

油炸自行车

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值