Modbus 报文结构与 CRC 校验实战指南(一)
一、引言
**
在工业通信领域,Modbus 协议无疑是最为广泛应用的协议之一。自 1979 年由 Modicon 公司(现属施耐德电气)推出以来,Modbus 凭借其简单易用、开放性以及广泛的兼容性,迅速成为工业自动化系统中设备之间通信的事实标准 。无论是在制造业、能源行业,还是楼宇自动化等场景中,Modbus 都扮演着关键角色,实现了不同设备之间的数据交互与协同工作。
Modbus 报文结构是理解 Modbus 通信的基础,它定义了数据在设备之间传输的格式和规则。通过深入掌握报文结构,开发者能够精确地解析和构建通信数据,确保设备之间的准确通信。而 CRC 校验则是保障 Modbus 通信可靠性的重要机制,它能够有效检测数据在传输过程中是否发生错误,从而避免因数据错误导致的系统故障。
对于从事工业自动化开发、设备维护以及系统集成的开发者而言,深入理解 Modbus 报文结构与 CRC 校验不仅是必备的技能,更是解决实际问题、优化系统性能的关键。在实际项目中,准确解析报文可以帮助快速定位设备状态和故障信息,而可靠的 CRC 校验则能大大提高系统的稳定性和可靠性,减少因通信错误带来的损失。接下来,让我们一起深入探索 Modbus 报文结构与 CRC 校验的奥秘。
二、Modbus 报文结构探秘
2.1 Modbus 协议概述
Modbus 协议诞生于 1979 年,由 Modicon 公司(现属施耐德电气)推出 ,旨在解决工业自动化领域中设备之间通信不兼容的问题。在当时,不同厂家生产的设备各自采用独特的通信方式,导致设备之间难以互联互通,严重制约了工业自动化系统的集成与扩展。Modbus 协议的出现,犹如一场及时雨,为工业设备提供了一种统一、标准化的通信方式,使得不同品牌、不同类型的设备能够顺畅地进行数据交互。
随着时间的推移,Modbus 协议凭借其简单易用、开放性强以及广泛的兼容性,迅速在工业自动化领域中占据了重要地位。它不仅实现了设备之间的无缝通信,还促进了工业系统的集成化和智能化发展。如今,Modbus 协议已经成为工业自动化领域中最为流行的通信协议之一,被广泛应用于制造业、能源行业、楼宇自动化、交通运输等众多领域。
在制造业中,Modbus 协议可实现生产线中各种设备,如 PLC、传感器、执行器等之间的数据传输与协同工作,从而提高生产效率和产品质量;在能源行业,它能够对发电设备、电网设备等进行实时监控与管理,确保能源的稳定供应;在楼宇自动化领域,Modbus 协议可实现对空调、照明、电梯等设备的集中控制,提高楼宇的智能化管理水平。
2.2 Modbus 报文的组成部分
一个完整的 Modbus 报文通常由设备地址、功能码、数据和校验码这几个关键部分组成,每一部分都有着独特的含义和重要作用。
- 设备地址:设备地址用于标识 Modbus 网络中的从设备,它占 1 个字节,取值范围是 0 - 255 。其中,0 通常被保留用于广播通信,即主设备向所有从设备发送消息;1 - 247 为有效的从设备地址,不同的从设备通过唯一的地址进行区分;248 - 255 则被保留用于特殊用途。例如,在一个工业自动化系统中,有多个传感器和执行器作为从设备连接到主设备上,每个传感器和执行器都被分配了一个唯一的设备地址,主设备通过这个地址来准确地与特定的从设备进行通信,实现数据的读取和控制指令的发送。
- 功能码:功能码占 1 个字节,取值范围是 1 - 255,用于指示主设备请求从设备执行的操作类型 。不同的功能码对应着不同的操作,常见的功能码包括读取线圈状态(功能码 01)、读取离散输入状态(功能码 02)、读取保持寄存器(功能码 03)、读取输入寄存器(功能码 04)、写单个线圈(功能码 05)、写单个保持寄存器(功能码 06)等。例如,当主设备需要读取某个传感器的测量值时,会向该传感器对应的从设备发送功能码为 03 的报文,以请求读取其保持寄存器中的数据。
- 数据:数据部分的内容和长度取决于功能码的类型 。它包含了主设备请求从设备读取或写入的具体信息。例如,在读取保持寄存器的操作中,数据部分会包含要读取的寄存器起始地址和寄存器数量;在写单个保持寄存器的操作中,数据部分则会包含要写入的寄存器地址和数据值。以读取从设备中地址为 0x0001 开始的 10 个保持寄存器的数据为例,数据部分就会包含起始地址 0x0001 和寄存器数量 0x000A(十进制 10)。
- 校验码:校验码用于检测数据在传输过程中是否发生错误,以确保数据的完整性和准确性 。Modbus 协议常用的校验方式有 CRC(循环冗余校验)和 LRC(纵向冗余校验),其中 CRC 校验更为常用。CRC 校验码通常占 2 个字节,它是根据报文中的设备地址、功能码和数据等部分通过特定的 CRC 算法计算得出的。接收方在收到报文后,会重新计算 CRC 校验码,并与报文中的校验码进行比对,如果两者一致,则认为数据传输正确;否则,说明数据可能发生了错误,需要重新传输。
2.3 不同功能码的报文结构解析
Modbus 协议定义了多种功能码,每种功能码对应的报文结构都有其特定的规则。下面以读取线圈状态(功能码 01)和读取保持寄存器(功能码 03)这两种常见的功能码为例,详细分析主站询问报文和从站响应报文的结构。
读取线圈状态(功能码 01)
- 主站询问报文结构:[设备地址(1 字节)][功能码 01(1 字节)][起始地址(2 字节)][线圈数量(2 字节)][CRC 校验码(2 字节)]。例如,主站向地址为 0x01 的从设备发送询问报文,请求读取从地址 0x0000 开始的 10 个线圈的状态,其报文内容可能为:0x01 0x01 0x00 0x00 0x00 0x0A [CRC 校验码]。其中,0x01 为设备地址,0x01 表示读取线圈状态的功能码,0x00 0x00 是起始地址,0x00 0x0A 表示要读取的线圈数量为 10 个,最后的 [CRC 校验码] 是根据前面的内容计算得出的。
- 从站响应报文结构:[设备地址(1 字节)][功能码 01(1 字节)][字节数(1 字节)][数据(n 字节)][CRC 校验码(2 字节)] 。从站在接收到主站的询问报文后,会返回响应报文。例如,从站地址为 0x01,功能码为 0x01,返回的字节数为 0x02(表示后面的数据占 2 个字节),数据为 0x30 0x00(假设这 10 个线圈的状态对应的二进制数据为 0011 0000 0000 0000),CRC 校验码为 [具体校验码值],则响应报文为:0x01 0x01 0x02 0x30 0x00 [CRC 校验码]。这里的数据部分,每个线圈的状态用 1 位二进制表示,8 个线圈的状态组成 1 个字节,不足 8 位的高位补 0。
读取保持寄存器(功能码 03)
- 主站询问报文结构:[设备地址(1 字节)][功能码 03(1 字节)][起始地址(2 字节)][寄存器数量(2 字节)][CRC 校验码(2 字节)] 。例如,主站向地址为 0x02 的从设备发送询问报文,请求读取从地址 0x0001 开始的 5 个保持寄存器的值,其报文内容可能为:0x02 0x03 0x00 0x01 0x00 0x05 [CRC 校验码]。其中,0x02 为设备地址,0x03 表示读取保持寄存器的功能码,0x00 0x01 是起始地址,0x00 0x05 表示要读取的寄存器数量为 5 个。
- 从站响应报文结构:[设备地址(1 字节)][功能码 03(1 字节)][字节数(1 字节)][数据(n*2 字节)][CRC 校验码(2 字节)] 。由于每个保持寄存器的数据通常是 16 位(2 字节),所以从站响应报文中数据部分的长度为寄存器数量的两倍。例如,从站地址为 0x02,功能码为 0x03,返回的字节数为 0x0A(表示后面的数据占 10 个字节,即 5 个寄存器 * 2 字节 / 寄存器),数据为 0x12 0x34 0x56 0x78 0x9A 0xBC 0xDE 0xF0 0x11 0x22(假设这 5 个保持寄存器的值分别为 0x1234、0x5678、0x9ABC、0xDEF0、0x1122),CRC 校验码为 [具体校验码值],则响应报文为:0x02 0x03 0x0A 0x12 0x34 0x56 0x78 0x9A 0xBC 0xDE 0xF0 0x11 0x22 [CRC 校验码]。
三、CRC 校验深度剖析
3.1 CRC 校验的基本概念
CRC 校验,即循环冗余校验(Cyclic Redundancy Check) ,是数据通信领域中最常用的一种查错校验码。它通过对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。CRC 校验的目的在于检测数据在传输过程中是否发生错误,确保接收端接收到的数据与发送端发送的数据一致。
在数据传输过程中,由于受到噪声干扰、信号衰减等因素的影响,数据可能会发生错误。例如,在工业自动化系统中,传感器采集的数据需要通过通信网络传输到控制器,如果数据在传输过程中发生错误,控制器可能会做出错误的决策,导致设备故障或生产事故。因此,采用有效的校验方法来保证数据的准确性和完整性至关重要。
与其他常见的校验方法,如奇偶校验、海明校验相比,CRC 校验具有显著的优势。奇偶校验是一种简单的校验方法,它通过在数据中添加一个校验位,使得数据中 “1” 的个数为奇数(奇校验)或偶数(偶校验) 。接收端通过检查 “1” 的个数是否符合奇偶性来判断数据是否正确。然而,奇偶校验只能检测出奇数位的错误,对于偶数位的错误则无法检测。例如,当数据中的两位同时发生错误时,奇偶校验无法发现这些错误。
海明校验则是一种更为复杂的校验方法,它通过在数据中添加多个校验位,不仅能够检测出错误,还能够纠正一些错误 。海明校验的实现相对复杂,需要较多的校验位,增加了数据传输的开销。而且,海明校验的纠错能力也受到一定限制,对于多个错误同时发生的情况,可能无法正确纠错。
相比之下,CRC 校验能够检测出多种类型的错误,包括突发错误和多位错误 。它通过特定的多项式计算,生成一个校验码,该校验码与数据之间存在着紧密的关联。即使数据中发生了多位错误,CRC 校验也能够有效地检测出来。此外,CRC 校验的计算效率较高,实现相对简单,在数据通信和存储领域得到了广泛应用。
3.2 CRC 校验的工作原理
CRC 校验基于多项式除法的数学原理,它将数据视为一个多项式,并使用预设的生成多项式进行除法运算,最终余数作为 CRC 校验值。在 CRC 校验中,数据被表示为一个系数为 0 或 1 的多项式序列,例如,数据 1011 可以表示为多项式\(x^3 + x^1 + x^0\) 。生成多项式则是一个预先定义的多项式,它的选择对 CRC 算法的错误检测能力有重要影响。
以一个简单的例子来说明 CRC 码的生成和校验步骤。假设要发送的数据为 1010,生成多项式为\(G(X) = X^3 + X + 1\)(对应的二进制数为 1011) 。
- 数据左移:将数据 1010 左移 3 位(生成多项式的次数),得到 1010000。这是因为在 CRC 校验中,需要在数据后面添加与生成多项式次数相同数量的 0,以便进行多项式除法运算。
- 模 2 除法:用左移后的数据 1010000 除以生成多项式 1011,这里的除法采用模 2 除法,即每一位除的结果不影响其他位,不向上借位,相当于二进制中的逻辑异或运算。具体计算过程如下:
-
- 首先,将 1010000 的前 4 位 1010 与 1011 进行异或运算,得到 0001。
-
- 然后,将 0001 与下一位 0 组合成 00010,再与 1011 进行异或运算,得到 1001。
-
- 接着,将 1001 与下一位 0 组合成 10010,继续与 1011 进行异或运算,得到 0011。
-
- 最后,将 0011 与下一位 0 组合成 00110,再与 1011 进行异或运算,得到 110,这就是余数。
- 生成 CRC 码:将得到的余数 110 作为 CRC 码,附加在原始数据 1010 后面,得到完整的发送数据 1010110。
在接收端,对接收到的数据进行同样的 CRC 校验操作。如果计算得到的余数为 0,则说明数据在传输过程中没有发生错误;如果余数不为 0,则说明数据发生了错误,需要进行相应的处理,如请求重发数据。
3.3 CRC-16 算法在 Modbus 中的应用
在 Modbus 协议中,常用的 CRC 校验算法是 CRC-16,它生成 16 位的校验码,能够有效地检测出数据传输中的错误 。CRC-16 算法使用的多项式为\(X^16 + X^15 + X^2 + 1\),对应的十六进制数为 0x8005,初始值为 0xFFFF 。
下面通过一个具体的代码示例,展示 CRC-16 算法在 Modbus 中的实现过程。以 C 语言代码为例:
#include <stdio.h>
// 计算CRC-16校验码
unsigned short crc16_modbus(unsigned char *data, int length) {
unsigned short crc = 0xFFFF;
int i, j;
for (i = 0; i < length; i++) {
crc ^= (unsigned short)data[i];
for (j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
int main() {
unsigned char data[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01}; // 示例数据
int length = sizeof(data) / sizeof(data[0]);
unsigned short crc = crc16_modbus(data, length);
printf("CRC-16校验码: 0x%04X\n", crc);
return 0;
}
在上述代码中,crc16_modbus函数实现了 CRC-16 算法。首先,将 CRC 寄存器初始化为 0xFFFF 。然后,遍历数据数组,对每个字节与 CRC 寄存器进行异或操作。接着,对每个字节的 8 位进行处理,如果 CRC 寄存器的最低位为 1,则右移一位并与 0xA001 进行异或;否则,仅右移一位。最后,返回计算得到的 CRC 校验码。
在main函数中,定义了一个示例数据数组,并调用crc16_modbus函数计算 CRC 校验码,然后输出结果。通过这个示例,可以清晰地看到 CRC-16 算法在 Modbus 中的具体实现过程。