Modbus协议,首先从字面理解它包括
Mod和
Bus两部分,首先它是一种bus,即总线协议,和
I2C、
SPI类似,总线就意味着有主机,有从机,这些设备在同一条总线上。
Modbus支持单主机,多个从机,最多支持247个从机设备。关于Mod,因为这种协议最早被用在PLC控制器中,准确的说是Modicon公司的PLC控制器,这也是Mod-Bus名称的由来。
后来Modicon被施耐德(Schneider)收购,Modbus协议广泛应用在工业控制器、HMI和传感器上,逐渐被其他厂商接受,成为一种主流的通讯协议,用于和外围设备进行通讯。
报文模型
一个报文就是一帧数据:一串完整的指令数据。
Modbus采用 "Big Endian"编码方式,先发送高位字节,然后是低位字节。
设备地址 | 功能代码 | 数据 | 校验 |
1* 8bit | 1* 8bit | N * 8bit | 2 * 8bit |
功能代码
功能码 | 功能码名称 | 可操作元件类型 | 注释 |
0x01 | 读线圈 | Y、X、M、SM、S、T、C | 读一个或多个位 |
0x02 | 读离散量输入 | X | 读一个或多个位 |
0x03 | 读寄存器 | D、SD、Z、T、C | 读一个或多个字 |
0x05 | 写单个线圈 | Y、M、SM、S、T、C | 写一个位 |
0x06、0x41 | 写一个寄存器 | D、SD、Z、T、C | 写一个字 |
0x0F | 写多个线圈 | Y、M、SM、S、T、C | 写多个位 |
0x10、0x43 | 写多个寄存器 | D、SD、Z、T、C | 写多个字 |
读线圈 (0x01 0x02)
- 请求帧
设备地址 | 功能码(01H) | 起始地址 | 读取个数 | 校验码 |
H | L | H | L |
- 响应帧
如果读取的地址不是8的倍数,剩下的位由0填充(由高位开始填充)。
设备地址 | 功能码(01H) | 读取的个数n (字节数) | 读取的数据No.1 | ...... | 读取的数据No.n | 校验码 |
3. 举例
请求 | 响应 |
字段名 | 16进制 | 字段名 | 16进制 |
功能码 | 01 | 功能码 | 01 |
起始地址 Hi | 00 | 字节计数 | 03 |
起始地址 Lo | 13 | 输出状态27~20 | CD |
输出数量 Hi | 00 | 输出状态35~28 | 6B |
输出数量 Lo | 13 | 输出状态38~36 | 05 |
输出27~20的状态表示为十六进制字节值CD,或二进制1100 1101.输出27是这个字节的MSB(最高有效位),输出20是LSB(最低有效位)
Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
在最后的数据字节中,将输出38~36的状态表示为十六进制字节值05,或二进制 0000 0101。用零填充5个剩余高位。
Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
| | | | | 38 | 37 | 36 |
读寄存器 (0x03 0x04)
- 请求帧
设备地址 | 功能码(03H) | 起始地址 | 读取个数 (N) | 校验码 |
H | L | H | L |
- 响应帧
设备地址 | 功能码(03H) | 读取的个数2*N | 读取的数据No.1 | ...... | 读取的数据No.n | 校验码 |
H | L | H | L |
3. 举例 请求读取寄存器108~110的示例。
请求 | 响应 |
字段名 | 16进制 | 字段名 | 16进制 |
功能码 | 03 | 功能码 | 03 |
起始地址 Hi | 00 | 字节计数 | 06 |
起始地址 Lo | 6B | 寄存器值 Hi(108) | 02 |
寄存器数量 Hi | 00 | 寄存器值 Lo(108) | 2B |
寄存器数量 Lo | 03 | 寄存器数量 Hi (109) | 00 |
| | 寄存器值 Lo(109) | 00 |
寄存器值 Hi(110) | 00 |
寄存器值 Lo(110) | 64 |
结果为:
寄存器 | 内容值 (十六进制) | 内容值 (十进制) |
寄存器 108 | 02 2B | 555 |
寄存器 109 | 00 00 | 0 |
寄存器 110 | 00 64 | 100 |
写单个线圈 (0x05)
- 请求帧
设备地址 | 功能码(05H) | 起始地址 | 写入的值 | 校验码 |
H | L | H | L |
注:写入的元件的值为0xFF00(ON,1)或者(0x0000)(OFF,0)。
- 响应帧
响应帧是请求帧的重复。
设备地址 | 功能码(05H) | 起始地址 | 写入的值 | 校验码 |
H | L | H | L |
3. 举例
请求 | 响应 |
字段名 | 16进制 | 字段名 | 16进制 |
功能码 | 05 | 功能码 | 05 |
起始地址 Hi | 00 | 起始地址 Hi | 00 |
起始地址 Lo | AC | 起始地址 Lo | AC |
输出值 Hi | FF | 输出值 Hi | FF |
输出值 Lo | 00 | 输出值 Lo | 00 |
写单个寄存器 (0x06 0x41)
- 请求帧
设备地址 | 功能码(06H) | 起始地址 | 写入的值 | 校验码 |
H | L | H | L |
- 响应帧
响应帧是请求帧的重复。
设备地址 | 功能码(06H) | 起始地址 | 写入的值 | 校验码 |
H | L | H | L |
3. 举例 将一个十六进制 00 03写入寄存器2的示例
请求 | 响应 |
字段名 | 16进制 | 字段名 | 16进制 |
功能码 | 06 | 功能码 | 06 |
起始地址 Hi | 00 | 起始地址 Hi | 00 |
起始地址 Lo | 02 | 起始地址 Lo | 02 |
寄存器值 Hi | 00 | 输出值 Hi | 00 |
寄存器值 Lo | 03 | 输出值 Lo | 03 |
写多个线圈(0x0F)
- 请求帧
设备地址 | 功能码(0FH) | 起始地址 | 元件个数 | 字节数 (N) | 写入的值No.1 | ...... | 写入的值No.N | 校验码 |
H | L | H | L |
N = 元件个数/8,如果余数不等于0,那么N=N+1。
- 响应帧
设备地址 | 功能码(0FH) | 起始地址 | 元件个数 | 校验码 |
H | L | H | L |
3. 举例
从线圈20开始写10个线圈的示例。请求的数据内容为2个字节:十六进制CD 01(二进制 1100 1101 0000 0001)。二进制安如下对应于输出。
位: | 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进制 | 字段名 | 16进制 |
功能码 | 0F | 功能码 | 0F |
起始地址 Hi | 00 | 起始地址 Hi | 00 |
起始地址 Lo | 13 | 起始地址 Lo | 13 |
输出数量 Hi | 00 | 输出数量 Hi | 00 |
输出数量 Lo | 0A | 输出数量 Lo | 0A |
字节计数 | 02 | | |
输出值 Hi | CD |
输出值 Lo | 01 |
写多个寄存器(0x10 0x43)
- 请求帧
设备地址 | 功能码(10H) | 起始地址 | 元件个数 (N) | 字节数 (2*N) | 写入的值No.1 | ...... | 写入的值No.N | 校验码 |
H | L | H | L | H | L | H | L |
- 响应帧
设备地址 | 功能码(10H) | 起始地址 | 元件个数 | 校验码 |
H | L | H | L |
3. 举例
将十六进制00 0A和01 02写入以第2个寄存器开始的两个寄存器的示例。
请求 | 响应 |
字段名 | 16进制 | 字段名 | 16进制 |
功能码 | 10 | 功能码 | 10 |
起始地址 Hi | 00 | 起始地址 Hi | 00 |
起始地址 Lo | 01 | 起始地址 Lo | 01 |
寄存器数量 Hi | 00 | 寄存器数量 Hi | 00 |
寄存器数量 Lo | 02 | 寄存器数量 Lo | 02 |
字节计数 | 04 | | |
寄存器值 Hi | 00 |
寄存器值 Lo | 0A |
寄存器值 Hi | 01 |
寄存器值 Lo | 02 |
错误代码