Modbus协议简介

Modbus 协议简介

  • Modbus就是一个软件层的总线通信协议。他不依赖于硬件总线,Modbus协议支持多种电气接口,包括RS232、RS-422、RS485、TCP/IP等。
  • Modbus的传输模式也分为多个版本,常见的有Modbus ASCII,Modbus RTU,Modbus TCP等。
  • Modbus协议使用串口传输时可以选择RTU或ASCII模式,Modbus RTU是一种紧凑的,十六进制表示数据的方式,Modbus ASCII是一种采用Ascii码表示数据,并且每个8Bit 字节都作为两个ASCII字符发送的表示方式。RTU格式后续的命令/数据带有循环冗余校验的校验和,而ASCII格式采用纵向冗余校验的校验和。
  • Modbus协议使用以太网传输时可以选择TCP模式,这种模式不使用校验。
  • Modbus是一主多从的通信协议,一个请求/应答协议,Modbus通信中只有一个设备可以发送请求。其他从设备接收主机发送的数据来进行响应。也就是说,主机在同一时间内只能向一个从机发送请求,不能Modbus同步进行通信,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。

Modbus 协议描述

modbus协议一帧完整的数据表示应用数据单元(ADU),内部包含一个与基础通讯层无关的简单协议数据单元(PDU)。

上图表示ADU和PDU的关系,以Modbus RTU协议为例,一个完整的应用数据单元(ADU)如下所示

<-------   ADU   -------->
01  05  00  00  FF  00  DD  FA
    <----  PDU   ---->

帧结构 = 设备地址(1字节) + 功能码(1字节) + 数据(N字节) + 校验(2字节)。

01: 表示设备地址
05: 功能码,表示写单个线圈
00 00 FF 00: 表示请求数据,00 00表示线圈地址,FF 00表示数值
DD FF: 表示CRC校验

这一帧数据表示向01设备的线圈1置一。PDU包括功能码和数据。下面介绍Modbus协议,只描述协议数据单元(PDU)。
一次完全的数据操作如下图所示,主机(客户机)发送数据请求,从机(服务器)执行操作并返回响应数据,如果出错则返回异常码,主机通过接收响应数据判读操作是否正常。


以上面05 00 00 FF 00的请求为例,如果正常响应则返回05 00 00 FF 00,如果非法数据异常则返回85 03。这只是协议数据单元(PDU)。Modbus RTU的一帧应用数据单元(ADU)则如下。

请求数据:01 05 00 00 FF 00 8C 3A
正常响应:01 05 00 00 FF 00 8C 3A
异常响应:01 85 03 02 91

正常响应:响应功能码 = 请求功能码
异常响应:响应功能码 = 异常功能码 = 请求功能码 + 0x80
异常码是一个单字节值,用于指示错误的类型。Modbus协议定义的几个常用异常码:

异常码名称描述
0x01非法功能请求的功能码不支持
0x02非法数据地址请求的数据地址错误
0x03非法数据值请求的数据值或操作无法执行
0x04服务器故障服务器设备故障
0x05应答已接收到请求并正在处理
0x06设备繁忙设备当前正忙无法执行请求的操作

下面为Modbus服务器端事务处理状态图,Modbus一直等待接收数据,接收到数据先判断功能码,异常则返回01,正常继续判断数据地址,异常则返回02,正常则判断数值,异常则返回02,正常则执行操作,异常则返回04,05或06,正常则返回响应继续等待下次数据。

Modbus 数据类型

Modbus协议规定了4个存储区,0x,1x,3x,4x分别表示四种地址类型

地址类型地址范围数据类似读写功能码
线圈(Coils)0x0000~0xFFFF位,1 bit读写布尔量01H,05H, 0FH
离散输入(Discrete Inputs)1x0000~1xFFFF位,1 bit只读布尔量02H
输入寄存器(Input Registers)3x0000~3xFFFF字,16 bit只读布尔量04H
保持寄存器(Holding Registers)4x0000~4xFFFF字,16 bit读写布尔量03H,06H, 10H

