ARM32开发--CRC循环冗余校验

CRC循环冗余校验

循环冗余校验码是一种用在数字网络和存储设备上的差错校验码,可以校验原始数据的偶然差

错。

CRC 计算单元使用固定多项式计算 32 位 CRC 校验码。

1. 硬件CRC

在单片机中,芯片具有专用的CRC计算单元,它是按照32位数据长度进行计算。 它相当于是我们的MCU有个小老弟,专门是干CRC计算的。

主要特征

1.1. 在C代码计算CRC

下面我们在C代码中去使用CRC循环冗余校验码:

  1. 在工程中导入gd32f4xx_crc.c文件
  2. 参考下面的示例代码,对数据进行计算
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,则新初值左移1位后和多项式进行异或,将结果设置为新初值
  2. 如果是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位的,所以左移24
        crc = 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位的,所以左移8
        crc = 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位的,所以左移0
        crc = 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客户端作为接收端。

  1. 在单片机端, 我们先对数据计算一个CRC校验码
  2. 在接收端,我们重新对数据计算一个CRC校验码
  3. 比对收到的校验码和自己计算出来的校验码
    1. 若相同,则数据校验成功
    2. 若不同,则数据校验失败

3.1. 在python中实现CRC8

按照前面的步骤,我们将CRC算法在python中实现一遍


def crc8(data, init_crc):
    poly = 0x80
    
    for d in data:
        init_crc = init_crc^d # 1.
        for i in range(8):
            if init_crc&0x80:
                init_crc = (init_crc<<1)^poly
            else:
                init_crc = init_crc << 1
    
    return 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客户端中,我们要干如下几件事:

  1. 接收到客户端数据
  2. 将数据转成字节数组
  3. 从字节数组中拆分出数据部分和校验码部分
  4. 使用crc校验算法,对数据部分重新计算校验码
  5. 比对计算出来的校验码和接收到的校验码
    1. 若相同,则校验成功
    2. 若不同,则校验失败,该条数据就是辣鸡
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()

  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛慕昭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值