🔎【博主简介】🔎

        🏅全网技术博客粉丝量十万

        🏅全网技术博客学习数百万

        🏅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
  • 寄存器数据:0xAABB

2. 生成报文拆解

生成的完整报文: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 D9 CRC 校验码: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 为例)

  1. 初始化:CRC = 0xFFFF
  2. 逐字节处理
    • 将 CRC 与当前字节异或
    • 对结果循环 8 次右移操作:
      • 若最低位为 1:右移 1 位后,与多项式 0xA001 异或
      • 若最低位为 0:仅右移 1 位
  3. 所有字节处理完成后,得到 16 位 CRC 值,拆分高低字节后按「低字节在前」填入报文

示例计算结果:0xD9E6 → 拆分为 E6(低字节)和 D9(高字节),即报文中的 E6 D9

3. 代码实现(嵌入式 / 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

四、关键知识点总结

  1. Modbus-RTU 报文 = 地址 + 功能码 + 数据 + CRC
  2. 功能码 06 代表「写单个保持寄存器」,是工业控制中最常用的操作之一
  3. CRC 校验 是 Modbus-RTU 通信可靠性的核心,算法固定为 CRC-16 (Modbus),多项式 0xA001
  4. 结果格式:计算得到的 16 位 CRC 值,报文中必须按「低字节在前,高字节在后」排列
  5. 开发建议:实际项目中直接使用成熟代码计算 CRC,避免手动计算出错

五、常见问题 Q&A

Q1:为什么 CRC 结果要拆成低字节在前?

A:这是 Modbus-RTU 协议规定的传输格式,接收方会按「先低字节、后高字节」的顺序拼接 CRC 并校验,若顺序颠倒会导致校验失败。

Q2:CRC 计算时包含哪些字节?

A:仅包含报文的地址、功能码、数据段,不包含 CRC 本身和串口帧间隔(如 3.5 字符空闲时间)。

Q3:功能码 0610 有什么区别?

A:06 用于写单个寄存器,10 用于写多个连续寄存器,报文结构和数据段长度会有所不同。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