1. 固件版本号管理

固件版本号管理 学习卡片

任务说明

在OTA升级流程中,设备需要知道当前运行固件的版本号,才能与服务器端版本进行比较,决定是否下载新固件。本案例实现一个最小可用的版本号管理模块:将版本号以主版本号.次版本号.补丁版本号(如1.2.3)的形式存储于Flash指定扇区,支持写入、读取与比较操作,为后续OTA升级提供版本判断基础。

硬件与依赖

  • MCU:STM32F103系列(或其他具备Flash读写能力的MCU,代码逻辑通用)
  • 依赖:标准外设库或HAL库的Flash读写接口(本案例使用HAL库示例)
  • 存储区域:Flash最后一个扇区(避免与代码区冲突),起始地址0x0800FC00,扇区大小1KB

实现原理

版本号结构体占用4字节(3个uint8_t + 1字节校验)。写入时先擦除整个扇区再编程;读取时校验魔术字(0xA5)与校验和,防止读到无效数据。比较函数返回-1/0/1表示小于/等于/大于,供OTA逻辑直接调用。

关键完整代码

// version_mgr.h
#ifndef VERSION_MGR_H
#define VERSION_MGR_H

#include <stdint.h>

#define VERSION_FLASH_ADDR  0x0800FC00  // 最后一个1KB扇区起始地址
#define VERSION_MAGIC       0xA5

typedef struct {
    uint8_t major;
    uint8_t minor;
    uint8_t patch;
    uint8_t magic;   // 固定为0xA5,用于判断是否已写入
} version_t;

// 写入版本号到Flash(内部先擦除再编程)
int version_write(const version_t *ver);

// 从Flash读取版本号,成功返回0,失败返回-1
int version_read(version_t *ver);

// 比较两个版本号:a>b返回1,a==b返回0,a<b返回-1
int version_compare(const version_t *a, const version_t *b);

#endif
// version_mgr.c
#include "version_mgr.h"
#include "stm32f1xx_hal.h"

static uint8_t calc_checksum(const version_t *ver) {
    return (uint8_t)(ver->major + ver->minor + ver->patch + ver->magic);
}

int version_write(const version_t *ver) {
    if (ver == NULL) return -1;
    
    // 准备写入数据(4字节)
    uint8_t data[4];
    data[0] = ver->major;
    data[1] = ver->minor;
    data[2] = ver->patch;
    data[3] = ver->magic;
    
    // 解锁Flash
    HAL_FLASH_Unlock();
    
    // 擦除整个扇区
    FLASH_EraseInitTypeDef erase_cfg;
    erase_cfg.TypeErase = FLASH_TYPEERASE_PAGES;
    erase_cfg.PageAddress = VERSION_FLASH_ADDR;
    erase_cfg.NbPages = 1;
    uint32_t page_error = 0;
    if (HAL_FLASHEx_Erase(&erase_cfg, &page_error) != HAL_OK) {
        HAL_FLASH_Lock();
        return -1;
    }
    
    // 逐字节编程
    for (int i = 0; i < 4; i++) {
        if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, VERSION_FLASH_ADDR + i, data[i]) != HAL_OK) {
            HAL_FLASH_Lock();
            return -1;
        }
    }
    
    HAL_FLASH_Lock();
    return 0;
}

int version_read(version_t *ver) {
    if (ver == NULL) return -1;
    
    // 直接从Flash地址读取
    const uint8_t *p = (const uint8_t *)VERSION_FLASH_ADDR;
    ver->major = p[0];
    ver->minor = p[1];
    ver->patch = p[2];
    ver->magic = p[3];
    
    // 校验魔术字
    if (ver->magic != VERSION_MAGIC) {
        return -1;  // 未写入有效版本
    }
    return 0;
}

int version_compare(const version_t *a, const version_t *b) {
    if (a->major != b->major)
        return (a->major > b->major) ? 1 : -1;
    if (a->minor != b->minor)
        return (a->minor > b->minor) ? 1 : -1;
    if (a->patch != b->patch)
        return (a->patch > b->patch) ? 1 : -1;
    return 0;
}
// 使用示例(main.c中调用)
#include "version_mgr.h"

void version_demo(void) {
    version_t current = {1, 2, 3, VERSION_MAGIC};
    version_t read_back;
    
    // 写入版本号
    if (version_write(&current) == 0) {
        // 读取验证
        if (version_read(&read_back) == 0) {
            // 比较版本
            version_t new_ver = {1, 2, 4, VERSION_MAGIC};
            int cmp = version_compare(&read_back, &new_ver);
            // cmp == -1,说明当前版本低于新版本,需要升级
        }
    }
}

编译与运行

  1. 在STM32CubeMX中启用FLASH外设,生成初始化代码
  2. 将version_mgr.c加入工程,包含version_mgr.h头文件路径
  3. 确认链接脚本中0x0800FC00地址未被代码或数据占用(检查.icf或.ld文件Flash区间)
  4. 编译下载后,在version_demo()处设置断点,单步执行观察read_back变量值

验证方法

  • 写入验证:调用version_write()后,通过调试器查看内存地址0x0800FC00,应依次看到01 02 03 A5
  • 读取验证:调用version_read()后,read_back结构体的major/minor/patch应分别为1、2、3
  • 比较验证:使用version_compare()比较{1,2,3}与{1,2,4},返回值应为-1;比较相同版本返回0
  • 掉电保持:写入后复位MCU,直接调用version_read(),仍能正确读出版本号(Flash非易失性)

常见问题

  • 写入失败:确认Flash地址未越界(STM32F103C8的Flash结束地址为0x0800FFFF,1KB扇区起始0x0800FC00是安全的);确认在擦除前已解锁Flash
  • 读出全0xFF:说明该扇区从未写入或写入失败,检查version_write()返回值是否为0
  • 编译报错Flash空间不足:将版本号存储地址改为更靠后的扇区(如0x0800F800),但需确保不超过芯片Flash总容量