STM32对函数进行CRC校验

STM32F4xx 内置了硬件 CRC 计算单元(CRC-32 以太网多项式 0x4C11DB7),可在运行期快速计算软件签名,与链接时生成并存放在指定存储单元的参考签名比较,用于验证 Flash 完整性。其计算方法为:复位 CRC 模块(置 CRC_CR=0x01,余数初始化为 0xFFFFFFFF),把数据按 32 位写入 CRC_DR,最后读回结果。

方案一:上电时对整个 Flash 区域做 CRC32 校验(最常用)

第 1 步:在程序 Flash 中预留 CRC 参考值存储区

把应用区起始地址、大小、CRC 参考值地址定义好。CRC 参考值通常由编译后处理工具在烧录时写入。

复制代码
#define FLASH_APP_START_ADDR  0x08002000U   /* 应用程序起始地址 */
#define FLASH_APP_SIZE        0x0003C000U   /* 应用程序大小 */
#define FLASH_CRC_REF_ADDR    0x0803E000U   /* CRC 参考值存储地址 */

第 2 步------用软件/硬件 CRC 计算 Flash 内容

复制代码
// 硬件 CRC 方式(利用 STM32F4 内置 CRC 外设)
uint32_t calculate_flash_crc(uint32_t *addr, uint32_t len_words)
{
    uint32_t crc;
    // 复位 CRC 单元
    CRC->CR = CRC_CR_RESET;
    for (uint32_t i = 0; i < len_words; i++) {
        CRC->DR = addr[i];
    }
    crc = CRC->DR;
    return crc;
}

或软件 CRC32(不依赖外设,便于 bootloader 使用):

复制代码
uint32_t calculate_crc32(uint32_t *data, uint32_t length) {
    uint32_t crc = 0xFFFFFFFF;
    for (uint32_t i = 0; i < length; i++) {
        crc ^= data[i];
        for (int j = 0; j < 32; j++) {
            crc = (crc & 0x80000000) ? (crc << 1) ^ 0x04C11DB7 : crc << 1;
        }
    }
    return crc;
}

第 3 步------上电校验并在跳转主应用前执行

复制代码
void verify_flash_integrity(void) {
    uint32_t stored_crc = *(volatile uint32_t *)FLASH_CRC_REF_ADDR;
    uint32_t calc_crc = calculate_crc32((uint32_t *)FLASH_APP_START_ADDR,
                                        FLASH_APP_SIZE / 4);
    if (calc_crc != stored_crc) {
        // CRC 不匹配!Flash 可能被位翻转/破坏,进入安全状态
        error_handler();
    }
    // 校验通过,正常跳转主应用
    jump_to_app();
}

校验函数必须在跳转到主应用之前调用,一旦校验失败就停止启动并进入安全状态。去除未使用的函数、保证整个 Flash 区域计算结果与参考值一致是方案要点。

方案二:只校验关键函数(针对性保护)

单粒子翻转通常是小概率、随机的,对全部 Flash 做完整校验耗时较长。可以只对关键函数(如安全关断、中断处理)所在段做 CRC 保护 ,配合上文的 __attribute__((section)) 分段机制:

  1. 把关键函数放入单独段(如 .func_crc);

  2. 用链接脚本的 __START__/__END__ 符号标记该段起止地址;

  3. 用同段内 CRC 与该段的参考值比对。

    关键函数定位到固定段 →
    extern uint32_t __func_crc_start, __func_crc_end;
    uint32_t len_words = (uint32_t)&__func_crc_end - (uint32_t)&__func_crc_start;
    crc = calculate_crc32((uint32_t*)&__func_crc_start, len_words);

方案三:运行时周期自检(持续防护)

单粒子翻转可能在上电后的运行中发生,因此周期性(由定时器触发)对运行代码区重新计算 CRC 比对,是更完整的防护。注意:

  • 自检期间避免同时执行被校验区代码,或选用短小的临界区段;
  • 校验本身使用的函数应放在 CRC 保护区之外,避免"以被篡改的代码验证被篡改的代码";
  • 翻转本质是位翻转检测,检测到后应转入安全状态(停机、进入安全模式、复位),因为多数场合无法在运行中纠正 Flash 内容(需要重新烧写/从备份区恢复)。

关键注意事项汇总

  1. 参考值需与内容严格一致:CRC 参考值必须是在链接后基于最终烧录内容的计算结果,否则无法匹配------一般由编译后脚本(如 srec_cat、Python 脚本)在生成 bin 后计算并追加到预留地址。
  2. 硬件 CRC 速度优势:STM32F4 硬件 CRC 每 4 个 AHB 周期完成一次 32 位输入计算,远快于软件实现,适合运行期自检。
  3. KEEP/满引用函数 :固定分段函数若未被调用需 __attribute__((used)) 或 KEEP 保留,否则链接器会剔除,导致地址/CRC 不匹配。
  4. 覆盖范围 :CRC 校验应覆盖 .text + .rodata + .data 初始值 等程序区,以及关键配置数据,避免遗漏。
  5. 无法自修复 :SEU 翻转基本是破坏性的(可擦除 Flash 但难以原位纠错),所以工程上通常是检测并进入安全态 ,配合备份机制(双分区 A/B、从备份区恢复)提升可靠性。
相关推荐
FPGA小徐1 小时前
STM32引脚详解:PC13-TAMPER-RTC 多功能复用引脚完全剖析(踩坑+配置+布线)
单片机·嵌入式硬件
M78佐菲1 小时前
ARM学习笔记(9)
linux·arm开发·笔记·嵌入式硬件·学习
铅笔小新z2 小时前
【stm32】中断与异常
stm32·单片机·嵌入式硬件
沐欣工作室_lvyiyi2 小时前
无线遥控电动阀门开关模块设计(论文+源码)
单片机·无线遥控
番茄灭世神3 小时前
PN学堂GD32教程第11篇——I2C与EEPROM
c语言·stm32·单片机·嵌入式·gd32
阿钱真强道3 小时前
27 嵌入式操作系统 | C 网关:串口 ⇄ UDP 转发(先用 UDP 当上位机替身)
c语言·单片机·网关·udp·uloop
沐欣工作室_lvyiyi3 小时前
基于STM32的便捷式智能气象仪设计(论文+源码)
stm32·单片机·嵌入式硬件·单片机毕业设计·电子信息工程毕业设计·河北工程技术学院·廊坊师范学院
iCxhust4 小时前
STM32F103C8T6 CAN 通讯
stm32·单片机·嵌入式硬件
铅笔小新z4 小时前
【stm32】SPI Flash 驱动开发
驱动开发·stm32·嵌入式硬件