概述
我在学习的过程中一直使用封装的模块,使用简单易上手,但是在编写通信协议时,我们还是要理解Modbus协议,才能更加完善描述协议,今天使用两台N80系列PLC进行通讯,使用串口助手演示一下具体的报文含义。Modbus 协议规定的传输模式一般有ASCII、RTU(远程终端控制系统)、TCP三种报文类型,常用的是Modbus RTU,矩形PLC支持MODBUS RTU和MODBUS TCP两种方式,即串口与网口。
目录
一、Modbus RTU协议介绍
1、Modbus存储区
从机需要存储数据,那么肯定要有一个存储区,那就需要文件操作,文件可以分为只读(-r)和读写(-wr)两种类型。
并且存储的数据类型可以分为 :布尔量 和 16位寄存器。
布尔量比如IO口的电平高低,灯的开关状态等。16位寄存器比如 传感器的温度数据,存储的密码等。
Modbus协议规定了4个存储区 分别是0 、1、3 、4区 其中1区和4区是可读可写,1区和3区是只读。
下表对PLC中地址进行说明
区号 | 寄存器种类 | 读写 | 地址 | PLC硬件点位对应功能 | I/O输出说明 及模拟量对应关系 | 其他说明 |
0区 | 输出线圈(位) | 可读可写布尔量 | 00001-09999 | 数字量(开关量)输出(DO) | 0/1 | 硬件点位从起始地址对应,不包括全部地址 |
1区 | 输入线圈(位) | 只读布尔量 | 10001-19999 | 数字量(开关量)输入(DI) | 0/1 | 硬件点位从起始地址对应,不包括全部地址 |
3区 | 输入寄存器(字) | 只读寄存器 | 30001-39999 | 模拟量输入(30001起始)(AI) | 0~65535----0~20mA、 13104~65535----4~20mA | 硬件点位从起始地址对应,只有部分地址做模拟量输入,不包括全部地址, |
4区 | 保持寄存器(字) | 可读可写寄存器 | 40001-49999 | 模拟量输出(40001起始)(AO) | 0~65535----0~20mA、 13104~65535----4~20mA | 硬件点位从起始地址对应,只有部分地址做模拟量输出,不包括全部地址 |
矩形PLC中具体地址说明可以参考帮助手册中的地址说明。
2、Modbus RTU报文
我们需要发送的数据,通常是以一种特定格式的一连串数据,其中每一个数据我们通常称为一个报文,也称帧数据,一个报文就是一帧数据,一个数据帧就一个报文,发送的一串完整的指令报文,本质就是一串数据。
(1)、帧格式
Modbus RTU报文的帧格式如下
帧结构 :站地址 + 功能码 + 数据 + 校验
站地址 | 功能码 | 数据 | CRC校验(包括站地址在内数据都参与校验) |
---|---|---|---|
1 byte | 1 byte | N bytes | 2 bytes |
(2)、帧结构说明
-
站地址: 占用一个字节,范围0-255,其中有效范围是1-247,其他有特殊用途,比如255是广播地址(广播地址就是应答所有地址,站地址是主站对从站进行查询和回复的必须条件)。
-
功能码:占用一个字节,不同的功能码对应不同功能,进行各种数据操作时要选择对应的功能码,才能实现正确的操作。
-
数据:根据功能码不同,有不同结构,在下面的实例中有说明。
-
校验:为了保证传输的数据不发生错误,会把传输的数据进行一种校验计算,是否一致,如果一致,就说明这帧数据是正确的,我再回复;如果不一样,说明这个数据在传输的时候出了问题,数据不对的,所以就抛弃了。(矩形PLC有专门的CRC校验计算功能块,我一般是直接用的,具体计算过程我没了解过,感兴趣的可以自己去网上学习)
(3)、寄存器功能码说明
Modbus协议处理的所有数据按照存储数据的类型可分为位寄存器(1bit)和16位寄存器(16bit)。因此功能码可分为位操作和字操作两类。位操作的最小单位是bit,字操作的最小单位是两个字节。
我们一般常用的功能码如下
数据类型 | 功能码 | 功能描述 | 异常码 | 操作数量 | |
---|---|---|---|---|---|
位操作(布尔量) | 物理离散量输入(DI) | 0x02H | 读输入离散量 | 0x82H | 单个或多个 |
内部比特或物理线圈(DO) | 0x01H | 读线圈 | 0x81H | 单个或多个 | |
0x05H | 写单个线圈 | 0x85H | 单个 | ||
0x0FH | 写多个线圈 | 0x8FH | 多个 | ||
字操作 | 输入寄存器 | 0x04H | 读输入寄存器 | 0x84 | 单个或多个 |
内部存储器或物理输出存储器 | 0x03H | 读多个寄存器 | 0x83 | 单个或多个 | |
0x06H | 写单个寄存器 | 0x86 | 单个 | ||
0x10H | 写多个寄存器 | 0x90 | 多个 |
二、操作演示
下面我们进行演示,我们继续使用之前编写的程序,为了更明显的区别数据,先将硬件的串口连接断开,因为从站一般不需要额外编写数据读写程序,从站会自动对主站发送至从站的指令进行应答处理,使用串口助手时,会将主站发送数据与从站回复数据都接收到同一组中,断开硬件接线从站就无法应答,我们就可以直观的看到主站发送的数据。(由于截图过于麻烦,不进行断开操作)由于PLC扫描是从上至下,从左到右,在进行通讯时,会进行多次数据发送,因此我们给功能块添加上升沿触发。
从站地址为1
1、位操作(布尔量)
1.1、线圈写入
PLC程序如下,寄存器读写功能块中站类型设置应为0,M_BUS上节点置1,执行写功能。(图太大了,后续不进行截图了,数据个数对地址41036中的值进行更改就行)
(1)、单个线圈写入
a、将从站地址为0001的线圈状态置为1,DO1指示灯点亮,继电器动作。
b、串口助手收到数据如下(图太大了,过于麻烦,后续就不放图了,直接写报文了)
询问帧:01 05 00 00 FF 00 8C 3A
应答帧:01 05 00 00 FF 00 8C 3A
c、数据分析
因为Modbus地址在与PLC对应时,需要减1,因此寄存器地址在发送的报文中对应0000,并非是0001.
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 数据 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 05 | 00 | 00 | FF | 00 | 8C | 3A |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 数据 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 05 | 00 | 00 | FF | 00 | 8C | 3A |
(2)、多个线圈写入
a、给从站线圈0001、0002赋值1,更改程序发送数据,可以看到从站DO1、DO2指示灯点亮,听到继电器动作
b、串口助手接收数据如下
[2024-06-20 17:33:41.953]# RECV HEX>
01 0F 00 00 00 02 01 03 9E 96 01 0F 00 00 00 02 D4 0A
询问帧:01 0F 00 00 00 02 01 03 9E 96
应答帧:01 0F 00 00 00 02 D4 0A
c、数据分析
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||||
---|---|---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | 字节数 | 数据 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||||
01 | 0F | 00 | 00 | 00 | 02 | 01 | 03 | 9E | 96 |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | |||
---|---|---|---|---|---|---|
寄存器起始地址 | 线圈数量 | |||||
高字节 | 低字节 | 数据低 | 高字节 | 低字节 | ||
01 | 0F | 00 | 00 | 02 | D4 | 0A |
1.2、线圈读
PLC程序如下,寄存器读写功能块中站类型设置应为0,M_BUS上节点置零,执行读功能。(后续不进行截图了,数据个数对地址41046中的值进行更改就行)
(1)、单个线圈读
a、从10001中读取数据放到0220中
b、串口助手报文
[2024-06-20 17:49:57.811]# RECV HEX>
01 02 00 00 00 01 B9 CA 01 02 01 00 A1 88
询问帧:01 02 00 00 00 01 B9 CA
应答帧:01 02 01 00 A1 88
c、数据分析
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 02 | 00 | 00 | 00 | 01 | B9 | CA |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||
---|---|---|---|---|---|
输入首地址 | 数据 | ||||
高字节 | 低字节 | ||||
01 | 02 | 01 | 00 | A1 | 88 |
(2)、多个线圈读
a、将 41046值更改为2,读取两个10001、10002的值(都为1)
b、串口助手数据
[2024-06-20 18:21:51.840]# RECV HEX>
01 02 00 00 00 02 F9 CB 01 02 01 03 E1 89
询问帧:01 02 00 00 00 02 F9 CB
应答帧:01 02 01 03 E1 89
c、数据分析
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 02 | 00 | 00 | 00 | 02 | F9 | CB |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||
---|---|---|---|---|---|
输入首地址 | 数据 | ||||
高字节 | 低字节 | ||||
01 | 02 | 01 | 03 | E1 | 89 |
2、字操作
2.1、寄存器写入
PLC程序如下,寄存器读写功能块中站类型设置应为3或4,M_BUS上节点置1,执行写功能。
(1)、单个寄存器写入
a、将主站40001的值1写入40001,数据数量更改41036中的值即可
b、串口助手数据如下
[2024-06-20 18:31:54.455]# RECV HEX>
01 06 00 00 00 01 48 0A 01 06 00 00 00 01 48 0A
询问帧:01 06 00 00 00 01 48 0A
应答帧:01 06 00 00 00 01 48 0A
c、数据分析
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 数据 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 06 | 00 | 00 | 00 | 01 | 48 | 0A |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 数据 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 06 | 00 | 00 | 00 | 01 | 48 | 0A |
(2)、多个寄存器写入
a、将主站40001~40003的值写入从站40001~40003,41036更改为数量3,主站对应地址内数据如下
主站地址 | 值 | 从站地址 |
40001 | 1 | 40001 |
40002 | 2 | 40002 |
40003 | 3 | 40003 |
b、串口助手数据
[2024-06-20 18:44:07.322]# RECV HEX>
01 10 00 00 00 03 06 00 01 00 02 00 03 3A 81 01 10 00 00 00 03 80 08
询问帧:01 10 00 00 00 03 06 00 01 00 02 00 03 3A 81
应答帧:01 10 00 00 00 03 80 08
c、数据分析:
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | 数据1 | 数据2 | 数据3 | |||||||||
高字节 | 低字节 | 数据高 | 数据低 | 数据高 | 数据低 | 数据高 | 数据低 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 10 | 00 | 00 | 00 | 03 | 00 | 01 | 00 | 02 | 00 | 03 | 3A | 81 |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 10 | 00 | 00 | 00 | 03 | 80 | 08 |
2.2、寄存器读取
PLC程序如下,寄存器读写功能块中站类型设置应为3或4,M_BUS上节点置0,执行读功能。
(1)、单个寄存器读
a、单个寄存器读,将从站40001的数据读取至主站40200中,地址41046数量设置为1.
b、串口助手数据如下
[2024-06-20 18:54:46.201]# RECV HEX>
01 03 00 00 00 01 84 0A 01 03 02 00 01 79 84
询问帧:01 03 00 00 00 01 84 0A
应答帧:01 03 02 00 01 79 84
c、数据分析
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 03 | 00 | 00 | 00 | 01 | 84 | 0A |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | |||
---|---|---|---|---|---|---|
字节数量 | 数据 | |||||
数据高 | 数据低 | 高字节 | 低字节 | |||
01 | 03 | 02 | 00 | 01 | 79 | 84 |
(2)、多个寄存器读
a、读40001~40003的值到40200~40202,将41046值改为3.
b、串口助手数据如下
[2024-06-20 19:05:11.189]# RECV HEX>
01 03 00 00 00 03 05 CB 01 03 06 00 01 00 02 00 03 FD 74
询问帧:01 03 00 00 00 03 05 CB
应答帧:01 03 06 00 01 00 02 00 03 FD 74
c、数据分析
询问RTU帧
站地址 | 功能码 | 数据 | CRC校验 | ||||
---|---|---|---|---|---|---|---|
寄存器起始地址 | 寄存器数量 | ||||||
高字节 | 低字节 | 数据高 | 数据低 | 高字节 | 低字节 | ||
01 | 03 | 00 | 00 | 00 | 03 | 05 | CB |
应答RTU帧
站地址 | 功能码 | 数据 | CRC校验 | |||||||
---|---|---|---|---|---|---|---|---|---|---|
字节数量 | 数据1 | 数据2 | 数据3 | |||||||
数据高 | 数据低 | 数据高 | 数据低 | 数据高 | 数据低 | 高字节 | 低字节 | |||
01 | 03 | 06 | 00 | 01 | 00 | 02 | 00 | 0. | FD | 74 |