一、简介
ModBus是Modicon公司为其PLC通讯而开发的一种通讯协议。如今Modicon公司已经被施耐德收购成为了施耐德旗下品牌。从1979年问世至今,已经成为工业通讯领域的业界标准。
ModBus具有两种串行传输模式,ASCII 和 RTU。它们定义了数据如何打包、解码的不同方式。支持 Modbus 协议的设备一般都支持 RTU 格式。通信双方必须同时支持上述模式中的一种。
二、寄存器类型
MODBUS寄存器分类
寄存器种类 | 读写状态 | 数据类型 | 功能码 | PLC地址 |
---|---|---|---|---|
线圈寄存器 | 读/写 | 位(bit) | 01H(读单/多个位); 05H(写单个位); 0FH(写多个位) | 00001-09999 |
离散输入寄存器 | 只读 | 位(bit) | 02H (读单/多个位) | 10001-19999 |
保持寄存器 | 读/写 | 字(byte) | 03H(读); 06H(写单个字节); 0FH(写多个字节) | 30001-39999 |
输入寄存器 | 只读 | 字(byte) | 04H (读单/多个字) | 40001-49999 |
线圈寄存器:实际上可以表示一个开关量,线圈操作位(bit)一个bit对应一个开关信号,即(0/false,1/true),每个byte字节就能代表8个的位的开关信号,线圈寄存器支持读和写,Modbus的功能码又能对线圈的单个或多个进行一个读取写入操作,其实就是在操作字节的位。实对应上面的功能码也就是:0x01 0x05 0x0f
离散输入寄存器:离散输入寄存器就相当于线圈寄存器的只读模式,功能跟上面基本一致,除了不能写入。所以功能码也简单就一个读的 0x02
保持寄存器:保持寄存器是对字节进行的操作,每两个字节对应一个寄存器,支持读取和写入。功能码有对应的三个:0x03 0x06 0x10
输入寄存器:,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器地址是占据两个byte的空间,也就是16个位。对应的功能码也就一个 0x04
言外之话:
当需要对保持寄存器操作时,不同的数据类型占据的字节长度其实是不一致的,一个寄存器地址占两个字节16位代表的只是一个单精度的数据。如C#的数据类型占据的长度如下
C# 数据类型和字节长度
所以在读取写入保持寄存器的时候不同类型需要读取不同长度的字节才能拿到对应类型的值,写入的时候同理。如(int类型读取2个数量为int的时候需要乘以2,也就是读取4个寄存器才能拿到两个int类型的数据)
三、MODBUS常用功能码
功能码 | 名称 | 操作数据类型 | 作用描述 | PLC地址 |
---|---|---|---|---|
01H | 读线圈寄存器 | 位(bit) | 获得一组开关线圈的当前状态(ON/OFF ) | 00001-09999 |
02H | 读离散输入寄存器 | 位(bit) | 获得一组开关线圈的当前状态(ON/OFF ) | 10001-19999 |
03H | 读保持寄存器 | 字(byte) | 在一个或多个保持寄存器中取得当前的二进制值 | 30001-39999 |
04H | 读输入寄存器 | 字(byte) | 在一个或多个输入寄存器中取得当前的二进制值 | 40001-49999 |
05H | 写单个线圈寄存器 | 位(bit) | 设置一个单独的线圈状态(ON/OFF ) | 00001-09999 |
06H | 写单个保持寄存器 | 字(byte) | 写单个保持寄存器,将两个字节写入到寄存器 | 40001-49999 |
0FH | 写多个线圈寄存器 | 位(bit) | 写多个线圈寄存器,可以设置多个bit开关状态 | 00001-09999 |
10H | 写多个保持寄存器 | 字(byte) | 写多个保持寄存器,将多个字节写入到寄存器 | 40001-49999 |
MODBUS通讯协议格式
读线圈寄存器——01H
从站发送读取格式:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 读取数量高位 | 读取数量低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x01 | 0x00 | 0x00 | 0x00 | 0x02 | BD | CB |
主站返回读取格式:
从站地址 | 功能码 | 返回字节数 | data1 | CRC高位 | CRC低位 |
---|---|---|---|---|---|
0x01 | 0x01 | 0x01 | 0x00 | 51 | 88 |
解读:
发送格式:
从站地址:01
功能码:01
起始地址: 00 00
读取数量: 00 02
CRC校验:BD CB
接收格式:
从站地址:01
功能码:01
返回字节数: 01
Data数据: 00(一个byte有8个bit,读取两个bit返回会补成字节用一个byte返回读取两个bit就行)
CRC校验:BD CB
读离散输入状态——02H
从站发送读取格式:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 读取数量高位 | 读取数量低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x02 | 0x00 | 0x00 | 0x00 | 0x02 | F9 | CB |
主站返回读取格式:
从站地址 | 功能码 | 返回字节数 | data1 | CRC高位 | CRC低位 |
---|---|---|---|---|---|
0x01 | 0x02 | 0x01 | 0x00 | A1 | 88 |
读保持寄存器——03H
从站发送读取格式:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 读取数量高位 | 读取数量低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x03 | 0x00 | 0x00 | 0x00 | 0x02 | C4 | CB |
主站返回读取格式:
从站地址 | 功能码 | 返回字节数 | data1 | data2 | data3 | data4 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|---|
0x01 | 0x03 | 0x04 | 0x00 | 0x0A | 0x00 | 0x00 | 0A | 31 |
解析读取返回:
为啥读取是读取两个地址数量会返回四个byte字节?因为在保持寄存器里面两个字节才对应一个寄存器,你读取两个寄存器地址数量就需要四个字节来表示。
注意:在解析字节的时候数据的高低位顺序倒序后才能表示一个数据
读输入寄存器——04H
同读取保存寄存器一样的格式,同上
写单个线圈——05H
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | data1 | data2 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x05 | 0x00 | 0x00 | 0xFF | 0x00 | 8C | 3A |
主站返回读取格式:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 写入数据高位 | 写入数据低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x05 | 0x00 | 0x00 | 0xFF | 0x00 | 8C | 3A |
解析:
写入无误的话返回同发送指令一样;通断标志为FF00H表示写ON,0000H表示写OFF,
写单个保持寄存器——06H
将地址0的保存寄存器的数据设置为10(单精度)
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | data1 | data2 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x06 | 0x00 | 0x00 | 0x00 | 0x0A | 09 | CD |
主站返回读取格式:
响应:同发送指令;
写多个线圈——0FH
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 写入数量高位 | 写入数据低位 | 字节长度 | data1 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|---|---|
0x01 | 0x0F | 0x00 | 0x00 | 0x00 | 0x02 | 0x01 | 0x03 | 9E | 96 |
主站返回读取格式:
响应:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 写入数量高位 | 写入数据低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x0F | 0x00 | 0x00 | 0x00 | 0x02 | 9E | 96 |
返回数据格式不返回Data数据
详情:写入数量为2的线圈,其中data1数据0x03代表0000 0011 将连续两个线圈置为ON
写多个保持寄存器——10H
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 写入数量高位 | 写入数据低位 | 字节长度 | data1 | data2 | data3 | data4 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0x01 | 0x10 | 0x00 | 0x00 | 0x00 | 0x02 | 0x04 | 0x0C | 0x02 | 0x12 | 0x45 | 9C | 6C |
主站返回读取格式:
响应:
从站地址 | 功能码 | 起始地址高位 | 起始地址低位 | 写入数量高位 | 写入数据低位 | CRC高位 | CRC低位 |
---|---|---|---|---|---|---|---|
0x01 | 0x10 | 0x00 | 0x00 | 0x00 | 0x02 | 41 | CB |
返回数据格式不返回Data数据
详情:写入数量为2的线圈,其中data1,data2,data3,data4数据两个代表一个单精度数据
0C 02表示一个3074 ,12 45代表一个4677
MODBUS TCP通讯协议格式
ModbusTCP的数据帧可分为两部分:MBAP+PDU。
| 事务处理标识| 协议标识 | 字节长度 | 单元标识符 | 功能码 |起始地址H | 起始地址L |数量H | 数量L
事务处理标识 | 协议标识 | 字节长度 | 单元标识符 | 功能码 | 起始地址H | 起始地址L | 数量H | 数量L |
---|---|---|---|---|---|---|---|---|
2字节 | 2字节 | 2字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 |
事务处理标识: 可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符: 00 00表示ModbusTCP协议。
字节长度: 表示接下来的数据长度,单位为字节
单元标识符: 可以理解为设备地址。
TCP跟RTU的协议格式其实基本上是一致的,只是TCP增加了报文头MBAP,取消了RTU的从站地址跟CRC校验