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(¤t) == 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,说明当前版本低于新版本,需要升级
}
}
}
编译与运行
- 在STM32CubeMX中启用
FLASH外设,生成初始化代码 - 将
version_mgr.c加入工程,包含version_mgr.h头文件路径 - 确认链接脚本中
0x0800FC00地址未被代码或数据占用(检查.icf或.ld文件Flash区间) - 编译下载后,在
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总容量