0x代表线圈(DO)类地址,1x代表触点(DI)类地址、 3x代表输入寄存器(AI)类地址、4x代表输出寄存器(AO)类地址。
通过Modbus不同的功能码可以访问不同的地址类型数据。

Modbus 功能码

Modbus 协议规定了多种功能码,不同的功能码可以实现不同的操作,对不同的数据类型实现读写。常用的功能码如下表所示。

功能码功能说明
01读线圈状态(Read Coils)
02读离散输入状态(Read Discrete Inputs )
03读保持寄存器(Read Holding Registers )
04读输入寄存器(Read Input Registers)
05写单线圈(Write Single Coil)
06写单寄存器(Write Single Register)
0F写多个线圈(Write Multiple Coils)
10写多个寄存器(Write Multiple Holding Registers)

0x01 读线圈状态

该功能码读取设备中1~2000个连续线圈的状态,请求指定起始地址,即指定第一个线圈地址,以及读取线圈的数量。
线圈的寻址从零开始。因此,编号为 1-16 的线圈的寻址为 0-15。

  • 请求
功能码1字节0x01
起始地址2字节0x0000 ~ 0xFFFF
线圈数量2字节1 ~ 2000(0x7D0)
  • 响应

响应消息中的线圈按数据字段每位一个线圈的方式打包,状态指示为1=ON和0=OFF。
第一个数据字节的 LSB 包含查询中寻址的输出。其他线圈紧随此字节的高位端,并在后续字节中从低位到高位排列。
如果输出数量不是 8 的乘积,则最终数据字节的剩余位将用零填充(朝向字节的高位端)。字节数字段指定使用的字节数量。

功能码1字节0x01
字节数1字节N*
线圈状态N字节n = N 或 N+1

(*)N = 输入数量 / 8,如果余数不为 0,则 N = N+1

  • 异常
异常功能码1字节0x81
异常码1字节01、02、03、04
  • 示例

此命令请求离散线圈 20 至 38 的状态。
请求:01 01 00 13 00 13 8C 02

01:从机地址
01:功能代码01(读取线圈)
00 13:要读取的第一个线圈的数据地址。(00 13十六进制 = 19,+1偏移量 = 线圈20)
00 13:请求的线圈总数(00 13十六进制 = 19,线圈 20 - 38)
8C 02:CRC(循环冗余校验)

响应:01 01 03 CD 6B 05 42 82

01:从机地址
01:功能代码01(读取线圈)
03:要跟随的数据字节数(19个线圈 = 2个字节 + 3 位 + 5 个空闲位 = 3 个字节)
CD:线圈 27 - 20 (1100 1101)
6B:线圈 35 - 28 (0110 1011)
05:线圈 38 - 36 (0000 0101)
42 82:CRC(循环冗余校验)

注:用0填充剩余5个高端空闲位。

0x02 读离散输入状态

该功能码读取离散量输入1~2000连续状态,请求数据指定起始地址(即指定的第一个输入的地址)和输入数量。
离散输入的寻址从零开始。因此,编号为 1-16 的离散输入的寻址为 0-15。

  • 请求
功能码1字节0x02
起始地址2字节0x0000 ~ 0xFFFF
输入数量2字节1~2000(0x7D0)

响应消息中的离散输入按数据字段每位一个线圈的方式打包,状态1=ON和0=OFF。
第一个数据字节的 LSB 包含查询中寻址的输入。其他线圈紧随此字节的高位端,并在后续字节中从低位到高位排列。
如果返回的输入数量不是 8 的倍数,则最终数据字节的剩余位将用零填充(朝向字节的高位端)。字节数字段指定完整数据字节的数量

  • 响应
功能码1字节0x02
字节数1字节N*
输入状态N字节n = N 或 N+1

(*)N = 输入数量 / 8,如果余数不为 0,则 N = N+1

  • 异常
异常功能码1字节0x82
异常码1字节01、02、03、04

