Modbus协议解析
Modbus协议分类
Modbus是工业现场常用的一种通讯协议,其本质是,利用通讯,读写其他设备的内存数据。
本文仅记录个人常用的Modbus-TCP,Modbus-RTU。顾名思义,这两者的区别之一是传输的物理介质不同。Modbus-TCP主要基于以太网传输,Modbus-RTU主要基于RS485传输。
Modbus通讯模型
Modbus通讯模型主要有两种,Client-Server模型和Master-Slave模型。
Client-Server模型
此模型适用于Modbus-TCP。在TCP通讯中,存在着Client与Server。Client首先发起请求,Server监听Client的请求,并在收到Client的请求后予以回应。TCP是基于连接的通讯,Modbus是基于TCP通讯之上,构建的应用层协议。Client发起Modbus请求,Server予以回应。在通讯层面,仅看做简单的TCP数据收发。而应用层的Modbus具体协议,请看后文。
关于TCP,Client,Server、IP地址,端口号等TCP通讯模型,本文不再赘述,如有疑问,请自行查阅其他资料。
Client-Server模型示意图:
Master-Slave模型
此模型适用于Modbus-RTU。如上文所述,RS485是两根差分线进行半双工通讯,CPU侧是UART外设,即串口。RS485是半双工总线,只有两根差分物理信号线,简洁高效。
RS485的总线拓扑:
此图截选自ADI公司的某款RS485/422驱动芯片的一份数据手册,RS422比RS485多了一对差分线,收发分立,所以可以全双工工作。但RS485由于其布线简单,所以应用场景更为广泛。
RS485的半双工特性,决定了它的通讯拓扑中,只能有一个Master,作为通讯的发起者,而其余设备作为Slave,每个Slave赋予唯一的ID号,借此判断Master是否是与自己对话。简单说,Master只能有一个,一次只能对一个Slave进行对话,同时刻,总线上只能有一个人在说话,不是对自己说的,则不予回复。
Master-Slave模型示意图如下:
模型本质
不管是何种模型,其本质都是数据的交换。发起者获取应答者的数据,或者发起者传递数据到应答者。Modbus协议的核心,就是定义数据交换的格式。由此引入了寄存器、功能码、典表这三个最重要的概念。
寄存器
Slave或者Server所在的设备的内存中的某些变量或区域需要共享给Master或者Client设备,则该变量被称为寄存器。
寄存器分为4类,分别是 Coil Reg,Input Discrete Reg,Hold Reg,Input Reg,它的常规翻译难以简单明了表示其属性,所以下表的描述,可能与常规翻译有出入。
寄存器 | 描述 |
---|---|
Coil Reg | 布尔量寄存器,Master/Client可读可写 |
Input Discrete Reg | 输入型布尔量寄存器,Master/Client只读 |
Hold Reg | 保持寄存器,最小单位16bit,Master/Client可读可写 |
Input Reg | 输入寄存器,最小单位16bit,Master/Client只读 |
功能码
Master/Client发起的每一帧Modbus请求,都包含有功能码。功能码标志此请求的类型,是请求帧中的核心数据。
功能码 | Dec | 中文描述 |
---|---|---|
0x01 | Read Coil Regs | 读取一个或多个 Coil Regs |
0x02 | Read Discrete Input Regs | 读取一个或多个Discrete Input Regs |
0x03 | Read Hold Regs | 读取一个或多个Hold Regs |
0x04 | Read Input Regs | 读取一个或多个 Input Regs |
0x05 | Write Single Coil Reg | 写单个Coil Reg |
0x06 | Write single Hold Reg | 写单个Hold Reg |
0x07 | Read Exception Status | |
0x0F | Write Multiple Coil Regs | 写多个Coil Reg |
0x10 | Write Multiple Hold Regs | 写多个Hold Reg |
0x11 | Export Slave ID | |
0x16 | Mask Write Register | |
0x17 | Write And Read Hold Registers |
功能码0x07\0x11\0x16\0x17本人不曾使用过,限于精力,本文不予描述。
典表
给Slave/Server中的寄存器,协定一个地址,形成地址–>寄存器的映射关系表,就是典表。Master/Client中看到的是地址,此地址对应到Slave/Server中一个具体的寄存器。典表由所有设备共同维护。
关系图如下:
如上图,Master发起写单个HoldReg请求,功能码06,地址是 500,附带了A数组中所有的数据。Slave接到此请求包后,将具体数据,写入到地址码500对应的变量B中。
Modbus-TCP通讯帧结构
Modbus-TCP由于TCP自带CRC校验,所以无需添加任何其他校验
功能码0x01\0x02
读取BOOL型数据。Sequence指的是通讯轮次,每完成一轮 Request-Respond,递增1。NB1指的是读取的BOOL的个数。
Respond的Sequence与Request一致。NB2 = (NB1)/8 + ((NB1 % 8) ? 1 : 0)。一个BOOL在内存中占1字节。在通讯中只需要占用1bit即可。所以Modbus中,将8个BOOL数据压缩成一个字节,余下不足8bit的,依然占用1字节。
N = 8+NB2,Respond[8]后面跟的数据,才是Master/Client读取的数据。
功能码0x03\0x04
Hold Reg 和 Input Reg,最小单位为Word,即16bit。NB1指的是读取Address后,NB1个Word的数据。
Respond中,NB2 = NB1 * 2,即NB2的单位为字节。N = 8 + NB2。
功能码0x05\0x06
写单个Coil和单个Hold Reg帧结构一致
对于写Coil:Data = 0xFF00 或者 0,取决于BOOL量的值
对于写Hold Reg:Data = 要写入的数据,需要注意大小端一致。
正常的Respond与Request的数据一摸一样。
功能码0x0F/0x10
写多个Coils和写多个Hold Regs帧结构一致
对于写Coils,NB2 = (NB1 / 8) + ((NB1 % 8) ? 1 : 0);
对于写Hold Regs,NB2 = NB1 * 2
异常回复
异常的回复帧将功能码最高位置1,并跟随一个异常代码:
异常代码表:
异常码 | Dec |
---|---|
0x01 | MODBUS_EXCEPTION_ILLEGAL_FUNCTION |
0x02 | MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS |
0x03 | MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE |
0x04 | MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE |
0x05 | MODBUS_EXCEPTION_ACKNOWLEDGE |
0x06 | MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY |
0x07 | MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE |
0x08 | MODBUS_EXCEPTION_MEMORY_PARITY |
0x09 | MODBUS_EXCEPTION_NOT_DEFINED |
0x0a | MODBUS_EXCEPTION_GATEWAY_PATH |
0x0b | MODBUS_EXCEPTION_GATEWAY_TARGET |
Modbus-RTU通讯帧结构
Modbus-RTU帧结构与Modbus-TCP相比,典表处理方式一致,帧首排列不一致,帧尾多了CRC校验。
读取Slave寄存器
功能码0x01\0x02\0x03\0x04,帧结构都如下:
功能码 | NB2计算 |
---|---|
0x01、0x02 | (NB1 / 8) + ((NB1 % 8) ? 1 : 0); |
0x03、0x04 | NB1 * 2 |
写单个Coil、Hold Reg
帧结构:
功能码 | data |
---|---|
0x05 | (Coil) ? 0xFF00 , 0 |
0x06 | 需要写入的数据 |
写多个Coils、Hold Regs
帧结构:
功能码 | NB2计算 |
---|---|
0x0F | (NB1 / 8) + ((NB1 % 8) ? 1 : 0); |
0x10 | NB1 * 2 |