1. 基于STM32的固件版本读取与校验

基于STM32的固件版本读取与校验 学习卡片

任务目标

在STM32平台上实现两个基础功能:读取当前固件版本号,并计算固件区域的CRC32校验值。该功能作为OTA升级的前置模块,用于版本比对与固件完整性验证。

实现方案

将版本号以字符串常量形式固化在Flash的固定地址段,通过链接脚本保证其位置确定。CRC32计算采用查表法,对内部Flash的固件区域(从起始地址到当前程序运行地址之前)进行校验。核心代码包含版本读取接口与CRC32计算接口,不依赖外部库。

关键完整代码

// firmware_info.h
#ifndef FIRMWARE_INFO_H
#define FIRMWARE_INFO_H

#include <stdint.h>

#define FW_VERSION_MAJOR  1
#define FW_VERSION_MINOR  0
#define FW_VERSION_PATCH  0

// 版本信息存放在Flash固定地址(由链接脚本保证)
#define FW_VERSION_ADDR   0x0800F000

typedef struct {
    uint8_t  major;
    uint8_t  minor;
    uint8_t  patch;
    uint8_t  reserved;
} fw_version_t;

void     fw_get_version(fw_version_t *ver);
uint32_t fw_calc_crc32(uint32_t start_addr, uint32_t length);

#endif // FIRMWARE_INFO_H
// firmware_info.c
#include "firmware_info.h"
#include <string.h>

// 版本号常量,放置在指定Flash地址
const fw_version_t fw_version __attribute__((section(".fw_version_section"))) = {
    FW_VERSION_MAJOR, FW_VERSION_MINOR, FW_VERSION_PATCH, 0
};

// CRC32查表法实现(多项式 0x04C11DB7)
static uint32_t crc_table[256];
static uint8_t  crc_table_ready = 0;

static void crc32_init_table(void) {
    for (uint32_t i = 0; i < 256; i++) {
        uint32_t c = i << 24;
        for (uint8_t k = 0; k < 8; k++) {
            c = (c & 0x80000000) ? (c << 1) ^ 0x04C11DB7 : (c << 1);
        }
        crc_table[i] = c;
    }
    crc_table_ready = 1;
}

void fw_get_version(fw_version_t *ver) {
    memcpy(ver, (const void*)FW_VERSION_ADDR, sizeof(fw_version_t));
}

uint32_t fw_calc_crc32(uint32_t start_addr, uint32_t length) {
    if (!crc_table_ready) {
        crc32_init_table();
    }
    uint32_t crc = 0xFFFFFFFF;
    const uint8_t *data = (const uint8_t*)start_addr;
    for (uint32_t i = 0; i < length; i++) {
        crc = (crc << 8) ^ crc_table[((crc >> 24) ^ data[i]) & 0xFF];
    }
    return ~crc;
}
// 链接脚本片段(.ld文件添加)
.fw_version_section 0x0800F000 :
{
    KEEP(*(.fw_version_section))
} > FLASH
// 调用示例
#include "firmware_info.h"

void ota_precheck(void) {
    fw_version_t ver;
    fw_get_version(&ver);
    
    // 计算固件区CRC(从0x08000000到当前代码之前)
    extern uint32_t _sidata;  // 由链接脚本提供
    uint32_t fw_end = (uint32_t)&_sidata;
    uint32_t crc = fw_calc_crc32(0x08000000, fw_end - 0x08000000);
    
    // 此时ver.major/minor/patch为版本号,crc为校验值
    // 可将两者通过串口或调试器输出
}

编译与运行

  1. 将上述代码加入STM32工程(如STM32CubeIDE或Keil),确保链接脚本包含.fw_version_section段定义。
  2. 编译工程,确认无错误。在链接映射文件(.map)中检查fw_version符号地址是否为0x0800F000。
  3. 烧录程序后,在ota_precheck()处设置断点,通过调试器查看ver结构体变量值应为1.0.0。
  4. 记录crc变量值,该值即为当前固件区域的CRC32校验结果。

验证方法

  • 版本读取验证:调试器中观察ver.major==1、ver.minor==0、ver.patch==0,与宏定义一致。
  • CRC32验证:将fw_calc_crc32计算的起始地址改为0x08000000、长度为0x1000(4KB),与PC端用相同算法计算该区域二进制文件的CRC值比对,结果应一致。
  • 确定性验证:连续两次调用fw_calc_crc32,返回值必须相同;修改Flash中任意一个字节后重新计算,CRC值应发生变化。

注意事项

  • 版本号地址0x0800F000需避开实际代码占用区域,建议放在Flash末尾前预留扇区。
  • CRC计算范围应排除版本号自身所在扇区(若包含则每次升级版本号变化会导致CRC变化,无法作为固件完整性依据)。
  • 若使用STM32CubeIDE,链接脚本修改后需重新生成工程或手动更新.ld文件。