示例
此命令请求读取离散量输入 197 至 218 的状态。
请求:01 02 00 C4 00 16 B8 39

01:从机地址
02:功能代码02(读取线圈)
00 C4:要读取的第一个线圈的数据地址。(00 C4十六进制 = 196,+1偏移量 = 输入197)
00 16:请求的线圈总数(00 16十六进制 = 22,输入 197 至 218)
B8 39:CRC(循环冗余校验)

响应:01 02 03 AC DB 35 22 88

01:从机地址
02:功能代码01(读取线圈)
03:要跟随的数据字节数(22个输入 = 2个字节 + 6 位 + 2 个空闲位 = 3 个字节)
CD:离散输入 204 - 197(1010 1100)
6B:离散输入 212 - 205(0110 1011)
05:离散输入 218 - 213(0011 0101)
22 88:CRC(循环冗余校验)

注:用0填充剩余2个高端空闲位。

0x03 读保持寄存器

该功能码读取保持寄存器连续块的内容,请求数据说明了起始寄存器地址和寄存器的数量。
寄存器的寻址从零开始。因此,编号为 1-16 的寄存器的寻址为 0-15。

  • 请求
功能码1字节0x03
起始地址2字节0x0000 ~ 0xFFFF
线圈数量2字节1~125(0x7D)

响应消息中的寄存器数据以每个寄存器两个字节值的形式打包。对于每个寄存器,第一个字节包含高位,第二个字节包含低位

  • 响应
功能码1字节0x03
字节数1字节2×N*
寄存器值N×2字节

(*)N = 寄存器数量

  • 异常
异常功能码1字节0x83
异常码1字节01、02、03、04

示例
此命令请求读寄存器 108 至 110 。每个寄存器包含2个字,16 位
请求:01 03 00 6B 00 03 74 17

01:从机地址
03:功能代码03(读取多个保持寄存器)
00 6B:请求的第一个寄存器的数据地址(006B 十六进制 = 107,+1 偏移 = 寄存器108)
00 03:请求的寄存器总数(读取3个寄存器108至110)
74 17:CRC(循环冗余校验)

响应:01 03 06 02 2B 00 00 00 64 05 7A

01:从机地址
03:功能代码03(读取多个保持寄存器)
06:要跟随的数据字节数(3 个寄存器 x 每个寄存器 2 个字节 = 6 个字节)
02 2B:寄存器108的内容(Hi Lo)
00 00:寄存器109的内容(Hi Lo)
00 64:寄存器110的内容(Hi Lo)
05 7A:CRC(循环冗余校验)

0x04 读输入寄存器

该功能码读取读取设备中 1 到 125 个连续输入寄存器,请求数据指定起始寄存器地址和寄存器数量。
输入寄存器的寻址从零开始。因此,编号为 1-16 的输入寄存器的寻址为 0-15。。

  • 请求
功能码1字节0x04
起始地址2字节0x0000 ~ 0xFFFF
输入寄存器值2字节1~125(0x7D)

响应消息中的寄存器数据按每个输入寄存器两个字节打包。对于每个寄存器,第一个字节包含高位,第二个字节包含低位。

  • 响应
功能码1字节0x04
字节数1字节2×N*
输入寄存器数值N×2字节

(*)N = 输入寄存器的数量

  • 异常
异常功能码1字节0x84
异常码1字节01、02、03、04

示例
此命令请求读取输入寄存器9。
请求:01 04 00 08 00 01 B0 08

01:从机地址
04:功能代码04(读取输入寄存器)
00 08:请求的第一个寄存器的数据地址(0008 十六进制 = 8,+ 1 偏移量 = 输入寄存器9)
00 01:请求的线圈总数(读取 1 个寄存器)
B0 08:CRC(循环冗余校验)

响应:01 04 02 00 0A 39 37

01:从机地址
04:功能代码04(读取输入寄存器)
02:要跟随的数据字节数(1 个寄存器 x 每个寄存器 2 个字节 = 2 个字节)
00 0A:寄存器9的内容
39 37:CRC(循环冗余校验)

