Modbus协议规范

MODBUS协议目前最常用的是Modbus RTU和Modbus TCP,分别对应串口和网络。二者核心是相同的,仅封包格式略有区别。以下以Modbus RTU 和 Modbus TCP为例总结。

协议中所有的16bit及以上数值均按大端字节序存储

通用帧定义

本节主要讲的是Modbus RTU和Modbus TCP报文中相同的通用帧部分,将此部分称为通用Modbus帧,即PDU(协议数据单元),这部分仅包含2部分数据,1个字节的功能码和若干数据。

功能码 数据域
1字节 若干字节

而数据域部分,各个类型的数据含义和读写状态如下,

类别 数据含义 读写状态 PLC地址
离散输入 位变量 只读 10001~19999
线圈 位变量 读写 00001~09999
输入寄存器 16bit变量 只读 30001~39999
保持寄存器 16bit变量 读写 40001~49999

此处地址为PLC地址,从1开始,Modbus协议地址从0开始,因此PLC地址转换为协议地址最终在协议传输的时候,转换公式为 (ADDR - 1)%10000

根据功能码不同,其请求和响应而数据格式不相同,以下分别以功能码为例详解。

01H 读线圈状态

读从机的线圈的ON/OFF状态,可以读单个或者多个。

举例请求格式及数据说明

数据(16进制) 备注
功能码 01 固定01
起始地址高位 00 起始地址11
起始地址低位 0A /
线圈数量高位 00 读取数量13
线圈数量低位 0D /

举例响应格式及数据说明

数据(16进制) 备注
功能码 01 固定01
字节数 02 共2个数据,所以只有数据1和数据2
数据1 0A 数据1
数据2 11 数据2

需要注意的是位操作,也就是说返回值的数据是用标志位表示的,每一个比特表示8个位,假设上面其实读取的地址为P,那么各个位表示的地址如下

数据 bit8 bit7 bit6 bit5 bit4 bit3 bit2 bit1
Data1 P+7 P+6 P+5 P+4 P+3 P+2 P+1 P
Data2 N/A N/A N/A N/A P+11 P+10 P+9 P+8

如果读取的数量不是8的倍数,会导致最后一个数据不够8位,因此会对这个字节高位不足的部分补0,如上表表示的是读取P地址的数据,读取数量是12,那么第二个数据位的高4位就没有数据,最终会用0补齐。

02H 读离散输入状态

读离散输入寄存寄存器,位操作,可以读一个或多个,协议类似 01H 读线圈状态

03H 读保持寄存器

读保持寄存器,可读单个或多个。举例请求格式如下

数据(16进制) 备注
功能码 03 固定03
起始地址高位 00 起始地址40001
起始地址低位 00 /
寄存器数量高位 00 读取2个
寄存器数量低位 02 /

响应格式如下

数据(16进制) 备注
功能码 03 固定03
后续数据字节长度 04 后续4个字节
数据1高位 00 数据1为0x0006
数据1低位 06 /
数据2高位 00 数据2为0x0005
数据2低位 05 /

04H 读输入寄存器

读输入寄存器,可读单个或者多个,协议类似 03H 读保持寄存器

05H 写单个线圈

写单个线圈,只能写一个,写0xff00表示设置线圈状态为ON,写0x0000表示写线圈状态为OFF。

比如写如ON数据的举例如下

数据(16进制) 备注
功能码 05 固定05
线圈起始地址高位 00 写线圈173
线圈起始地址低位 AC /
写入数据高位 FF 写入ON
写入数据低位 00 /

响应直接回显,格式同请求格式。

06H 写单个保持寄存器

写单个保持寄存器,只能写一个

数据(16进制) 备注
功能码 06 固定06
寄存器起始地址高位 00 写入40002
寄存器起始地址低位 01 /
写入数据高位 00 写入值0x0003
写入数据低位 03 /

响应直接回显,格式同请求格式。

0FH 写多个线圈

