Modbus协议简介

来自Waveshare Wiki
跳转至: 导航搜索

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)。
Modubs Protocol 1.jpg
上图表示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)。
一次完全的数据操作如下图所示,主机(客户机)发送数据请求,从机(服务器)执行操作并返回响应数据,如果出错则返回异常码,主机通过接收响应数据判读操作是否正常。
Modubs Protocol 2.jpg
Modubs Protocol 3.jpg
以上面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,正常则返回响应继续等待下次数据。
Modubs Protocol 4.jpg

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(循环冗余校验)