0x05 写单线圈

该功能码用于将设备中的单个输出写入“开”或“关”。请求的“开/关”状态由请求数据字段中的常量指定。请求指定要强制的线圈的地址。
线圈的寻址从零开始。因此编号为 1 的线圈的寻址为 0。
值 0xFF00 请求线圈处于 On 状态。值 0x0000 请求线圈处于 Off 状态。所有其他值都是非法的,不会影响线圈。

  • 请求
功能码1字节0x05
线圈地址2字节0x0000 ~ 0xFFFF
状态值2字节0x0000 或 0xFF00
  • 响应
功能码1字节0x05
线圈地址2字节0x0000 ~ 0xFFFF
状态值2字节0x0000 或 0xFF00
  • 异常
异常功能码1字节0x85
异常码1字节01、02、03、04

示例
此命令请求写线圈 173 为ON。
请求:01 05 00 AC FF 00 4C 1B

01:从机地址
05:功能代码05(写入单个线圈)
00 AC:线圈的数据地址(00 AC 十六进制 = 172, + 1 偏移 = 线圈173)
FF 00:要写入的状态(FF00 = ON,0000 = OFF)
4C 1B:CRC(循环冗余校验)

响应:01 05 00 AC FF 00 4C 1B

01:从机地址
05:功能代码05(写入单个线圈)
00 AC:线圈的数据地址(00 AC 十六进制 = 172, + 1 偏移 = 线圈173)
FF 00:要写入的状态(FF00 = ON,0000 = OFF)
4C 1B:CRC(循环冗余校验)

0x06 写单寄存器

该功能码向设备写单个保存寄存器,请求数据说明了写入寄存器地址,以及寄存器值。
寄存器寻址从零开始。因此,编号为 1 的寄存器的寻址为 0。

  • 请求
功能码1字节0x06
寄存器地址2字节0x0000 ~ 0xFFFF
寄存器值2字节0x0000 ~ 0xFFFF
  • 响应
功能码1字节0x06
寄存器地址2字节0x0000 ~ 0xFFFF
寄存器值2字节0x0000 ~ 0xFFFF
  • 异常
异常功能码1字节0x86
异常码1字节01、02、03、04

示例
此命令请求将十六进制 00 03写入寄存器2。
请求:01 06 00 01 00 03 98 0B

01:从机地址
06:功能代码06(写入单个保持寄存器)
00 01:寄存器的数据地址(00 01 十六进制 = 1,+1 偏移量 = 寄存器2)
00 13:要写入寄存器的值
98 0B:CRC(循环冗余校验)

响应:01 01 03 CD 6B 05 42 82

01:从机地址
06:功能代码06(写入单个保持寄存器)
00 01:寄存器的数据地址(00 01 十六进制 = 1,+1 偏移量 = 寄存器2)
00 13:要写入寄存器的值
98 0B:CRC(循环冗余校验)

0x0F 写多个线圈

该功能码向强制线圈序列中的每个线圈打开或关闭,请求数据说明了线圈起始地址,线圈数量以及线圈状态。
寄存器寻址从零开始。因此,编号为 1 的寄存器的寻址为 0。
请求的开/关状态由请求数据字段的内容指定。字段中某个位位置的逻辑“1”请求相应输出为开。逻辑“0”请求其为关。

  • 请求
功能码1字节0x0F
起始地址2字节0x0000 ~ 0xFFFF
线圈数量2字节0x0001 ~ 0x07B0
字节数1字节N*
输出值N x 1 字节

(*)N = 输出数量 / 8,如果余数不为 0,则 N = N+1

  • 响应
功能码1字节0x0F
起始地址2字节0x0000 ~ 0xFFFF
线圈数量2字节0x0001 ~ 0x07B0
  • 异常
异常功能码1字节0x8F
异常码1字节01、02、03、04

