Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
一、Modbus-RTU报文格式
设备地址 | 功能码 | 数据区 | CRC校验 |
---|---|---|---|
1个字节 | 1个字节 | N个字节 | 2 个字节(16 位循环冗余校验码) |
注:1 个字节由 8 位二进制组成,即 8 bits。
1. 设备地址
设备地址是每次通讯信息帧的第一个字节,从 0 到 255。这个字节表明由用户设置为该地址的设备将接收由主站发过来的此条信息,每个设备必须有一个唯一的地址,只有符合这个地址的设备才能响应主站回送信息。当从机回送信息时,回送数据的第一个字节也是这个设备的地址。
主站发送的数据当中的设备地址表明将要发送到哪个设备,设备返回的数据当中的设备地址表明此数据来自何处。
2. 功能码
功能码是每次通讯的数据的第二个字节,MODBUS 通讯规约可以定义的功能码的范围为 1 到 127,我们仅采用了其中一部分功能码,具体如下:
功能码(HEX) | 定义 | 描述 |
---|---|---|
01 | 读开关 | 读取一路或多路开关的状态 |
03 | 读寄存器 | 读取一个或多个寄存器 (模拟量) 数据 |
05 | 写单路开关 | 控制一路开关的分或合 |
06 | 写单个寄存器 | 写入一个寄存器 (模拟量)数据 |
0F | 写多路开关 | 控制多路开关的分或合 |
10 | 写多个寄存器 | 写入多个寄存器 (模拟量)数据 |
3. 数据区
数据区是主站要写给从站的数据和从站回复主站要读的数据。
数据区的内容以 Big Endian 形式储存,通讯时先发高位字节,后发低位字节。
4. CRC校验
CRC校验是16 位循环冗余校验码,主要用来校验传输数据的准确性。
CRC校验详细介绍
二、功能码详细说明
1. 功能码01:读开关
所有的开关都以二进制位进行编码,每个开关一位,一个字节可以容纳 8 个开关的状态,1 为合状态,0 为分状态。
开关的地址为位编码的,可以理解为地址为 0 的开关在数据区第 1 个字节的 D0 位,地址为 1 的开关在 数据区的第 1 个字节的 D1 位,……地址为 7 的开关在数据区的第 1 个字节的 D7 位,地址为 8 的开关 在数据区的第 2 个字节的 D0 位,地址为 X 的开关,在数据区第 X/8+1 个字节的 D[X%8]位。
主机发送报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 01:读开关状态 |
起始地址 | 2个字节 | 从哪个地址的开关开始读取开关状态 (起始 bit 位) |
开关个数 | 2个字节 | 读取几个开关的状态 (bits 数) |
CRC校验码 | 2个字节 | 设备地址、功能码、起始地址、开关个数的 CRC 校验码 |
从机返回数据报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 01:读开关状态 |
数据字节数N | 1个字节 | 要返回多少个字节的数据,每个字节包含 8 开关的状态 数据字节数 N= (开关个数+7)÷8 |
数据 | N个字节 | 返回的第 1 个字节的 D0 位为第一个 (起始地址) 开关的状态; 返回的第 1 个字节的 D1 位为第二个 (起始地址+1) 开关的状态; …… 返回的第 N 个字节的 D0 位为第 8N-7 个 (起始地址+8N-8) 开关的状态 返回的第 N 个字节的 D1 位为第 8N-6 个 (起始地址+8N-7) 开关的状态 …… |
CRC校验码 | 2个字节 | 设备地址、功能码、数据字节数、数据的 CRC 校验码 |
2. 功能码03:读寄存器
每个寄存器都是两个字节 (16 位二进制数据),高位字节在前,低位字节在后。每个寄存器表示的数据 范围为-32768 到 32767,负数用补码 (two’s complement) 表示。
寄存器的地址编码,可以理解为地址为 0 的寄存器在数据区的第 1 个和第 2 个字节,地址为 1 的寄存 器在数据区的第 3 个和第 4 个字节,地址为 2 的寄存器在数据区的第 5 个和第 6 个字节……
主机发送报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 03:读寄存器 |
起始地址 | 2个字节 | 从哪个地址的寄存器开始读取数据 |
寄存器个数 | 2个字节 | 读取几个寄存器的数据 (字节数=寄存器个数×2) |
CRC校验码 | 2个字节 | 设备地址、功能码、起始地址、寄存器个数的 CRC 校验码 |
从机返回数据报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 03:读寄存器 |
数据字节数N | 1个字节 | 数据字节数 N=寄存器个数×2 |
寄存器数据 | N个字节 | 寄存器个数=数据字节数÷2 返回的第一个字节和第二个字节是第一个 (起始地址) 的寄存器数据 返回的第三个字节和第四个字节是第二个 (起始地址+1) 的寄存器数据 …… |
CRC校验码 | 2个字节 | 设备地址、功能码、数据字节数、寄存器数据的 CRC 校验码 |
3. 功能码05:写单路开关
主机发送报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 05:写单路开关 |
开关的地址 | 2个字节 | 对哪个地址的开关进行控制 |
控制命令 | 2个字节 | FF00 为合闸命令,0000 为分闸命令 |
CRC校验码 | 2个字节 | 设备地址、功能码、开关的地址、控制命令的 CRC 校验码 |
从机返回数据报文格式:
从机返回的报文与主机发送的报文完全相同。
从机返回这个报文,说明装置接受了遥控命令,开始执行命令,判断是否成功的执行完成了要以读开关的状态等于控制的目标值为准,即读出的开关状 态等于写入的开关状态,认为遥控执行成功的完成了。
4. 功能码06:写单个寄存器
主机发送报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 06:写单个寄存器 |
寄存器地址 | 2个字节 | 把数据写入哪个寄存器 |
写入的数据 | 2个字节 | 写入寄存器的数据 |
CRC校验码 | 2个字节 | 设备地址、功能码、寄存器地址、写入的数据的 CRC 校验码 |
从机返回数据报文格式:
从机返回的报文与主机发送的报文完全相同。
从机返回这个报文,说明装置接受了写入寄存器的命令, 开始执行命令,判断是否成功的执行完成了写入数据,要以读寄存器的数据等于写入的值为准, 即读出的寄存器数据等于写入的寄存器数据,认为写入执行成功的完成了。
5. 功能码0F:写多路开关
主机发送报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 0F:写多路开关 |
起始地址 | 2个字节 | 从哪个地址的开关开始进行控制 |
开关个数 | 2个字节 | 对几个开关进行控制 |
数据字节数N | 1个字节 | 写入开关的数据的字节数,即接下来的遥控命令的字节数 数据字节数 N= (开关个数+7)÷8 |
写入的数据 | N个字节 | 写入的第 1 个字节的 D0 位为第一个 (起始地址) 开关的状态; 写入的第 1 个字节的 D1 位为第二个 (起始地址+1) 开关的状态; …… 写入的第 2 个字节的 D0 位为第九个 (起始地址+8) 开关的状态 …… |
CRC校验码 | 2个字节 | 设备地址、功能码、起始地址、开关个数、字节数、数据的 CRC 校验码 |
从机返回数据报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 0F:写多路开关 |
起始地址 | 2个字节 | 从哪个地址的开关开始进行控制 |
开关个数 | 2个字节 | 对几个开关进行控制 |
CRC校验码 | 2个字节 | 设备地址、功能码、起始地址、开关个数的 CRC 校验码 |
从机返回这个报文,说明装置接受了遥控命令,开始执行命令,判断是否成功的执行完成了要以读开关 的状态 等于控制的目标值为准,即读出的开关状态等于写入的开关状态,认为遥控执行成功的 完成了。
6. 功能码10:写多个寄存器
主机发送报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 10:写多个寄存器 |
起始地址 | 2个字节 | 从哪个地址的寄存器开始写入 |
寄存器个数 | 2个字节 | 对几个寄存器进行写入 |
数据字节数N | 1个字节 | 写入寄存器的数据的字节数,即接下来的遥调命令的字节数 数据字节数 N=寄存器个数×2 |
写入的数据 | N个字节 | 写入的第一个字节和第二个字节是第一个 (起始地址) 的寄存器数据 写入的第三个字节和第四个字节是第二个 (起始地址+1) 的寄存器数据 … |
CRC校验码 | 2个字节 | 设备地址、功能码、起始地址、寄存器个数、字节数、数据的 CRC 校验码 |
从机返回数据报文格式:
格式 | 长度 | 作用 |
---|---|---|
设备地址 | 1个字节 | 设备地址 |
功能码 | 1个字节 | 10:写多个寄存器 |
起始地址 | 2个字节 | 从哪个地址的寄存器开始写入 |
寄存器个数 | 2个字节 | 对几个寄存器进行写入 |
CRC校验码 | 2个字节 | 设备地址、功能码、起始地址、寄存器个数的 CRC 校验码 |
从机返回这个报文,说明从机接受了遥调命令,开始执行命令,判断是否成功的执行完成了要以读寄存 器的数据等于遥调的目标值为准,即读出的寄存器的数据等于写入寄存器的数据,认为遥调执 行成功的完成了。
三、从机对主机命令的回应
1. 从机对主机的正确命令回应
设备地址 | 功能码 | 数据区 | CRC校验 |
---|---|---|---|
1个字节 | 1个字节 | N个字节 | 2个字节(16 位循环冗余校验码) |
从机的功能码和主机下发的功能码相同。
2. 从机对主机的错误命令回应
设备地址 | 功能码 | 数据区 | CRC校验 |
---|---|---|---|
1个字节 | 1个字节 最高位置1 | 1个字节 错误编码 | 2个字节(16 位循环冗余校验码) |
从机功能码=主机功能码|0x80。
错误编码
编码(HEX) | 含义 | 说明 |
---|---|---|
01 | 无效功能码 | 无法实现的功能 未设置的功能 |
02 | 无效数据地址 | 起始地址越界 数据个数越界 |
03 | 无效数据值 | 数据帧个数不对 数据CRC校验出错 写入的数值无效 |
3. 错误命令回应报文示例
01 81 02 C1 91 收到的功能码为01的命令有错误(81),错误码为 02:地址无效或长度越界 。
01 83 02 C0 F1 收到的功能码为03的命令有错误(83),错误码为 02:地址无效或长度越界。
01 85 03 02 91 收到的功能码为05的命令有错误(85),错误码为 03:写入的数值无效。
三、Modbus-RTU通讯协议报文示例
从机——设备地址为 8
从机——开关状态:0----分,1----合。
地址 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
状态 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 |
从机——寄存器数据
地址 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
数据(DEC) | 1000 | 100 | 10 | 2000 | 200 | 20 | 3000 | 300 | 30 | 4000 | 400 | 40 | 5000 | 500 | 50 | 6000 | 600 | 60 | 7000 | 700 | 70 |
数据(HEX) | 03EA | 0064 | 000A | 07D0 | 00C8 | 0014 | 0BB8 | 012C | 001E | 0FA0 | 0190 | 0028 | 1388 | 01F4 | 0032 | 1770 | 0258 | 003C | 1B58 | 02BC | 0046 |
1. 功能码01:读开关
查询地址从4到8的5个开关状态:
主机发送数据(HEX):08 01 00 04 00 05 BD 51
格式 | 长度 | 数据(HEX) | 描述 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址:08 |
功能码 | 1个字节 | 01 | 功能码01:读开关状态 |
起始地址 | 2个字节 | 00 04 | 起始地址: 0004,先发高位字节 00,后发低位字节 04 |
开关个数 | 2个字节 | 00 05 | 读取 0005 个开关的状态,先发高位字节 00,后发低位字节 05 |
CRC校验码 | 2个字节 | BD 51 | 08 01 00 04 00 05 的 CRC 校验码 |
从机返回数据(HEX):08 01 01 03 12 15
格式 | 长度 | 数据(HEX) | 描述 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 01 | 01:读开关状态 |
数据字节数N | 1个字节 | 01 | 接下来有 1 个字节的数据,最多可表示 8 开关的状态 |
数据 | 1个字节 | 03 | 只查询 5 个开关的状态,D0-D4: 开关状态,D5-D7 无意义 数据 03 用二进制表示等于 00000011 |
CRC校验码 | 2个字节 | 12 15 | 08 01 01 03 的 CRC 校验码 |
2. 功能码03:读寄存器
查询地址从2到5的4个寄存器数据:
主机发送数据(HEX):08 03 00 02 00 04 E5 50
格式 | 长度 | 数据 | 描述 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 03 | 03:读寄存器 |
起始地址 | 2个字节 | 00 02 | 起始地址: 0002,先发高位字节 00,后发低位字节 02 |
寄存器个数 | 2个字节 | 00 04 | 读取 0004 个寄存器的数据,先发高位字节 00,后发低位字节 04 |
CRC校验码 | 2个字节 | E5 50 | 08 03 00 02 00 04 的 CRC 校验码 |
从机返回数据(HEX):08 03 08 00 0A 07 D0 00 C8 00 14 50 DF
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 03 | 03:读寄存器 |
数据字节数N | 1个字节 | 08 | 接下来有 8 个字节,即 4 个寄存器的数据 |
寄存器数据 | 8个字节 | 00 0A 07 D0 00 C8 00 14 | 因为查询命令是从地址 2 的寄存器开始查询的, 返回的第一个寄存器的数据就是地址为 2 的寄存器的数据: 00 0A: 地址为 2 的寄存器的数据 = 0x000A,即10 07 D0: 地址为 3 的寄存器的数据 = 0x07D0,即 2000 00 C8: 地址为 4 的寄存器的数据 = 0x00C8,即 200 00 14: 地址为 5 的寄存器的数据 = 0x0014,即 20 |
CRC校验码 | 2个字节 | 50 DF | 08 03 08 00 0A 07 D0 00 C8 00 14 的 CRC 校验码 |
3. 功能码05:写单路开关
对地址为6的开关进行合闸:
主机发送数据(HEX):08 05 00 06 FF 00 6C A2
从机返回数据(HEX):08 05 00 06 FF 00 6C A2
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 05 | 05:写单路开关 |
开关的地址 | 2个字节 | 00 06 | 开关的地址:0006,先发高位字节 00,后发低位字节 06 |
控制命令 | 2个字节 | FF 00 | 开关合闸命令:0xFF00 |
CRC校验码 | 2个字节 | 6C A2 | 08 05 00 06 FF 00 的 CRC 校验码 |
对地址为6的开关进行分闸:
主机发送数据(HEX):08 05 00 06 00 00 2D 52
从机返回数据(HEX):08 05 00 06 00 00 2D 52
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 05 | 05:写单路开关 |
开关的地址 | 2个字节 | 00 06 | 开关的地址:0006,先发高位字节 00,后发低位字节 06 |
控制命令 | 2个字节 | 00 00 | 开关分闸命令:0x0000 |
CRC校验码 | 2个字节 | 2D 52 | 08 05 00 06 00 00 的 CRC 校验码 |
4. 功能码06:写单个寄存器
把数据-30写入地址为8的寄存器:
主机发送数据(HEX):08 06 00 08 FF E2 C9 28
从机返回数据(HEX):08 06 00 08 FF E2 C9 28
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 06 | 06:写单个寄存器 |
寄存器地址 | 2个字节 | 00 08 | 寄存器地址:0008,先发高位字节 00,后发低位字节 08 |
写入的数据 | 2个字节 | FF E2 | 写入寄存器的数据 -30 的补码为 0xFFE2,先发高位字节 FF,后发低位字节 E2 |
CRC校验码 | 2个字节 | C9 28 | 08 06 00 08 FF E2的 CRC 校验码 |
5. 功能码0F:写多路开关
对地址为 6 的开关进行合闸置1、对地址为 7 的开关进行分闸置0、对地址为 8 的开关进行合闸置1:
主机发送数据(HEX):08 0F 00 06 00 03 01 05 07 3E
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 0F | 0F:写多路开关 |
起始地址 | 2个字节 | 00 06 | 起始地址:0006,先发高位字节 00,后发低位字节 06 |
开关个数 | 2个字节 | 00 03 | 对 3 个开关进行遥控,先发高位字节 00,后发低位字节 03 |
数据字节数 | 1个字节 | 01 | 写数据的字节数:1 个字节,最多能表示 8 个开关的状态 |
写入的数据 | 1个字节 | 05 | 由于写入 3 个开关的状态,D0-D2: 开关状态,D3-D7 无意义 数据 05 用二进制表示等于 00000101 |
CRC校验码 | 2个字节 | 07 3E | 08 0F 00 06 00 03 01 05的 CRC 校验码 |
从机返回数据(HEX):08 0F 00 06 00 03 F5 52
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 0F | 0F:写多路开关 |
起始地址 | 2个字节 | 00 06 | 起始地址:0006,先发高位字节 00,后发低位字节 06 |
开关个数 | 2个字节 | 00 03 | 对 3 个开关进行遥控,先发高位字节 00,后发低位字节 03 |
CRC校验码 | 2个字节 | F5 52 | 08 0F 00 06 00 03的 CRC 校验码 |
6. 功能码10:写多个寄存器
把数据 -20 写入地址为 5 的寄存器,把-3000 写入地址为 6 的寄存器,把 -300 写入地址为 7 的寄存器:
主机发送数据(HEX):08 10 00 05 00 03 06 FF EC F4 48 FE D4 9C 9B
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 10 | 10:写多个寄存器 |
起始地址 | 2个字节 | 00 05 | 起始地址:0005,先发高位字节 00,后发低位字节 05 |
寄存器个数 | 2个字节 | 00 03 | 写入 3 个寄存器的数据 |
数据字节数 | 1个字节 | 06 | 数据的字节数:6 个字节,包括 3 个寄存器的数据 |
写入的数据 | 6个字节 | FF EC F4 48 FE D4 | 因为写入命令是从地址 5 的寄存器开始查询的,写入的第一个寄存器的数据就是地址为 5 的寄存器的数据: FF EC: 地址为 5 的寄存器的数据 = 0xFFEC,即 -20 F4 48: 地址为 6 的寄存器的数据 = 0xF448,即 -3000 FE D4 地址为 7 的寄存器的数据 = 0xFED4,即 -300 |
CRC校验码 | 2个字节 | 9C 9B | 08 10 00 05 00 03 06 FF EC F4 48 FE D4的 CRC 校验码 |
从机返回数据(HEX):08 10 00 05 00 03 90 90
格式 | 长度 | 数据(HEX) | 作用 |
---|---|---|---|
设备地址 | 1个字节 | 08 | 设备地址 |
功能码 | 1个字节 | 10 | 10:写多个寄存器 |
起始地址 | 2个字节 | 00 05 | 起始地址:0005,先发高位字节 00,后发低位字节 05 |
寄存器个数 | 2个字节 | 00 03 | 写入 3 个寄存器的数据 |
CRC校验码 | 2个字节 | 90 90 | 08 10 00 05 00 03的 CRC 校验码 |