【MODBUS】一文搞懂 Modbus-RTU 报文与 CRC 校验计算
🔎【博主简介】🔎
🏅全网技术博客粉丝量十万
🏅全网技术博客学习数百万
🏅CSDN博客专家领域专家
🏅2021年博客之星物联网与嵌入式开发TOP5
🏅2022年博客之星物联网与嵌入式开发TOP4
🏅2021年2022年C站百大博主
🏅华为云开发者社区专家博主
🏅阿里云开发者社区专家博主
🏅掘金INFOQ腾讯云优秀博主一文搞懂 Modbus-RTU 报文与 CRC 校验计算
从报文结构到实战计算,嵌入式 / 工业通信入门必备!
一、Modbus-RTU 报文结构是什么?
Modbus-RTU 是工业现场最常用的串行通信协议,采用二进制帧格式,传输效率高、抗干扰能力强。其标准帧结构如下:
字段名 长度(字节) 作用说明 Slave Address 1 从机地址,指定通信目标设备(范围 1~247,0 为广播地址) Function Code 1 功能码,告诉从机执行何种操作(如读寄存器、写寄存器等) Data 0~252 数据段,承载指令参数或业务数据(如寄存器地址、要写入的值等) CRC 2 循环冗余校验,用于验证数据传输完整性,分为 CRC Low(低字节)和 CRC High(高字节) 报文长度范围:最小 4 字节(地址 + 功能码 + CRC),最大 256 字节(地址 + 功能码 + 252 字节数据 + CRC)。
二、实战:写单个寄存器报文解析
以常见的「写单个保持寄存器」操作为例,我们来看一个完整报文生成示例:
1. 界面参数
- 协议类型:Modbus-RTU
- 从设备 ID:
0x01- 功能号:
06(写单个寄存器)- 寄存器地址:
0x0001- 寄存器数据:
0xAABB2. 生成报文拆解
生成的完整报文:
01 06 00 01 AA BB E6 D9
字节段 内容 含义 第 1 字节 01从机地址:目标设备地址为 1 第 2 字节 06功能码:执行「写单个保持寄存器」操作 第 3~4 字节 00 01寄存器地址:要写入的寄存器地址为 0x0001 第 5~6 字节 AA BB寄存器数据:要写入的 16 位数据值 0xAABB 第 7~8 字节 E6 D9CRC 校验码: E6为低字节,D9为高字节,用于校验数据传输是否出错这条报文的完整指令含义:
向地址为 1 的设备,执行写单个寄存器操作,将数据
0xAABB写入地址为0x0001的保持寄存器中。三、核心:Modbus CRC-16 校验计算
Modbus-RTU 使用 CRC-16 (Modbus) 算法进行数据完整性校验,这是通信可靠性的关键保障。
1. 算法核心规则
- 初始值:
0xFFFF(CRC 寄存器初始化为全 1)- 多项式:
0xA001(等价于标准 CRC-16 多项式0x8005的位反转形式)- 计算范围:报文主体(地址 + 功能码 + 数据段),不含 CRC 字段
- 结果格式:计算结果为 16 位值,报文中需按「低字节在前,高字节在后」排列
2. 手动计算步骤(以报文
01 06 00 01 AA BB为例)
- 初始化:CRC =
0xFFFF- 逐字节处理:
- 将 CRC 与当前字节异或
- 对结果循环 8 次右移操作:
- 若最低位为 1:右移 1 位后,与多项式
0xA001异或- 若最低位为 0:仅右移 1 位
- 所有字节处理完成后,得到 16 位 CRC 值,拆分高低字节后按「低字节在前」填入报文
示例计算结果:
0xD9E6→ 拆分为E6(低字节)和D9(高字节),即报文中的E6 D93. 代码实现(嵌入式 / PC 通用)
手动计算繁琐,实际开发中直接使用以下代码即可:
C 语言版本(嵌入式推荐)
#include <stdint.h> /** * @brief 计算 Modbus-RTU 报文的 CRC16 校验值 * @param data 报文主体字节数组(不含 CRC) * @param len 报文主体长度 * @return 16 位 CRC 值(高字节在前) */ uint16_t modbus_crc16(const uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; // 初始值 uint8_t i; while (len--) { crc ^= *data++; // 异或当前字节 for (i = 0; i < 8; i++) // 8 次移位循环 { if (crc & 0x0001) // 最低位为 1 crc = (crc >> 1) ^ 0xA001; else // 最低位为 0 crc >>= 1; } } return crc; } // 测试示例 int main(void) { uint8_t frame[] = {0x01, 0x06, 0x00, 0x01, 0xAA, 0xBB}; uint16_t crc = modbus_crc16(frame, sizeof(frame)); // 输出:CRC 值 = 0xD9E6,报文 CRC 段 = 0xE6 0xD9 // crc & 0xFF → 低字节 E6 // crc >> 8 → 高字节 D9 return 0; }Python 版本(快速验证)
def modbus_crc16(data): crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 0x0001: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc # 测试 frame = [0x01, 0x06, 0x00, 0x01, 0xAA, 0xBB] crc = modbus_crc16(frame) print(f"CRC 值: 0x{crc:04X}") # 输出: 0xD9E6 print(f"报文 CRC 段: 0x{crc&0xFF:02X} 0x{crc>>8:02X}") # 输出: 0xE6 0xD9四、关键知识点总结
- Modbus-RTU 报文 = 地址 + 功能码 + 数据 + CRC
- 功能码
06代表「写单个保持寄存器」,是工业控制中最常用的操作之一- CRC 校验 是 Modbus-RTU 通信可靠性的核心,算法固定为 CRC-16 (Modbus),多项式
0xA001- 结果格式:计算得到的 16 位 CRC 值,报文中必须按「低字节在前,高字节在后」排列
- 开发建议:实际项目中直接使用成熟代码计算 CRC,避免手动计算出错
五、常见问题 Q&A
Q1:为什么 CRC 结果要拆成低字节在前?
A:这是 Modbus-RTU 协议规定的传输格式,接收方会按「先低字节、后高字节」的顺序拼接 CRC 并校验,若顺序颠倒会导致校验失败。
Q2:CRC 计算时包含哪些字节?
A:仅包含报文的地址、功能码、数据段,不包含 CRC 本身和串口帧间隔(如 3.5 字符空闲时间)。
Q3:功能码
06和10有什么区别?A:
06用于写单个寄存器,10用于写多个连续寄存器,报文结构和数据段长度会有所不同。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)