跳到主要内容

Modbus协议简介

  • Modbus是一种串行通信协议为工业电子设备互联的业界常用标准协议本文档简单介绍Modbus协议。

Modbus 协议简介

  • Modbus就是一个软件层的总线通信协议。他不依赖于硬件总线Modbus协议支持多种电气接口包括RS232、RS-422、RS485、TCP/IP等。
  • Modbus的传输模式也分为多个版本常见的有Modbus ASCIIModbus RTUModbus 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字节)。

名称功能
设备地址数据要发给哪个从设备 (1-247)
功能码要执行什么操作 (如:读线圈、读保持寄存器)
数据操作的具体参数 (如:起始地址、读取数量)
校验码确保数据在传输中没有出错
01: 表示设备地址
05: 功能码表示写单个线圈
00 00 FF 00: 表示请求数据,00 00 表示线圈地址,FF 00 表示数值
DD FA: 表示 CRC 校验

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

Modbus 事务处理(无差错)

Modbus 事务处理(异常响应)

以上面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 服务器端事务处理状态图:一直等待接收数据;接收到数据后先判断功能码,异常则返回 0x01;正常则继续判断数据地址,异常则返回 0x02;正常则判断数值,异常则返回 0x03;正常则执行操作,异常则返回 0x040x050x06;正常则返回响应,继续等待下次数据。

Modbus 事务处理状态图

Modbus 数据类型

Modbus协议规定了4个存储区0x1x3x4x分别表示四种地址类型。

地址类型地址范围数据类型读写功能码常见用途
线圈(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存储设定值、设置模拟输出

注意:PLC 地址常采用5位地址(如 40001)实际通信中发送的是寄存器地址(0 开始)故PLC地址=协议地址 + 1。

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

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:功能代码02(读离散输入)
03:要跟随的数据字节数(22 个输入 = 2 个字节 + 6 位 + 2 个空闲位 = 3 个字节)
AC:离散输入 204 - 197(1010 1100)
DB:离散输入 212 - 205(1101 1011)
35:离散输入 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 = ON0000 = 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 = ON0000 = 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 03:要写入寄存器的值
98 0B:CRC(循环冗余校验)

响应:01 06 00 01 00 03 98 0B

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

响应:01 10 00 01 00 02 10 08

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

Modbus 系列模块说明

Modbus系列模块已添加寄存器地址介绍,下面介绍如何使用功能码操作对应的寄存器。

继电器输出

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

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

  • 读取继电器状态

请求:01 01 00 00 00 08 3D CC

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:继电器状态,每位对应一个继电器
BE D5: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:读取输入数量
F1 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 通道模拟量输出
3C 05:CRC(循环冗余校验)

Modbus 资料文档