单片机 CRC 校验原理与实现:Modbus CRC16 详解
👁 4 阅读 · 2026-08-14 · 嵌入式 协议
CRC校验在嵌入式通信中广泛使用,尤其Modbus协议依赖CRC16保证数据完整性。本文从数学原理出发,讲解CRC的除法模型、Modbus CRC16的参数特性,并给出基于查表法的单片机C实现,包括表生成、计算函数与调用示例,帮助开发者快速移植到任意MCU。
## 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 通过标准参数和查表优化,可在任何单片机上高效实现。掌握其数学原理和实现细节,能帮助你更好地调试通信故障。