## CRC 校验的数学本质 CRC(循环冗余校验)本质上是将数据视为二进制多项式,用约定的生成多项式做模2除法,余数即为校验值。例如 Modbus CRC16 使用多项式 x^16 + x^15 + x^2 + 1,即 0x8005(高位省略),实际计算时通过左移或右移方式处理。 ## Modbus CRC16 的算法参数 - 多项式:0x8005(反转后为0xA001) - 初始值:0xFFFF - 输入/输出反转:均反转(即处理时低位在前) - 结果异或:0x0000 以上参数决定了 Modbus 在 RTU 模式下的标准 CRC16。 ## 两种实现方式:直接计算 vs 查表 直接计算适合理解原理,但逐位循环效率低。查表法用空间换时间,适合实时通信。 ### 直接计算示例(右移版) ```c uint16_t crc16_modbus_direct(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= *data++; for (int i = 0; i < 8; i++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; } ``` 这里使用反转多项式 0xA001,与直接使用0x8005左移算法等价,但更贴近单片机小端处理。 ## 查表法实现 ### 生成查找表(在PC或初始化时调用) ```c uint16_t crc16_table[256]; void crc16_init(void) { for (int i = 0; i < 256; i++) { uint16_t crc = i; for (int j = 0; j < 8; j++) { if (crc & 1) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } crc16_table[i] = crc; } } ``` ### 查表计算函数 ```c uint16_t crc16_modbus_table(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; while (len--) { uint8_t idx = (crc ^ *data++) & 0xFF; crc = (crc >> 8) ^ crc16_table[idx]; } return crc; } ``` 注意:查表函数与生成表的移位方向必须一致。这里表内保存的是单字节参与计算后的结果,因此直接异或高字节即可。 ## 实用例程:Modbus 报文生成与校验 Modbus RTU 帧末尾附加两个字节 CRC,先低字节后高字节。以下是一个完整示例: ```c #include uint16_t crc16_modbus(uint8_t *data, uint16_t len); // 上面任意实现 void build_frame(uint8_t addr, uint8_t func, uint8_t *payload, uint8_t plen, uint8_t *frame) { uint8_t len = 2 + plen; // 地址、功能码 + 数据 frame[0] = addr; frame[1] = func; for (int i = 0; i < plen; i++) { frame[2 + i] = payload[i]; } uint16_t crc = crc16_modbus(frame, len); frame[len] = crc & 0xFF; // 低字节 frame[len + 1] = crc >> 8; // 高字节 } ``` 在接收端,对整帧(含CRC)做一次CRC计算,结果为0则校验通过,这是一种常见做法。 ## 配置步骤(移植到新MCU) 1. 定义索引表类型,确认 `uint16_t` 至少16位。 2. 在初始化阶段调用 `crc16_init()` 生成表。 3. 将 `crc16_modbus_table` 封装到协议层。 4. 发送时计算CRC并按“低前高后”填充。 5. 接收时对包括CRC的整帧进行校验,结果应为0。 如果没有特殊要求,可跳过生成表,直接将预生成的静态表存入代码区,节省启动时间。 ## 注意事项 - 字节序:Modbus 规定 CRC 低字节在前,发送顺序不可颠倒。 - 多字节数据:数据流按实际字节顺序输入,不要对16位数做字节序转换。 - 查表法生成的表是全局的,若在中断中使用,需保证初始化完成。 - 某些单片机可用硬件CRC外设,但参数需与Modbus匹配(如多项式、初值、输入/输出反转),否则结果不一致。 ## 总结 CRC 是协议层的“安全带”,Modbus CRC16 通过标准参数和查表优化,可在任何单片机上高效实现。掌握其数学原理和实现细节,能帮助你更好地调试通信故障。