4. CRC校验实现

任务目标

在Bootloader升级流程中,固件包在传输或存储过程中可能发生位翻转、丢包等错误。本案例实现标准CRC32(IEEE 802.3,多项式0x04C11DB7)校验算法,为后续固件包完整性验证提供基础校验函数。

实现方案

采用查表法实现CRC32,以字节为单位处理数据。查表法预先计算256个CRC余式,运行时每个字节仅需一次查表与异或操作,相比逐位计算速度提升约8倍,适合在Bootloader中对整个固件包进行校验。代码不依赖任何硬件外设,可直接在主机编译验证,也可移植到MCU环境。

完整代码

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

/* CRC32表(IEEE 802.3,多项式0x04C11DB7,初值0xFFFFFFFF,结果异或0xFFFFFFFF) */
static uint32_t crc_table[256];

/* 初始化CRC32查找表 */
static void crc32_init_table(void) {
    for (uint32_t i = 0; i < 256; i++) {
        uint32_t crc = i;
        for (int j = 0; j < 8; j++) {
            crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320 : (crc >> 1);
        }
        crc_table[i] = crc;
    }
}

/* 计算CRC32校验值
 * data: 输入数据指针
 * len:  数据长度(字节)
 * 返回: 32位CRC校验值
 */
uint32_t crc32_compute(const uint8_t *data, size_t len) {
    uint32_t crc = 0xFFFFFFFF;
    for (size_t i = 0; i < len; i++) {
        uint8_t index = (uint8_t)(crc ^ data[i]);
        crc = (crc >> 8) ^ crc_table[index];
    }
    return crc ^ 0xFFFFFFFF;
}

/* 最小验证入口 */
int main(void) {
    crc32_init_table();
    
    /* 测试向量1:空数据 */
    uint32_t crc_empty = crc32_compute(NULL, 0);
    printf("空数据 CRC32: 0x%08X\n", crc_empty);
    
    /* 测试向量2:字符串 "123456789"(标准CRC32校验向量) */
    const char *test_str = "123456789";
    uint32_t crc_str = crc32_compute((const uint8_t *)test_str, 9);
    printf("\"123456789\" CRC32: 0x%08X\n", crc_str);
    
    /* 测试向量3:固件模拟数据(含帧头/地址/长度/负载) */
    uint8_t firmware_packet[] = {
        0xAA, 0x55,           /* 帧头 */
        0x00, 0x10,           /* 目标地址 */
        0x04, 0x00,           /* 数据长度=4 */
        0xDE, 0xAD, 0xBE, 0xEF /* 负载数据 */
    };
    uint32_t crc_pkt = crc32_compute(firmware_packet, sizeof(firmware_packet));
    printf("固件包 CRC32: 0x%08X\n", crc_pkt);
    
    return 0;
}

编译与运行

# 主机编译验证(需支持C99或更高版本)
gcc -std=c99 -Wall -o crc32_test crc32.c
./crc32_test

验证方法

程序运行后观察输出:

  • 空数据:CRC32值为 0x00000000(初值0xFFFFFFFF经最终异或后为0)
  • "123456789":CRC32值必须为 0xCBF43926,这是CRC32算法的标准校验向量,用于确认算法实现正确
  • 固件模拟包:记录输出值,在后续Bootloader案例中作为固件包完整性校验的基准值

若"123456789"的输出不是0xCBF43926,说明查表生成或计算逻辑有误,需检查多项式方向(0xEDB88320为0x04C11DB7的反射形式)及初值/异或处理。

MCU移植说明

在Bootloader工程中使用时,将crc32_init_table()在启动时调用一次,之后对接收到的固件包调用crc32_compute()即可。若MCU的Flash资源紧张,可将crc_table声明为const并预生成常量表,省去运行时初始化。