示例
此命令请求从线圈20写入10个线圈。
请求:01 0F 00 13 00 0A 02 CD 01 72 CB

01:从机地址
0F:功能代码0F(写入多个线圈)
00 13:要读取的第一个线圈的数据地址。(00 13十六进制 = 19,+1偏移量 = 线圈20)
00 0A:要写入的线圈数(0A十六进制=10)
02:要跟随的数据字节数(10 个线圈 = 1 个字节 + 2 个位 + 6 个空闲位 = 2 个字节)
CD:线圈 27 - 20 (1100 1101)
01:线圈 29 - 28 (0100 0001)
72 CB:CRC(循环冗余校验)

响应:01 0F 00 13 00 0A 24 09

01:从机地址
0F:功能代码0F(写入多个线圈)
00 13:要读取的第一个线圈的数据地址。(00 13十六进制 = 19,+1偏移量 = 线圈20)
00 0A:要写入的线圈数(0A十六进制=10)
24 09:CRC(循环冗余校验)

0x10 写多个寄存器

该功能码向设备写入一组连续的寄存器(1 到 123 个寄存器),请求数据说明了寄存器起始地址,请求的写入值在请求数据字段中指定。数据按每个寄存器两个字节打包。

  • 请求
功能码1字节0x10
起始地址2字节0x0000 ~ 0xFFFF
寄存器数量2字节0x0001 ~ 0x007B
字节数1字节2×N*
寄存器值N2 x字节

(*)N = 寄存器数量

  • 响应
功能码1字节0x10
起始地址2字节0x0000 ~ 0xFFFF
寄存器数量2字节0x0001 ~ 0x007B
  • 异常
异常功能码1字节0x90
异常码1字节01、02、03、04

示例
此命令请求将十六进制00 0A和01 02写入寄存器2和寄存器3。
请求:01 10 00 01 00 02 04 00 0A 01 02 92 30

01:从机地址
01:功能代码 10(写入多个保持寄存器)
00 01:第一个寄存器的数据地址(00 11十六进制 = 1,+1偏移量 = 寄存器2)
00 02:要写入的寄存器的数量
04:要跟随的数据字节数(2 个寄存器 x 每个寄存器 2 个字节 = 4 个字节)
00 0A:要写入寄存器2 的值
01 02:要写入寄存器3 的值
92 30:CRC(循环冗余校验)

响应:01 01 10 00 01 00 02 1B D3

01:从机地址
01:功能代码 10(写入多个保持寄存器)
00 01:第一个寄存器的数据地址(00 11十六进制 = 1,+1偏移量 = 寄存器2)
00 02:要写入的寄存器的数量
1B D3:CRC(循环冗余校验)

Modbus 系列模块说明

Modbus系列模块以及添加了寄存器地址介绍,下面介绍如果使用功能码操作对于的寄存器。

继电器输出

地址(HEX)地址存储内容寄存器取值权限Modbus 功能码
0x0000
……
0x0007
道通1~通道8继电器地址0xFF00:继电器开启;
0x0000:继电器关闭;
0x5500:继电器翻转;
读/写0x01,0x05,0x0F

上面为8路继电器模块的寄存器地址介绍。0x代表线圈(DO)类地址,即可控制继电器开关,可以通过0x01,0x05,0x0F功能码操作。

  • 读取继电器状态

请求:01 01 00 13 00 13 8C 02

01:从机地址
01:功能代码01(读取线圈状态)
00 00:要读取继电器起始地址
00 08:读取继电器数量
3D CC:CRC(循环冗余校验)
  • 控制单个继电器

请求:01 05 00 00 FF 00 8C 3A

01:从机地址
05:功能代码05(写单线圈)
00 00:要操作继电器地址
FF 00:要写入的状态
8C 3A:CRC(循环冗余校验)
  • 写继电器状态

请求:01 0F 00 00 00 08 01 FF BE D5

01:从机地址
0F:功能代码0F(写多线圈)
00 00:要操作继电器起始地址
00 08:读取继电器数量
01:状态字节数
FF:继电器状态,每位对应一个继电器
3D CC:CRC(循环冗余校验)

