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