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 |
举例响应格式如下,返回写入的地址和数量
列 | 数据(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) { |