基本概念
Modbus基本概念
什么是Modbus
Modbus是Modicon(施耐德)公司于1979年开发的串行通信协议。它最初设计用于公司的可编程逻辑控制器(PLC)。Modbus是一种开放式协议,支持使用RS232/RS485/RS422协议的串行设备,同时还支持调制解调器。它的简单性以及制造商可以免费将其纳入其产品的事实使其成为连接工业电子设备的最流行的方法。Modbus比其他通信协议使用的更广泛的主要原因有以下几点:
- 公开发表并且无著作权要求
- 易于部署和维护
- 对供应商来说,修改移动本地的比特或字节没有很多限制
Modbus通过设备之间的串行线进行数据传输。最简单的设置是使用一根串行电缆连接两个设备(主设备和从设备)上的串行端口。数据以称为比特的1和0的序列发送。每个位都作为电压发送。0被发送为正电压,1被发送为负电压。
主从模式
Modbus解决了通过串行线路在电子设备之间发送信息的问题。该协议在遵循该协议的体系结构中实现主/从模型。Modbus主站(Master)负责从其他设备(Slave)请求信息。标准Modbus网络中有一个Modbus主站。主设备向从设备请求信息,最多大约可达到240个 ,0-247,0为广播地址;每个从设备都有自己唯一的从设备地址标识(Slave Address)。除了从从设备请求信息之外,主设备还可以写入从设备的内部寄存器。
通信特点
- Modbus是一种第三方公开协议,采用主从结构,主控设备房称为Modbus Master,从设备方称为Modbus Slave。
- Modbus物理接口可以选用串口:RS232、RS485、RS422,也可以采用以太网口。
- 采用串口或者以太网口对应的Modbus协议是不一样的,使用串口通讯,对应的是Modbus RTU或者Modbus ASCII协议,使用网口的对应的是Modbus TCP协议。
- Modbus通信遵循以下过程:主设备向从设备发送请求,从设备分析并处理主设备的请求,然后向主设备发送结果,如果出现任何差错,从设备都会返回一个异常的功能码。
- Modbus工作方式是请求和应答,每一次都是主设备发送指令,可以是广播或是向特定从站的单播,从站响应指令并按要求应答或者报告异常,当主站不发送请求的时候,从站是不会自己发送数据的。
- 从站与从站之间是不能直接通信,只能主站对从站发送请求,从站响应主站的请求。
应用场景
- modbus是专为工业开发的协议,所以主要用于工业场合,也可以用于基础设施,如:住宅、商业中心、机场、水处理、电厂等。
- modbus RTU协议紧凑,可以使用RS232/RS485、无线、等介质,用于速度要求不高的场合,如:楼宇、工业现场、管道输送、远程泵站等。
- modbusASCII协议比较宽松,时序要求不高,可以用于条形码阅读器、打印机、仪器仪表读取等。
- modbusTCP速度很高,可以用于实时控制、时钟对时、全局数据、发送邮件、故障设备替换、网络管理、用户网页制定和浏览、固件更新等多种服务。
三种通信模式
最常用Modbus协议总共有以下三种:Modbus ASCII, Modbus RTU,Modbus TCP
- Modbus TCP基于以太网和TCP/IP协议
- Modbus RTU和Modbus ASCII则是使用异步串行传输(通常是RS-232/422/485)
存储区概念及范围
存储区概念
Modbus通信协议有4个存储区:输出线圈
、输入线圈
、输入寄存器
、保持寄存器(也被称为输出寄存器)
- 线圈:对应PLC中bool量,1位,输入线圈相当于PLC的输入点,输出线圈相当于PLC的输出点(存放bool量)
- 寄存器:一个寄存器占1个字长度即2个字节(存放数据)
存储区范围
寄存器种类 | 数据类型 | 访问类型 | 功能码 | PLC地址 | 寄存器地址 |
线圈寄存器 | 位 | 读写 | 01H 05H 0FH | 00001-09999 | 0000H-FFFFH |
离散输入寄存器 | 位 | 只读 | 02H | 10001-19999 | 0000H-FFFFH |
输入寄存器 | 位 | 只读 | 04H | 30001-39999 | 0000H-FFFFH |
保持寄存器 | 位 | 读写 | 03H 06H 10H | 40001-49999 | 0000H-FFFFH |
寄存器相关概念
代码 | 名称 | 寄存器PLC地址 | 位/字操作 | 操作数量 |
01 | 读线圈状态 | 00001~09999 | 位操作 | 单个或多个 |
02 | 读离散输入状态 | 10001~19999 | 位操作 | 单个或多个 |
03 | 读保持寄存器 | 40001~49999 | 字操作 | 单个或多个 |
04 | 读输入寄存器 | 30001~39999 | 字操作 | 单个或多个 |
05 | 写单个线圈 | 00001~09999 | 位操作 | 单个 |
06 | 写单个保持寄存器 | 40001~49999 | 字操作 | 单个 |
15 | 写多个线圈 | 00001~09999 | 位操作 | 多个 |
16 | 写多个保持寄存器 | 40001~49999 | 字操作 | 多个 |
功能码详解
功能码 | 名称 | 作用 |
01 | 读取线圈状态 | 取得一组逻辑线圈的当前状态(ON/OFF) |
02 | 读取输入状态 | 取得一组开关输入的当前状态(ON/OFF) |
03 | 读取保持寄存器 | 在一个或多个保持寄存器中取得当前的二进制值 |
04 | 读取输入寄存器 | 在一个或多个输入寄存器中取得当前的二进制值 |
05 | 强置单线圈 | 强置一个逻辑线圈的通断状态 |
06 | 预置单寄存器 | 把具体二进值装入一个保持寄存器 |
07 | 读取异常状态 | 取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定 |
08 | 回送诊断校验 | 把诊断校验报文送从机,以对通信处理进行评鉴 |
09 | 编程(只用于484) | 使主机模拟编程器作用,修改PC从机逻辑 |
10 | 控询(只用于484) | 可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送 |
11 | 读取事件计数 | 可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时 |
12 | 读取通信事件记录 | 可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误 |
13 | 编程(184/384 484 584) | 可使主机模拟编程器功能修改PC从机逻辑 |
14 | 探询(184/384 484 584) | 可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送 |
15 | 强置多线圈 | 强置一串连续逻辑线圈的通断 |
16 | 预置多寄存器 | 把具体的二进制值装入一串连续的保持寄存器 |
17 | 报告从机标识 | 可使主机判断编址从机的类型及该从机运行指示灯的状态 |
18 | (884和MICRO 84) | 可使主机模拟编程功能,修改PC状态逻辑 |
19 | 重置通信链路 | 发生非可修改错误后,是从机复位于已知状态,可重置顺序字节 |
20 | 读取通用参数(584L) | 显示扩展存储器文件中的数据信息 |
21 | 写入通用参数(584L) | 把通用参数写入扩展存储文件,或修改之 |
22~64 | 保留作扩展功能备用 | |
65~72 | 保留以备用户功能所用 | 留作用户功能的扩展编码 |
73~119 | 非法功能 | |
120~127 | 保留 | 留作内部作用 |
128~255 | 保留 | 用于异常应答 |
在这些功能码中较长使用的是1、2、3、4、5、6号功能码,使用它们即可实现对下位机的数字量和模拟量的读写操作。
Modbus TCP通信协议
使用MODSIM模拟从站,使用网络调试助手模拟主站
01功能码读线圈
01功能码报文组成结构
- 事务处理标识:00 01,相当于报文的序号,可以是任意的数字,不会影响报文的意义
- 协议标识:默认00 00即可,也是一个没有意义的数字
- 报文长度:固定为6,指的是报文长度这两个字节后总共有6个字节
- 单元标识:01表示访问1号站,对应下面的"Device ID"
- 功能码:01读线圈,使用1号功能码
- 起始地址:从00 00开始读
- 寄存器个数:读8个线圈
格式 | 事务处理标识 | 协议标识 | 报文长度 | 单元标识 | 功能码 | 起始地址 | 寄存器个数 |
字节占用 | 2 | 2 | 2 | 1 | 1 | 2 | 2 |
报文举例 | 00 01 | 00 00 | 00 06 | 01 | 01 | 00 00 | 00 08 |
01功能码报文示例
# 报文收发
[2024-07-13 15:25:29.607]# SEND HEX/12 >>>
00 01 00 00 00 06 01 01 00 00 00 08
[2024-07-13 15:25:29.618]# RECV HEX/10 from SERVER <<<
00 01 00 00 00 04 01 01 01 09
# SEND报文解释
00 01 ==> 事务处理标识
00 00 ==> 协议标识
00 06 ==> 报文长度
01 ==> 读取1号站,对应MOSSIM配置中的Device ID
01 ==> 功能码
00 00 ==> 读取起始地址
00 08 ==> 读取数量
# RECV报文解释
00 01 ==> 没有意义
00 00 ==> 没有意义
00 04 ==> 报文长度,表示后面有4个字节的报文
01 ==> 站号地址
01 ==> 功能码
01 ==> 表示读取到的数据占用1个字节
09 ==> 表示读取到的数据,09转二进制为0000 1001,从右向左可知道线圈状态
03功能码读保持寄存器
03功能码报文组成结构
格式 | 事务处理标识 | 协议标识 | 报文长度 | 单元标识 | 功能码 | 起始地址 | 寄存器个数 |
字节占用 | 2 | 2 | 2 | 1 | 1 | 2 | 2 |
报文举例 | 02 48 | 00 00 | 00 06 | 09 | 03 | 00 06 | 00 02 |
03功能码报文示例
# 报文收发
[2024-07-13 15:42:11.552]# SEND HEX/12 >>>
02 48 00 00 00 06 09 03 00 06 00 02
[2024-07-13 15:42:11.567]# RECV HEX/13 from SERVER <<<
02 48 00 00 00 07 09 03 04 00 0B 00 14
# SEND报文解释
02 48 ==> 事务处理标识
00 00 ==> 协议标识
00 06 ==> 报文长度
09 ==> 读取9号站,对应MOSSIM配置中的Device ID
03 ==> 功能码
00 06 ==> 读取起始地址
00 02 ==> 读取数量
# RECV报文解释
02 48 ==> 事务处理标识,没有意义
00 00 ==> 协议标识,没有意义
00 07 ==> 报文长度,表示后面有7个字节的报文
09 ==> 站号地址
03 ==> 功能码
04 ==> 读取数据占用4个字节
00 0B ==> 表示读取到的数据,00 0B转十进制为11
00 14 ==> 表示读取到的数据,00 14转十进制为20
0F功能码写多个线圈
0F功能码报文组成结构
格式 | 事务处理标识 | 协议标识 | 报文长度 | 单元标识 | 功能码 | 起始地址 | 寄存器个数 | 写入数据字节数 | 写入数据 |
字节占用 | 2 | 2 | 2 | 1 | 1 | 2 | 2 | 1 | 2 |
报文举例 | 14 32 | 00 00 | 00 09 | 23 | 0F | 00 00 | 00 10 | 02 | FF FF |
0F功能码报文示例
# 报文收发
[2024-07-13 16:09:39.178]# SEND HEX/15 >>>
14 32 00 00 00 09 23 0F 00 00 00 10 02 FF FF
[2024-07-13 16:09:39.188]# RECV HEX/12 from SERVER <<<
14 32 00 00 00 06 23 0F 00 00 00 10
# SEND报文解释
14 32 ==> 事务处理标识
00 00 ==> 协议标识
00 09 ==> 报文长度
23 ==> 23十进制为35,操作35号站,对应MOSSIM配置中的Device ID
0F ==> 功能码
00 00 ==> 读取起始地址
00 10 ==> 读取数量
02 ==> 写入数据字节数
FF FF ==> FF FF转二进制为1111 1111 1111 1111 所以会从0位开始写16个1到对应位置
# RECV报文解释
14 32 ==> 事务处理标识,没有意义
00 00 ==> 协议标识,没有意义
00 06 ==> 报文长度,表示后面有7个字节的报文
23 ==> 站号地址
0F ==> 功能码
04 ==> 读取数据占用4个字节
00 00 ==> 起始地址
00 10 ==> 访问数量,00 10转十进制为16
# 此处注意Modbus报文是高字节在前,低字节在后
[2024-07-13 16:32:37.952]# SEND HEX/15 >>>
14 32 00 00 00 09 23 0F 00 00 00 10 02 01 01
[2024-07-13 16:32:37.973]# RECV HEX/12 from SERVER <<<
14 32 00 00 00 06 23 0F 00 00 00 10
# 解析规则
A0 1D
10100000 00011101 ==> A0倒叙:00000101 10111000
0000 0101 1011 1000 ==> 对应线圈位置
10号功能码:写多个保持寄存器
10号功能码报文组成结构
格式 | 事务处理标识 | 协议标识 | 报文长度 | 单元标识 | 功能码 | 起始地址 | 寄存器个数 | 写入数据字节数 | 写入数据 |
字节占用 | 2 | 2 | 2 | 1 | 1 | 2 | 2 | 1 | 6 |
报文举例 | 00 00 | 00 00 | 00 09 | 32 | 10 | 00 00 | 00 03 | 06 | 0C BA 2F 0F 1D 9B |
10号功能码报文示例
# 报文收发
[2024-07-13 18:10:57.640]# SEND HEX/19 >>>
00 00 00 00 00 09 32 10 00 00 00 03 06 0C BA 2F 0F 1D 9B
[2024-07-13 18:10:57.661]# RECV HEX/12 from SERVER <<<
00 00 00 00 00 06 32 10 00 00 00 03
# SEND报文解释
00 00 ==> 事务处理标识
00 00 ==> 协议标识
00 09 ==> 报文长度
32 ==> 32十进制为50,操作50号站,对应MOSSIM配置中的Device ID
10 ==> 功能码
00 00 ==> 读取起始地址
00 03 ==> 操作数量
06 ==> 写入数据字节数
0C BA 2F 0F 1D 9B ==> 0C BA转十进制是3258,2F 0F转十进制为:12047,1D 9B转十进制为:7579
# RECV报文解释
00 00 ==> 事务处理标识,没有意义
00 00 ==> 协议标识,没有意义
00 06 ==> 报文长度,表示后面有7个字节的报文
32 ==> 站号地址
10 ==> 功能码
04 ==> 读取数据占用4个字节
00 00 ==> 起始地址
00 03 ==> 访问数量,00 03转十进制为3
Modbus RTU通信协议
使用
VSPD
工具虚拟串口,串口调试工具收发Modbus RTU报文,使用MODSIM
作为Modbus RTU从站
1号功能码:读线圈
01号功能码报文组成结构
格式 | 站地址 | 功能码 | 寄存器地址 | 寄存器数量 | CRC校验码 |
字节占用 | 1 | 1 | 2 | 2 | 2 |
报文举例 | 01 | 01 | 00 00 | 00 08 | 3D CC |
01号功能码报文示例
# 报文收发
[2024-07-14 21:52:07.496]# SEND HEX>
01 01 00 00 00 08 3D CC
[2024-07-14 21:52:07.654]# RECV HEX>
01 01 01 6F 11 A4
# SEND报文解释
01 ==> 站地址
01 ==> 功能码
00 00 ==> 寄存器起始地址
00 08 ==> 读取寄存器数量
3D CC ==> CRC校验码
# RECV报文解释
01 ==> 站地址
01 ==> 功能码
01 ==> 读取到的数据占用字节数
6F ==> 读取到的数据,线圈记录数据为:11110110,倒序后为01101111,转16进制即为6F
11 A4 ==> CRC校验
2号功能码:读离散寄存器
02号功能码报文组成结构
格式 | 站地址 | 功能码 | 寄存器地址 | 寄存器数量 | CRC校验码 |
字节占用 | 1 | 1 | 2 | 2 | 2 |
报文举例 | 06 | 02 | 00 00 | 00 10 | 78 71 |
02号功能码报文示例
# 报文收发
[2024-07-14 22:12:54.074]# SEND HEX>
06 02 00 00 00 10 78 71
[2024-07-14 22:12:54.224]# RECV HEX>
06 02 02 6D DD E0 B1
# SEND报文解释
06 ==> 站地址
02 ==> 功能码
00 00 ==> 寄存器起始地址
00 10 ==> 读取寄存器数量
78 71 ==> CRC校验码
# RECV报文解释
06 ==> 站地址
02 ==> 功能码
02 ==> 读取到的数据占用字节数
6D DD ==> 读取到的数据,线圈记录数据为:6D ==> 01101101 DD ==> 11011101,倒序后为10110110 10111011,即为线圈各位状态
E0 B1 ==> CRC校验
04号功能码:读输入寄存器
04号功能码报文组成结构
格式 | 站地址 | 功能码 | 寄存器地址 | 寄存器数量 | CRC校验码 |
字节占用 | 1 | 1 | 2 | 2 | 2 |
报文举例 | 20 | 04 | 00 00 | 00 06 | 76 B9 |
04号功能码报文示例
# 报文收发
[2024-07-14 22:50:19.325]# SEND HEX>
20 04 00 00 00 06 76 B9
[2024-07-14 22:50:19.477]# RECV HEX>
20 04 0C 0C BA 00 00 1D 9B 00 01 00 00 00 01 05 F9
# SEND报文解释
20 ==> 站地址:32
04 ==> 功能码
00 00 ==> 寄存器起始地址
00 06 ==> 读取寄存器数量
76 B9 ==> CRC校验码
# RECV报文解释
20 ==> 站地址:32
04 ==> 功能码
0C ==> 读取到的数据占用字节数:12字节 ==> 一个寄存器地址占用1个字,一个字等于2个字节
0C BA 00 00 1D 9B 00 01 00 00 00 01 ==> 读取到的数据
0C BA ==> 3258
1D 9B ==> 7579
05 F9 ==> 校验码
05号功能码:写单个线圈
05号功能码报文组成结构
0xFF00
代表为ON
,0x0000
代表为OFF
,注意不同状态的报文要获取对应的CRC校验码才能通信成功
格式 | 站地址 | 功能码 | 寄存器地址 | 状态写入 | CRC校验码 |
字节占用 | 1 | 1 | 2 | 2 | 2 |
报文举例 | 08 | 05 | 00 04 | FF 00 | CD 62 |
05号功能码报文示例
# 报文收发
[2024-07-14 23:04:11.507]# SEND HEX>
08 05 00 04 FF 00 CD 62
[2024-07-14 23:04:11.668]# RECV HEX>
08 05 00 04 FF 00 CD 62
# SEND报文解释
08 ==> 站地址:8
05 ==> 功能码
00 04 ==> 写入位置
FF 00 ==> 写入状态:ON
CD 62 ==> CRC校验码
# RECV报文解释
08 ==> 站地址:8
05 ==> 功能码
00 04 ==> 读取到写入位置
FF 00 ==> 读取到的状态
CD 62 ==> 校验码
# OFF状态
[2024-07-14 23:27:33.453]# SEND HEX>
08 05 00 04 00 00 8C 92
[2024-07-14 23:27:33.596]# RECV HEX>
08 05 00 04 00 00 8C 92
10H号功能码:写多个保持寄存器
10H号功能码报文组成结构
格式 | 站地址 | 功能码 | 寄存器地址 | 寄存器数量 | 数据字节数 | 数据 | CRC校验 |
字节占用 | 1 | 1 | 2 | 2 | 1 | 4 | 2 |
报文举例 | 12 | 10 | 00 00 | 00 02 | 04 | 04 57 08 AE | 9E B7 |
10H号功能码报文示例
# 报文收发
[2024-07-14 23:32:45.757]# SEND HEX>
12 10 00 00 00 02 04 04 57 08 AE 9E B7
[2024-07-14 23:32:45.910]# RECV HEX>
12 10 00 00 00 02 43 6B
# SEND报文解释
12 ==> 站地址:18
10 ==> 功能码
00 00 ==> 寄存器起始地址
00 02 ==> 寄存器数量
04 ==> 数据字节数
04 57 08 AE ==> 写入数据
04 57 ==> 1111
08 AE ==> 2222
9E B7 ==> CRC校验码
# RECV报文解释
12 ==> 站地址:8
10 ==> 功能码
00 00 ==> 寄存器起始位置
00 02 => 寄存器数量
43 6B ==> 校验码