Modbus RTU协议
1、RS485接口标准
1.1 基本概念
-
RS485 是一种电气标准,定义了串行通信的物理层特性。
-
它采用差分信号传输,具有较强的抗干扰能力,适合长距离通信。
-
RS485 支持多点通信,即多个设备可以连接到同一条总线上。
1.2 主要特点
-
长距离传输:最大传输距离可达 1200 米(速率较低时)。
-
多点通信:支持最多 32 个设备连接到同一条总线(使用中继器可扩展至更多设备)。
-
半双工通信:同一时间只能发送或接收数据,不能同时进行。
1.3 电气特性
-
信号电平:
-
逻辑 1:A 线电压 > B 线电压(差分电压 > +200mV)。
-
逻辑 0:A 线电压 < B 线电压(差分电压 < -200mV)。
-
-
传输速率:通常支持 100 kbps 到 10 Mbps,速率越高,传输距离越短。
-
终端电阻:在总线两端需要接入 120Ω 的终端电阻,以匹配阻抗,减少信号反射。
2. RS485 在网络中的层次
RS485 属于 OSI 模型 的 物理层(Physical Layer)。
-
数据链路层:RS485 本身不定义数据链路层协议,但可以与 Modbus、Profibus 等协议结合使用
3. RS485 与 RS232 的比较
| 特性 | RS485 | RS232 |
|---|---|---|
| 传输方式 | 差分信号 | 单端信号 |
| 抗干扰能力 | 强 | 弱 |
| 传输距离 | 1200 米(低速时) | 15 米 |
| 通信方式 | 半双工/全双工 | 全双工 |
| 设备数量 | 最多 32 个(可扩展) | 点对点 |
| 应用场景 | 工业自动化、长距离通信 | 短距离通信(如 PC 外设) |
4、MODBUS RTU通信规则
MODBUS RTU是处于数据链路层的,他将数据进行帧封装,然后通过rs485物理层发送出去
-
系统中只有一个设备是主机:主从通信
-
系统中的从机不可以主动的向主机发送数据
-
系统上电后所有的主从设备都应该处于监听总线的状态,也就是接收状态
-
如果要发起一次通信,必须由主机主动发起(任何一次的通信或数据交换都必须由主机来发起)
-
只有主机可以发起广播通信,从机收到广播消息无需恢复
-
主从通讯有问有答,即使主机发送的数据有误,从机也要响应
5、MODBUS RTU协议
5.1信息帧格式
| 地址码 | 功能码 | 数据 | 错误校验 |
|---|---|---|---|
| 8bits(1字节) | 8bits(1字节) | N*8bits(N字节) | 16bits(2字节) |
5.2地址码
-
地址码范围: 0~247
-
对于一主多从的通讯架构中,主机为 0。从设备地址为 1~247。
-
主机发送地址码 0 ,一般称为广播数据,从机无需响应。
-
地址码其实就是对应挂载在总线上的从机,0x01表示第一个设备
5.3功能码
MODBUS通讯规约定义功能码为1-127,通常主从双方会约定好使用其中一部分,如下是常用的一些功能码
功能码就是表明这一个数据帧的作用的,常用功能码如下
| 0x01 | 读线圈寄存器 |
|---|---|
| 0x02 | 读离散输入寄存器 |
| 0x03 | 读保持寄存器 |
| 0x04 | 读输入寄存器 |
| 0x05 | 写单个线圈寄存器 |
| 0x06 | 写单个保持寄存器 |
| 0x0f | 写多个线圈寄存器 |
| 0x10 | 写多个保持寄存器 |
我对功能码的理解是:首先他们是对寄存器不同的功能的描述,Modbus有四个逻辑上的寄存器,这些寄存器是虚构的,为了可以更好操作对应的硬件,比如线圈寄存器对应的是开关类硬件,支持0xff00表示开,0x0000表示关。每个寄存器对应着不同的功能,而这四种功能还能再细分为写单个,和写多个,以及其它功能。
线圈寄存器:实际上就可以类比为开关量,每个bit都对应一个信号的开关状态。
离散输入寄存器:也可以叫做触点。只能去读取状态不能去写入状态。
保持寄存器:长度是16个bit 的寄存器,可以被读取和写入,用于保存一定的信息
输入寄存器:和保持寄存器比较相似,但是只能被读取不能写入。
注意上面提到的不能写入是指不能被主机端写入,从机自己是可以修改自己的离散输入和输入寄存器的!
5.4数据
数据通常是由功能码决定的,功能码不同数据格式会有差别,这里先举一个简单的例子,先有一个了解,第5节会详细介绍读寄存器和写寄存器的详细交互流程。如下数据帧为通过0x03指令码读取寄存器数据,功能码和错误校验中间的是数据,数据共4个字节。前两字节表示寄存器地址,后两字节表示要读取的寄存器个数。
寄存器地址你可以理解为是一种映射,主机需要和从机通信,往从机寄存器写值,需要将从机的地址通过协议的地址码映射,这种映射可以是芯片厂商固定的也可以是灵活修改的。比如地址0Xaaaa可能对应于从机的某个寄存器0x0001。
| 地址码 | 功能码 | 寄存器地址 | 寄存器个数 | 错误校验低位 | 错误校验高位 |
|---|---|---|---|---|---|
| 0x01 | 0x03 | 0x00,0x60 | 0x00,0x02 | 0xC4 | 0x15 |
| 字段 | 值 | 说明 |
|---|---|---|
| 地址码 | 0x01 | 从站设备的地址,表示这帧数据是发送给地址为 1 的设备。 |
| 功能码 | 0x03 | 功能码,表示这是一个“读取保持寄存器”的请求。 |
| 寄存器地址 | 0x00, 0x60 | 起始寄存器地址,表示从寄存器地址 0x0060 开始读取数据。 |
| 寄存器个数 | 0x00, 0x02 | 要读取的寄存器数量,表示读取 2 个寄存器的数据。 |
| 错误校验低位 | 0xC4 | CRC 校验码的低字节,用于验证数据的完整性。 |
| 错误校验高位 | 0x15 | CRC 校验码的高字节,用于验证数据的完整性。 |
-
寄存器地址由两个字节组成,高字节在前,低字节在后。
-
寄存器个数由两个字节组成,高字节在前,低字节在后。
-
校验码由两个字节组成,低字节在前,高字节在后。
-
MODBUS RTU一般采用CRC16校验方式,CRC16的多项式:0x8005,CRC校验范围为CRC字段前所有字节的校验。
响应帧示例
假设从站设备成功响应,返回 2 个寄存器的数据(假设寄存器 0x0060 的值为 0x1234,寄存器 0x0061 的值为 0x5678),响应帧如下:
| 字段 | 值 | 说明 |
|---|---|---|
| 地址码 | 0x01 | 从站设备的地址,表示这帧数据来自地址为 1 的设备。 |
| 功能码 | 0x03 | 功能码,表示这是一个“读取保持寄存器”的响应。 |
| 字节数 | 0x04 | 返回的数据字节数,表示返回了 4 个字节的数据。 |
| 数据 | 0x12, 0x34 | 寄存器 0x0060 的值。 |
| 数据 | 0x56, 0x78 | 寄存器 0x0061 的值。 |
| 错误校验低位 | 0xXX | CRC 校验码的低字节。 |
| 错误校验高位 | 0xXX | CRC 校验码的高字节。 |
5.5CRC校验
-
CRC 校验基于多项式除法,通过对数据帧中的每个字节进行计算,生成一个 16 位的校验码。
-
发送方计算 CRC 校验码并附加到数据帧末尾。
-
接收方重新计算 CRC 校验码,并与接收到的 CRC 校验码进行比较。如果两者一致,说明数据帧完整;否则,说明数据帧有误。
-
以下是 CRC 校验的详细计算步骤:
初始化
-
初始化一个 16 位的 CRC 寄存器,值为
0xFFFF。
逐字节处理
-
对数据帧中的每个字节(不包括 CRC 部分)进行处理:
-
将当前字节与 CRC 寄存器的低字节进行异或操作。
-
对 CRC 寄存器进行 8 次移位操作(每次右移 1 位):
-
如果移出的最低位为 1,则将 CRC 寄存器与多项式
0xA001进行异或操作。 -
如果移出的最低位为 0,则继续移位。
-
-
-
假设我们有一个 Modbus RTU 请求帧:
01 03 00 60 00 02
-
地址码:
0x01 -
功能码:
0x03 -
寄存器地址:
0x00, 0x60 -
寄存器个数:
0x00, 0x02
5.5.1 计算步骤
初始化 CRC 寄存器
CRC = 0xFFFF
处理第一个字节(0x01):
异或操作:
CRC = CRC ^ 0x01 = 0xFFFF ^ 0x0001 = 0xFFFE
移位操作(8 次):
移位 1:CRC = 0x7FFF(最低位为 0,不移出) 移位 2:CRC = 0x3FFF 移位 3:CRC = 0x1FFF 移位 4:CRC = 0x0FFF 移位 5:CRC = 0x07FF 移位 6:CRC = 0x03FF 移位 7:CRC = 0x01FF 移位 8:CRC = 0x00FF
接下来对每一个字节按上面说说进行处理即可得到CRC校验码。
在线校验工具:CRC校验工具-ME2在线工具
5.5.2错误校验代码
unsigned short count_CRC(unsigned char *addr, int num)
{
int i;
unsigned short CRC = 0xFFFF;
while (num--)
{
CRC ^= *addr++;
for (i = 0; i < 8; i++)
{
CRC = (CRC & 0x0001) ? ((CRC >> 1)^0xa001) : (CRC >> 1);
}
}
return CRC;
}
5.5.2高低位颠倒代码
通过5.5.2的代码计算出来的CRC高位在前低位在后,但 通常MODBUS的CRC用的是高位在后低位在前,所以还需要转换一下。
//大小端序转换,颠倒16位数据的高位和低位
void swap16(void *p)
{
unsigned short *ptr = p;
unsigned sort x = *ptr;
x = (x << 8) | (x >> 8);
*ptr = x;
}
//大小端序转换,颠倒32位数据的高位和低位
void swap16(void *p)
{
unsigned short *ptr = p;
unsigned sort x = *ptr;
x = (x << 16) | (x >> 16);
x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF)
*ptr = x;
}
5.6错误码
| 异常码名称 | 说 明 |
|---|---|
| 01 非法功能码 | 对于从节点来说,询问中接收到的功能码是不可允许的操作。这也许是因为功能码仅仅适用于新设备而在被选单元中是不可实现的。同时,还指出从节点在错误状态中处理这种请求,例如:因为它是未配置的,并且要求返回寄存器值。 |
| 02 非法数据地址 | 对于从节点来说,询问中接收到的数据地址是不可允许的地址。特别是,参考号和传输长度的组合是无效的。对于带有100个寄存器的控制器来说,带有偏移量96和长度4的请求会成功,带有偏移量96和长度5的请求将产生异常码02。 |
| 03 非法数据值 | 对于从节点来说,询问中包括的值是不可允许的值。这个值指示了组合请求剩余结构中的故障,例如:隐含长度是不正确的。并不意味着,因为MODBUS协议不知道任何特殊寄存器的任何特殊值的重要意义,寄存器中被提交存储的数据项有一个应用程序期望之外的值。 |
总的来说就是在 Modbus 协议 中,当主站(Master)向从站(Slave)发送请求时,如果从站无法正确处理请求,会返回一个异常响应。异常响应中包含一个 异常码,用于指示具体的错误类型。
示例
-
主站发送功能码
0x03(读取保持寄存器),但从站不支持该功能码。 -
主站发送功能码
0x05(写单个线圈),但从站当前未配置,无法处理该请求。
异常响应格式
异常响应帧中,功能码的最高位被置为 1(即原功能码 + 0x80),并附加异常码 0x01。
地址码 | 功能码 + 0x80 | 异常码 0x01 | 0x83 | 0x01
更多推荐



所有评论(0)