CRC循环冗余校验
循环冗余校验码是一种用在数字网络和存储设备上的差错校验码,可以校验原始数据的偶然差
错。
CRC 计算单元使用固定多项式计算 32 位 CRC 校验码。
1. 硬件CRC
在单片机中,芯片具有专用的CRC计算单元,它是按照32位数据长度进行计算。 它相当于是我们的MCU有个小老弟,专门是干CRC计算的。
主要特征
1.1. 在C代码计算CRC
下面我们在C代码中去使用CRC循环冗余校验码:
- 在工程中导入gd32f4xx_crc.c文件
- 参考下面的示例代码,对数据进行计算
void example26_crc_test(){// 开启外设时钟rcu_periph_clock_enable(RCU_CRC);// 复位crc_deinit();// 数据重置crc_data_register_reset();// 要发送的数据uint32_t data[] = {0x00000001,0x00000002,0x00000003,0x00000004};// CRC校验码 uint32_t crc_data = crc_block_data_calculate((uint32_t*)data,4);printf("0x%X\r\n",crc_data);
}
1.2. 在线计算CRC
CRC(循环冗余校验)在线计算_ip33.com
1.3. 比对二者结果
通过下图,我们可以看到两端所计算的结果是相同的,说明数据在通讯的过程中,数据是正确的。
如果通讯的过程中,数据传错了,哪怕是错一位,最终计算出来的结果都是不一致的。
请问下图,我错在哪?
2. 软件CRC
在上面的案例中,我们是将数据传输给硬件计算单元去计算,但是芯片默认只支持32位的结果输出。但是在日常开发中,我们使用16位或者8位的情况非常多,所以我们无法直接使用硬件CRC,这个时候,咱们就得使用软件CRC自己来计算。
好的,我来简单地描述一下如何在软件中实现CRC(循环冗余校验)的步骤:
选择多项式生成器: 首先需要确定使用哪种CRC多项式作为生成器。常见的有CRC-16、CRC-32等多种选择,需要根据实际需求选择合适的生成器。
例如在我们硬件CRC,芯片内部默认使用的多项式生成器是 0x4C11DB7
对每个输入数据位执行以下步骤:
a. 取一个字节数据,将字节左移到最高处
b. 将左移之后的数据和初值进行异或处理,将结果作为新初值
c. 处理新初值每一位, 判断最高位是否是1:
- 如果是1,则新初值左移1位后和多项式进行异或,将结果设置为新初值
- 如果是0,仅左移。
重复步骤3,直到所有输入数据位都处理完毕。
输出结果: 此时出书的就是最终的CRC校验码。
通常,我们进行数据的传输都是使用字节进行传输的,所以在以下的案例中,我们的数据都是按照1字节的方式进行计算
2.1. 32位CRC
uint32_t calculate_crc32(uint8_t *data, uint32_t length) {uint32_t crc = 0xFFFFFFFF;uint32_t poly = 0x04C11DB7; for(int i=0; i< length;i++){// 1. 取出1个字节uint32_t d = data[i];// 2. 将数据进行左移,最高处是32位,传入的数据是8位的,所以左移24crc = crc ^ (d << 24);// 3. 处理新初值每一位for (int j = 0; j < 8; j++) {if (crc & 0x80000000) { // 若最高位是1,则左移1位与多项式进行异或crc = (crc << 1) ^ poly;} else {crc = crc << 1; //若最高位不是1, 则右移一位}}}return crc;
}
void example27_crc_soft_test(){uint8_t data[] = {0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x04};// CRC校验码 uint32_t crc_data = calculate_crc32(data,16);printf("0x%X\r\n",crc_data);
}
2.2. 16位CRC
uint32_t calculate_crc16(uint8_t *data, uint32_t length) {uint32_t crc = 0xFFFF;uint32_t poly = 0x8005; for(int i=0; i< length;i++){// 1. 取出1个字节uint32_t d = data[i];// 2. 将数据进行左移,最高处是16位,传入的数据是8位的,所以左移8crc = crc ^ (d << 8);// 3. 处理新初值每一位for (int j = 0; j < 8; j++) {if (crc & 0x8000) { // 若最高位是1,则左移1位与多项式进行异或crc = (crc << 1) ^ poly;} else {crc = crc << 1; //若最高位不是1, 则右移一位}}}return crc;
}
测试代码
uint8_t data16[] = {0x00,0x01,0x00,0x02,0x00,0x03,0x00,0x04};uint16_t crc_data = calculate_crc16(data16,8);printf("0x%X\r\n",crc_data);
执行代码,查看代码运行结果是否和网页计算一致
2.3. 8位CRC
uint32_t calculate_crc8(uint8_t *data, uint32_t length) {uint32_t crc = 0xFF;uint32_t poly = 0x80; for(int i=0; i< length;i++){// 1. 取出1个字节uint8_t d = data[i];// 2. 将数据进行左移,最高处是8位,传入的数据是8位的,所以左移0crc = crc ^ (d << 0);// 3. 处理新初值每一位for (int j = 0; j < 8; j++) {if (crc & 0x80) { // 若最高位是1,则左移1位与多项式进行异或crc = (crc << 1) ^ poly;} else {crc = crc << 1; //若最高位不是1, 则右移一位}}}return crc;
}
uint8_t data8[] = {0x01,0x02,0x03,0x04};
uint8_t crc_data8 = calculate_crc8(data8,4);
printf("0x%X\r\n",crc_data8);
在网页中查看执行结果,比对与程序执行输出的结果是否一致
3. 模拟数据传输
在该案例中,我们使用单片机作为发送端,使用python客户端作为接收端。
- 在单片机端, 我们先对数据计算一个CRC校验码
- 在接收端,我们重新对数据计算一个CRC校验码
- 比对收到的校验码和自己计算出来的校验码
-
- 若相同,则数据校验成功
- 若不同,则数据校验失败
3.1. 在python中实现CRC8
按照前面的步骤,我们将CRC算法在python中实现一遍
def crc8(data, init_crc):poly = 0x80for d in data:init_crc = init_crc^d # 1.for i in range(8):if init_crc&0x80:init_crc = (init_crc<<1)^polyelse:init_crc = init_crc << 1return init_crc&0xff # 这里的0xff只是为了去取出8位数据
3.2. 单片发出数据
在单片机中,我们循环的去发送数据
while(1){// 假设我们要发送的数据是前4个字节uint8_t data8[5] = {0x01,0x02,0x03,0x04,0};// 计算数据的CRC校验码uint8_t crc_data8 = calculate_crc8(data8,4);// 将CRC校验码拼接到要发送数据的末尾data8[4] = crc_data8;usart0_dma_send_data(data8,5);delay_1ms(2000);}
3.3. python接收数据
在python客户端中,我们要干如下几件事:
- 接收到客户端数据
- 将数据转成字节数组
- 从字节数组中拆分出数据部分和校验码部分
- 使用crc校验算法,对数据部分重新计算校验码
- 比对计算出来的校验码和接收到的校验码
-
- 若相同,则校验成功
- 若不同,则校验失败,该条数据就是辣鸡
ser = serial.Serial("COM58", 115200, timeout=3000)
print("ser is open:",ser.isOpen())while True:# print(ser)count = ser.in_waiting# 先找到头if count >= 5:data = ser.read(count)print(f"count={count},data={data}");if count == 5:result = struct.unpack('<BBBBB',data )print(f"result={result}")# 将数据部分取出来,计算校验码code = crc8(result[0:4],0xff)print("tx_code=0x{:02X}, rx_code=0x{:02X}".format(result[4],code)) time.sleep(0.001)ser.close()