写多个线圈寄存器,若数据区的某位值为1,表示请求设置该线圈状态为ON,为0表示设置为OFF,其写入值的BIT格式同 01H 读线圈状态 定义。

举例写入请求格式如下

数据(16进制) 备注
功能码 0F 固定0F
线圈起始地址高位 00 写入地址20
线圈起始地址低位 13 /
线圈数量高位 00 写入数量10
线圈数量低位 0A /
写入数据长度 02 2个字节
写入数据1 CD 数据1
写入数据2 01 数据2

写入数据 0xCD01 对应的线圈如下

数据位值 : 1  1  0  0  1  1  0  1   0  0  0  0  0  0  0  1
对应线圈 : 27 26 25 24 23 22 21 20 - - - - - - 29 28

举例响应格式如下,返回写入的地址和数量

数据(16进制) 备注
功能码 0F 固定0F
线圈起始地址高位 00 地址20
线圈起始地址低位 13 /
线圈数量高位 00 数量10
线圈数量低位 0A /

10H 写多个保持寄存器

写多个寄存器,指定寄存器个数,寄存器值按short类型排列,举例写入格式如下

数据(16进制) 备注
功能码 10
寄存器起始地址高位 00
寄存器起始地址低位 01
寄存器数量高位 00 寄存器数量
寄存器数量低位 02
数据长度 4 数据长度是按字节计算,寄存器二倍
数据值 XX XX XX XX 数据

举例响应格式如下,返回写入的地址和数量

数据(16进制) 备注
功能码 10
寄存器起始地址高位 00 对应请求
寄存器起始地址低位 01
寄存器数据高位 00 对应请求
寄存器数量低位 02

除了上面常用功能码外,还有很多,特别的是当上面的功能码在对端返回错误的时候,功能码会将高位置1,也就是0x01对应0x81,即当前功能码加上0x80,以此代表本次请求错误

应用帧定义

在前面通用帧定义的基础上再做不同的封装,实现了RTU和TCP两种,其主要封包格式如下:

RTU包格式定义如下

起始位 设备地址 PDU CRC校验 结束符
3.5字符时间 从机地址1字节 N字节 2字节 3.5字符时间

TCP封包因为TCP本身是可靠协议,因此不需要CRC校验,但是却单独增加了一个MBAP头,完整结构如下

长度 说明 主机 从机
事务标识符 2字节 用于返回校验 主机生成 复制返回
协议标识符 2字节 置为0 主机生成 复制返回
长度 2字节 以下字节长度 主机生成 从机生成
单元标识符 1字节 从站地址 主机生成 复制返回
数据PDU N字节 上面的PDU格式 主机生成 从机生成

Modbus TCP模式是主机连接从机的模式

CRC校验算法

RTU使用CRC16校验算法,因为CRC16算法很多,此处直接给出其C代码算法

WORD CRC16 (const BYTE *nData, WORD wLength) {
static const WORD wCRCTable[] = {
0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };

BYTE nTemp;
WORD wCRCWord = 0xFFFF;

while (wLength--){
nTemp = *nData++ ^ wCRCWord;
wCRCWord >>= 8;
wCRCWord ^= wCRCTable[nTemp];
}
return wCRCWord;
}
最近的文章

Tensorflow常见错误

最近重新安装了Tensorflow的训练环境,并不是一帆风顺,比起PyTorch的安装,Tensorflow还是问题多多,比如字符编码、PyCocoTools等,在此记录下安装和使用过程中的常见问题,但并不对安装过程进行详细记录 无法加载Tensorflow在使用GPU版本情况下,目前我直接pip安 …

技术 继续阅读
更早的文章

浏览器迷之UA

不光是网页前台,网页后台经常也会出于统计等目的进行用户信息进行统计,而用户的信息一般只可能标记在浏览器的身份信息里面,也就是User-Agent,但是当你仔细的查看各个浏览器的UA(User-Agent)时候,你会惊奇的发现其如此的混乱,这要从浏览器的发展历史说起。 浏览器UA起源在万维网兴起之后, …

闲言 继续阅读