1、modbus协议简介
modbus是工业现场总线通信协议中应用较为成熟稳定的协议。理解起来也比较简单。modbus数据传输采用大端模式
1.1功能码简要说明
modbus定义了不同的功能码来操作不同类型的数据。具体如下:
序号 | 功能码 | 名称 | 读写 | 寄存器数据类型 | 说明 |
1 | 0x01 | 读线圈寄存器 | R | bit | 读输出开关量,每个bit代表一个信号。类比mcu的通用输出口 |
2 | 0x02 | 读离散输入寄存器 | R | bit | 读输入开关量,每个bit代表一个信号。类比mcu的通用输入口 |
3 | 0x03 | 读保持寄存器 | R | uint16 | 读数据,每个数据是16位 |
4 | 0x04 | 读输入寄存器 | R | uint16 | 读输入数据,每个数据是16位,类比模拟量输入信号 |
5 | 0x05 | 写单个线圈寄存器 | W | bit | 写输出开关量,每个bit代表一个信号。类比mcu的通用输出口 |
6 | 0x06 | 写单个保持寄存器 | W | uint16 | 写数据,每个数据是16位 |
7 | 0x0f | 写多个线圈寄存器 | W | bit | 写输出开关量,每个bit代表一个信号。类比mcu的通用输出口 |
8 | 0x10 | 写多个保持寄存器 | W | uint16 | 写数据,每个数据是16位 |
1.2线圈寄存器读写操作(读输出口状态,设置输出口状态)
1.2.1 读单个线圈寄存器(功能码0x01)
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 数据长度高字节 | 数据长度低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x01 | 0x00 | 0x00 | 0x00 | 0x01 | 0xFD | 0xCA |
数据地址:开始读取的首地址
数据长度:读取的长度,注意由于线圈寄存器的最小单位是bit,所以改长度对应的读取的bit数量,上面的例子中读取的是地址第一个线圈寄存器的值
如果线圈寄存器的值为1,则返回如下数据:
字节 | 1 | 2 | 3 | 4 | 5 | 5 |
说明 | 通信地址 | 功能码 | 字节数 | 数据0 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x01 | 0x01 | 0x01 | 0x90 | 0x48 |
1.2.2写单个线圈寄存器(功能码0x05)
将单个线圈寄存器写1,注意写1要发送0xff 0x00
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 数据长度高字节 | 数据长度低字节 | CRC低字节 | CRC高字节 |
寄存器置1 | 0x01 | 0x05 | 0x00 | 0x00 | 0xFF | 0x00 | 0x8C | 0x3A |
返回数据为发送的数据:
01 05 00 00 FF 00 8C 3A
将单个线圈寄存器写0,注意写0要发送0x00 0x00
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 数据长度高字节 | 数据长度低字节 | CRC低字节 | CRC高字节 |
寄存器置0 | 0x01 | 0x05 | 0x00 | 0x00 | 0x00 | 0x00 | 0xCD | 0xCA |
返回的数据为发送的数据:
01 05 00 00 00 00 CD CA
1.2.3读多个线圈寄存器(功能码0x01)
读取两个线圈寄存器,假如线圈寄存器1值为1,线圈寄存器2值为1则,
读
01 01 00 00 00 02 BD CB
数据返回
01 01 01 03 11 89
返回的值为3,3的2进制表述为11b
1.2.4写多个线圈寄存器(功能码0x0F)
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 线圈数量高字节 | 线圈数量低字节 | 字节数量 | 数据 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x0F | 0x00 | 0x00 | 0x00 | 0x02 | 0x01 | 0x03 | 0x9E | 0x96 |
上面的例子是将第一和第二个线圈写入1。0x03的二进制为11b。
返回的数据格式如下:
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 线圈数量高字节 | 线圈数量低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x0F | 0x00 | 0x00 | 0x00 | 0x02 | 0xD4 | 0x0A |
注意,当写入的线圈数量不大于8则字节数量为1,数据为1个字节。如果长度超过8,则字节数量为2,字节8对应1-8线圈的值,字节9对应线圈9-16的值,字节10和11为crc
1.3读输入离散输入寄存器(功能码0x02)
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 读取数量高字节 | 读取数量低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x02 | 0x00 | 0x00 | 0x00 | 0x04 | 0xFD | 0xCA |
上面读取了4个离散输入寄存器的值,假如4个寄存器的值为0101b,则返回数据如下:
字节 | 1 | 2 | 3 | 4 | 7 | 8 |
说明 | 通信地址 | 功能码 | 字节数量 | 数据 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x02 | 0x01 | 0x05 | 0x61 | 0x8b |
其中的每个bit代表io口的输入值。
1.4读取输入寄存器(功能码0x04)
输入寄存器读取的是类似模拟量的数据,每个数据占16bit。而离散输入寄存器每个bit占1个bit,这是这两者的区别。
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 读取数量高字节 | 读取数量低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x04 | 0x00 | 0x00 | 0x00 | 0x01 | 0x90 | 0x0a |
上面读取了1个输入寄存器值,假如寄存器的值为0x1000,从机的返回如下:
字节 | 1 | 2 | 3 | 4 | 7 | 8 | |
说明 | 通信地址 | 功能码 | 字节数量 | 寄存器数据高字节 | 寄存器数据低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x04 | 0x02 | 0x10 | 0x00 | 0xb4 | 0xf0 |
读取4个输入寄存器如下:
01 04 00 00 00 04 F1 C9
返回数据
01 04 08 10 00 10 01 10 02 10 03 F2 90
返回的数据长度:8字节
第一个寄存器值:0x1000
第二个寄存器值:0x1001
第三个寄存器值:0x1002
第四个寄存器值:0x1003
1.5 保持寄存器读写操作
1.5.1读保持寄存器(功能码0x03)
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 寄存器高地址 | 寄存器低地址 | 读取数量高字节 | 读取数量低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x03 | 0x00 | 0x00 | 0x00 | 0x02 | 0xc4 | 0x0b |
上面读取了两个寄存器的值,假如第一个寄存器值0x147b,第二个寄存器值0x3f8e,则从机的返回数据如下:
字节 | 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 |
说明 | 通信地址 | 功能码 | 字节数量 | 第一个寄存器数据高字节 | 第一个寄存器数据低字节 | 第二个寄存器数据高字节 | 第二个寄存器数据低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x03 | 0x04 | 0x14 | 0x7b | 0x3F | 0x8E | 0x1E | 0x4E |
1.5.2写单个保持寄存器(功能码0x06)
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
说明 | 通信地址 | 功能码 | 寄存器高地址 | 寄存器低地址 | 写入值高字节 | 写入值低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x06 | 0x00 | 0x00 | 0x00 | 0x02 | 0x08 | 0x0b |
如果写入成功:则返回发送下去的值
01 06 00 00 00 02 08 0B
这样就成功将第一个寄存器的值写为0x0002
1.5.3写多个保持寄存器(功能码0x10)
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
说明 | 通信地址 | 功能码 | 寄存器高地址 | 寄存器低地址 | 寄存器数量高字节 | 寄存器数量低字节 | 写入字节数 | 第一个寄存器值高字节 | 第一个寄存器值低字节 | 第二个寄存器值高字节 | 第二个寄存器值低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x10 | 0x00 | 0x00 | 0x00 | 0x02 | 0x04 | 0x00 | 0x01 | 0x00 | 0x02 | 0x23 | 0xae |
如果写入正常则从机的返回值如下:
字节 | 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 |
说明 | 通信地址 | 功能码 | 数据高地址 | 数据低地址 | 线圈数量高字节 | 线圈数量低字节 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x10 | 0x00 | 0x00 | 0x00 | 0x02 | 0x41 | 0xc8 |
这样就正常将第一个寄存器写入0x0001,第二个寄存器写入0x0002
1.6 错误反馈
如果发送的命令有问题,则会返回错误帧,如果是crc错误,从机是不会返回任何数据。从机返回错误的格式如下:
字节 | 1 | 2 | 3 | 9 | 10 |
说明 | 地址码 | 功能码 | 错误码 | CRC低字节 | CRC高字节 |
示例 | 0x01 | 0x80+功能码 | 0x00 |
常见错误码
错误码 | 名称 | 说明 |
0x01 | 非法功能码 | 不支持该功能码操作寄存器 |
0x02 | 非法的寄存器地址 | 设备没有该地址 |
0x03 | 非法的数据 | 数据格式不对 |
0x04 | 从机故障 | 从机工作不正常 |
发送:01 06 00 00 01 02 04 00 01 00 02
返回:01 86 03 02 61
错误码为3,说明数据格式错误。上面的数据应该是写多个寄存器0x10,但功能码是写单个寄存器0x06,所有导致错误。
发送:01 16 00 00 01 02 04 00 01 00 02 D3 71
返回:01 96 01 8E 60
错误码为1,说明功能码不对,没有0x16功能码