离散输入

地址(HEX)地址存储内容寄存器取值权限Modbus 功能码
1x0000
……
1x0007
道通1~通道8输入地址表示1~8输入通道状态0x02

上面为8路IO模块的输入寄存器地址介绍。1x代表触点(DI)类地址,即离散输入,可以通过0x02功能码操作。

  • 读取继电器状态

请求:01 02 00 00 00 08 79 CC

01:从机地址
02:功能代码02(读离散输入状态)
00 00:读取离散输入起始地址
00 08:读取离散输入数量
79 CC:CRC(循环冗余校验)

模拟输入

地址(HEX)地址存储内容寄存器取值权限Modbus 功能码
3x0000
……
3x0007
道通1~通道8输入数据读取数值为无符号十六进制0x04

上面为8路模拟输入的寄存器地址介绍。3x代表输入寄存器(AI)类地址,即模拟输入,可以通过0x04功能码操作。

  • 读取继电器状态。

请求:01 04 00 00 00 08 F1 CC

01:从机地址
04:功能代码04(读输入寄存器)
00 00:读取输入起始地址
00 08:读取输入数量
79 CC:CRC(循环冗余校验)

模拟输出

地址(HEX)地址存储内容寄存器取值权限Modbus 功能码
4x0000
……
4x0007
道通1~通道8输出数据数值为无符号十六进制读/写0x03,0x06,0x10

上面为8路模拟输出的寄存器地址介绍。4x代表保持寄存器,输出寄存器(AO)类地址,即模拟输出,可以通过0x03,0x06,0x10功能码操作。

  • 读取输出寄存器。

请求:01 03 00 00 00 08 44 0C

01:从机地址
03:功能代码03(读保持寄存器)
00 00:读取输出起始地址
00 08:读取输出数量
44 0C:CRC(循环冗余校验)
  • 写单通道输出。

请求:01 06 00 00 03 E8 89 74

01:从机地址
06:功能代码06(写单保持寄存器)
00 00:写入输出寄存器
03 E8:写入数值
89 74:CRC(循环冗余校验)
  • 写多通道输出。

请求:01 10 00 00 00 08 10 03 E8 03 E8 03 E8 03 E8 03 E8 03 E8 03 E8 03 E8 3C 05

01:从机地址
10:功能代码 10(写入多个保持寄存器)
00 00:第一个寄存器起始地址
00 08:要写入的寄存器的数量
10:输出字节数
03 E8:第 1 通道模拟量输出
……
03 E8:第 8 通道模拟量输出
92 30:CRC(循环冗余校验)

Modbus 资料连接

https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf
https://ozeki.hu/p_5873-modbus-function-codes.html

TAG: LCD1602 I2C蓝 3.3V/5V适用于Arduino树莓派Jetson Nano ESP32 Pico 树莓派5外壳Argon ONE V3 Case带风扇红外IR ¥215 电子墨水屏标签 树莓派4 PiKVM-A3 V3 HAT远程控制kvm运维overip服务器HDMI CSI Jetson Orin Nano Super 人工智能(AI)开发套件内置8GB显存核心板 墨水屏字库 使用教程 PDF 30kg舵机 Jetson Orin专用铝合金外壳 带摄像头支架迷你机箱 适用于Jetson Orin Nano和Jetson Orin NX套件 斑梨 5G转千兆以太网 荔枝派糖Lichee Tang Nano 20K FPGA Sipeed RISCV Linux开源游戏机 JLINK OB ESP32手表1.54寸电子墨水屏E-lnk开源可编程Watch适用于Arduino For Watchy CAN逻辑分析仪 树莓派5 PCIE转SSD Modbus RTU 双电子眼睛0.71寸圆形LCD显示屏适用于Arduino/树莓派/PICO/STM32 工业级USB HUB 4路USB3.2 Gen1 4U 1拖4 教程 树莓派RTC