一、modbus-RTU协议整理
1.1 RS-485串口通信
modbus-RTU一般运行于RS-485串口通信中。所以先简单提下这部分内容。
串口的设计是传输数据是通过高低电平,高电平0,低电平1。一般高电平5伏左右,低电平0v左右。在传输中受到电磁干扰,电压不稳定,就会出现偏差,所以通信距离一般都在1米之内。两根线一根接收数据,一根发送数据,支持全双工通信,但限于点对点。
RS-232则是把高低电平做了转换,并加入0v地线,高电平是正电压,且高达12v左右,低电压是负电压,-12v左右。电压差加大后,提高了抗干扰能力。通信距离可以达到15米。除地线外,两根线一根接收数据,一根发送数据,支持全双工通信,但限于点对点。
RS-485是RS-232的升级版,使用差分信号传输,只需要两根线。A线电平比B线高是0,B线电平比A线高是1,AB线绕成双绞线,即使同时受到电磁干扰时电压产生偏差,也不会对差值产生影响,所以RS-485 最大的通信距离能达到1200米。一对差分信号线,同一时刻,要么在发送数据,要么在接收数据,所以标准RS-485一般是半双工的,一般使用主从模式。使用标准485芯片一个主机最多可以挂32个从机,高负载芯片可以挂载更多从机。传输中,要采用双绞线保持信号稳定。
主从接线(注意红蓝线在现实中不是独立的,是双绞线):
具体介绍可以参见【物联网】看这篇就够了!串口通信、RS232、RS485最本质的区别!
1.2 modbus-RTU通信模式
Modbus-RTU使用单主站的主/从通信模式,从站的地址范围为1-247。 在Modbus-RTU通信中,主机向总线发送指令帧后,地址匹配的从机将执行以下流程:
- 校验与解析:验证帧的CRC校验码,若正确则解析功能码及数据;
- 任务执行:根据功能码要求执行操作(如读取寄存器或控制设备);
- 响应反馈:将执行结果封装为响应帧(含自身地址码、功能码、结果数据及CRC校验码)发回主机;
- 错误处理:若CRC校验失败,从机丢弃该帧且不响应,保持静默。
Modbus-RTU 采用严格的主从式半双工通信:
- 主站轮询控制:仅主站可发起请求,从站无权主动发送数据;
- 单帧传输原则:总线上任一时刻仅允许一帧数据传输(主站请求 或 从站响应);
- 请求-响应机制:
- 主站向指定从站发送指令帧 → 对应从站处理并返回响应帧;
- 其他从站处于监听静默状态,不占用总线;
- 总线空闲规则:无主站请求时,总线无通信流量,直至主站发起下一轮询。
Modbus-RTU 帧间隔规则
- 帧起始界定:消息必须以 ≥3.5个字符时间(Tchar)的静默间隔 开始,标识新帧起始。
- 帧结束界定:帧内连续字符间隔 ≤1.5个Tchar ;若帧间静默间隔 <3.5个Tchar ,接收端会将其合并为同一帧,导致CRC校验失败或指令解析错误。
Tchar=1个字符的位数/波特率(bps)= 11 bit / Baud Rate
字符位构成:
- 1起始位 + 8数据位 + 1校验位(偶/奇校验) + 1停止位 = 11位
- 注:若使用2停止位,则按12位计算(较少见)
| 波特率(bps) | 1个Tchar(ms) | 3.5个Tchar(ms) |
|---|---|---|
| 9600 | 119600≈1.146960011≈1.146 | 3.5×1.146≈4.013.5×1.146≈4.01 |
| 19200 | 1119200≈0.5731920011≈0.573 | 3.5×0.573≈2.003.5×0.573≈2.00 |
| 115200 | 11115200≈0.09511520011≈0.095 | 3.5×0.095≈0.333.5×0.095≈0.33 |
1.3 Modbus-RTU 报文格式
| 从机地址 | 功能码 | 数据区 | CRC校验 |
|---|---|---|---|
1字节(8位)0x01-0xF7:从机唯一地址 | 1字节(8位) 最高位=1表示异常响应(如 0x81=读线圈异常) | n字节(8*n位) 内容由功能码决定:读操作:含起始地址+数据长度 写操作:含地址+写入值 响应:含执行结果数据 | 2字节(16位) 循环冗余校验码,覆盖地址域+功能码+数据区 接收方校验失败时丢弃报文且不响应 |
Modbus-RTU协议功能码表
| 功能码 | 名称 | 作用 |
|---|---|---|
| 0x01 | 读取线圈状态 | 取得一组逻辑线圈的当前状态(NO/OFF) |
| 0x02 | 读取离散输入状态 | 取得一组开关输入的当前状态(NO/OFF) |
| 0x03 | 读取保持寄存器 | 在一个或多个保持寄存器中取得当前的二进制数值 |
| 0x04 | 读取输入寄存器 | 在一个或多个输入寄存器中取得当前的二进制数值 |
| 0x05 | 强置(写)单线圈 | 强置一个逻辑线圈的通断状态 |
| 0x06 | 预置(写)单寄存器 | 把具体二进制数值装入一个保持寄存器 |
| 0x07 | 读取异常状态 | 取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适用于迅速读取状态 |
| 0x08 | 回送诊断校验 | 把诊断校验报文报送给从机,以对通信处理进行评鉴 |
| 0x09 | 编程(只用于484) | 使主机模拟编程器作用修改PC从机逻辑 |
| 0x0A | 控询(只用于484) | 可使主机与一个正在执行长程序任务的从机通信,探询该从机是否已完成其操作任务,仅在含有功能码0x09的报文发送后,本功能码才发送 |
| 0x0B | 读取事件记录 | 可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应当产生通信错误时 |
| 0x0C | 读取通信事件记录 | 可使主机检索每台从机的Modbus事务处理通信事件记录,如果某项事务处理完成,记录会给出有关错误 |
| 0x0D | 编程(184/384/484/584) | 使主机模拟编程器作用修改PC从机逻辑 |
| 0x0E | 控询(184/384/484/584) | 可使主机与正在执行任务的从机通信,定期探询该从机是否已完成其操作任务,仅在含有功能码0x0D的报文发送后,本功能码才发送 |
| 0x0F | 强置(写)多线圈 | 强置一串连续逻辑线圈的通断状态 |
| 0x10 | 预置(写)多寄存器 | 把具体的二进制数值装入一串连续的保持寄存器 |
| 0x11 | 报告从机标识 | 可使主机判断编址从机的类型及该从机运行指示灯的状态 |
| 0x12 | (884和MICRO84) | 可使主机模拟编程功能,修改PC状态逻辑 |
| 0x13 | 重置通信链路 | 发生非可修改错误后,使从机复位于已知状态,可重置顺序字节 |
| 0x14 | 读取通用参数(584L) | 显示扩展存储器文件中的数据信息 |
| 0x15 | 写入通用参数(584L) | 把通用参数写入或修改扩展存储器文件 |
| 0x16-0x40 | 保留 | 作扩展功能备用 |
| 0x41-0x48 | 保留 | 留作用户功能的扩展编码 |
| 0x49-0x77 | 非法功能 | |
| 0x78-0x7F | 保留 | 留作内部使用 |
| 0x80-0xFF | 保留 | 用于异常应答 |
常见协议格式示例:
1.读取线圈状态、读取离散输入状态(功能码:0x01、0x02)
主机发送报文如下(大端):
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 线圈状态数量(高) | 线圈状态数量(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x11 | 0x01 | 0x00 | 0x13 | 0x00 | 0x25 | 0xF9 | 0xC8 |
0x11:从机的地址
0x01:01功能码,读取从机线圈状态
0x00 0x13:读取的寄存器起始地址。从0x0013开始读取
0x00 0x25:读取的线圈数量为0x0025个 。也就是37个状态量,8位一个字节,需要5个字节的空间。
0xF9 0xC8:循环冗余校验 CRC校验。
从机返回的报文如下:
| 从站地址 | 功能码 | 字节数量 | 数据 | 数据 | 数据 | 数据 | 数据 | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|---|---|
| 0x11 | 0x01 | 0x05 | 0xCD | 0x6B | 0xB2 | 0x0E | 0x1B | 0x18 | 0x8D |
0x11:从机的地址
0x01:01功能码,读取从机线圈状态
0x05:返回字节数量为5个。注意指定读取的线圈数量是37个,所以第5个字节的只有从低到高的前5个字节有效,bit6,bit7,bit8是无效值,需要忽略。
0xCD 0x6B 0xB2 0x0E 0x1B:返回的数据,5个字节的具体值。
0x18 0x8D:循环冗余校验 CRC校验
第一个数据0xCD转为二进制数值为11001101,从低位到高位读。说明线圈的状态值如下:
| 线圈地址 (十进制) | 线圈地址 (十六进制) | 位位置 | 状态 (OFF/ON) | 二进制值 |
|---|---|---|---|---|
| 19 | 0x0013 | bit0 | ON 合 | 1 |
| 20 | 0x0014 | bit1 | OFF 分 | 0 |
| 21 | 0x0015 | bit2 | ON 合 | 1 |
| 22 | 0x0016 | bit3 | ON 合 | 1 |
| 23 | 0x0017 | bit4 | OFF 分 | 0 |
| 24 | 0x0018 | bit5 | OFF 分 | 0 |
| 25 | 0x0019 | bit6 | ON 合 | 1 |
| 26 | 0x001A | bit7 | ON 合 | 1 |
其他的字段以此类推。
2.读取保持寄存器、读取输入寄存器(功能码:0x03、0x04)
主机发送报文如下(大端,即数据的高位先发送):
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 寄存器数量(高) | 寄存器数量(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x01 | 0x03 | 0x00 | 0x01 | 0x00 | 0x01 | 0xD5 | 0xCA |
0x01:从机的地址
0x03:读取从机保持寄存器的数据
0x00 0x01:读取的寄存器起始地址。从0x0001开始读取。
0x00 0x01: 读取的寄存器数量为1个 。一个寄存器占16位,对应2字节数据。
0xD5 0xCA: 循环冗余校验 CRC校验。
从机返回的报文如下
| 从站地址 | 功能码 | 字节数量 | 数据(高) | 数据(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|
| 0x01 | 0x03 | 0x02 | 0x00 | 0x17 | 0xF8 | 0x4A |
0x01:从机的地址
0x03:读取从机保持寄存器的数据
0x02:返回字节数量为2个(一个寄存器2个字节)
0x00 0x17:寄存器存储的数据是0x0017,十进制23
0xF8 0x4A:循环冗余校验 CRC
3.强置(写)单线圈(功能码:0x05)
主机发送报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 写入的值(高) | 写入的值(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x11 | 0x05 | 0x00 | 0xAC | 0xFF | 0x00 | 0xF3 | 0xF6 |
0x11:从机的地址
0x05:写单个线圈
0x00 0xAC:写的寄存器地址。
0xFF 0x00:要写入的数据。即对0x11号从机的0x00AC地址,写入数值0xFF00。
0xF3 0xF6:循环冗余校验 CRC校验。
从机返回的报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 写入的值(高) | 写入的值(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x11 | 0x05 | 0x00 | 0xAC | 0xFF | 0x00 | 0xF3 | 0xF6 |
从机返回的报文和主机下发的报文一样,相当于确认命令已执行。
4.预置(写)单寄存器(功能码:0x06)
主机发送报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 写入的值(高) | 写入的值(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x11 | 0x06 | 0x00 | 0x01 | 0x00 | 0x32 | 0x79 | 0xB9 |
0x11:从机的地址
0x06:写单个寄存器
0x00 0x01:写的寄存器地址。
0x00 0x32:要写入的数据。即对0x11号从机的0x0001地址,写入数值0x0032。
0x79 0xB9:循环冗余校验 CRC校验。
从机返回的报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 写入的值(高) | 写入的值(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x11 | 0x06 | 0x00 | 0x01 | 0x00 | 0x32 | 0x79 | 0xB9 |
从机返回的报文和主机下发的报文一样,相当于确认命令已执行。
5.强置(写)多线圈(功能码:0x0F)
主机对从机发送报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 线圈状态数量(高) | 线圈状态数量(低) | 字节数量 | 数值1 | 数值2 | CRC校验(高) | CRC校验(低) |
|---|---|---|---|---|---|---|---|---|---|---|
| 0x01 | 0x0F | 0x04 | 0xA5 | 0x00 | 0x0D | 0x02 | 0x0C | 0x02 | 0xB9 | 0xDE |
0x01:从机的地址
0x0F:写多个线圈
0x04 0xA5:写的寄存器起始地址。大端模式,计算后地址十进制为1189。
0x00 0x0D:要写的线圈状态数量。这里是13个。
0x02:要写入的字节的数量。13个线圈比特位,需要两个字节的数据来填充。注意填充的2个字节有16比特,但bit14-bit16的值是不会写入的。
0x0C 0x02:要写入的数据,这里是2个字节的内容。即 0000 1100, 0000 0010,每个字节从低位到高位的顺序写。
0xB9 0xDE:循环冗余校验 CRC校验。
写入之后的线圈状态值:
| 线圈地址 (十进制) | 线圈地址 (十六进制) | 位位置 | 状态 (OFF/ON) | 二进制值 |
|---|---|---|---|---|
| 1189 | 0x04A5 | 字节1(0x0C), bit0 | OFF 分 | 0 |
| 1190 | 0x04A6 | 字节1(0x0C), bit1 | OFF 分 | 0 |
| 1191 | 0x04A7 | 字节1(0x0C), bit2 | ON 合 | 1 |
| 1192 | 0x04A8 | 字节1(0x0C), bit3 | ON 合 | 1 |
| 1193 | 0x04A9 | 字节1(0x0C), bit4 | OFF 分 | 0 |
| 1194 | 0x04AA | 字节1(0x0C), bit5 | OFF 分 | 0 |
| 1195 | 0x04AB | 字节1(0x0C), bit6 | OFF 分 | 0 |
| 1196 | 0x04AC | 字节1(0x0C), bit7 | OFF 分 | 0 |
| 1197 | 0x04AD | 字节2(0x02), bit0 | OFF 分 | 0 |
| 1198 | 0x04AE | 字节2(0x02), bit1 | ON 合 | 1 |
| 1199 | 0x04AF | 字节2(0x02), bit2 | OFF 分 | 0 |
| 1200 | 0x04B0 | 字节2(0x02), bit3 | OFF 分 | 0 |
| 1201 | 0x04B1 | 字节2(0x02), bit4 | OFF 分 | 0 |
| 1202 | 0x04B2及以上 | 字节2(0x02), bit5 | (超出13个线圈范围,忽略) | - |
从机返回的报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 寄存器数量(高) | 寄存器数量(低) | 字节数量 | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|---|
| 0x11 | 0x0F | 0x04 | 0xA5 | 0x00 | 0x0D | 0x02 | 0xB9 | 0x79 |
6.预置(写)多寄存器(功能码:0x10)
主机发送报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 寄存器数量(高) | 寄存器数量(低) | 字节数量 | 数值1(高) | 数值1(低) | 数值2(高) | 数值2(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0x01 | 0x10 | 0x00 | 0x34 | 0x00 | 0x02 | 0x04 | 0x0C | 0x02 | 0x09 | 0x0E | 0xB9 | 0xDE |
0x01:从机的地址
0x10:写多个保持寄存器
0x00 0x34:写的寄存器起始地址。
0x00 0x02:要写的寄存器数量。这里是2个。
0x04:要写入的字节的数量。一般是寄存器数量*2,所以是4个字节。
0x0C 0x02 0x09 0x0E:要写入的数据
0xB9 0xDE:循环冗余校验 CRC校验。
从机返回的报文如下:
| 从站地址 | 功能码 | 寄存器地址(高) | 寄存器地址(低) | 寄存器数量(高) | 寄存器数量(低) | CRC校验(低) | CRC校验(高) |
|---|---|---|---|---|---|---|---|
| 0x01 | 0x10 | 0x00 | 0x34 | 0x00 | 0x02 | 0xB9 | 0x79 |