4. 片内Flash存储用户参数
任务说明
本案例面向STM32F103系列单片机(内置Flash容量≥64KB),实现用户参数(如设备ID、校准系数、使能标志)的掉电保存。要求:将参数写入指定Flash扇区,系统启动时自动读取并恢复;若参数无效(首次上电或数据损坏),则使用默认值并回写Flash。
存储方案设计
STM32F103的Flash按页(Page)组织,每页1KB(中容量)或2KB(大容量)。本案例选用主Flash末尾区域(如地址0x0800F800,即最后一页)作为参数存储区。写入前需先擦除整页,再写入数据。为检测数据有效性,在参数结构体头部添加2字节魔数(0xA5A5)和2字节校验和(对参数区逐字节累加)。
代码实现
#include "stm32f10x.h"
#include <string.h>
// ---------- 参数结构体定义 ----------
#define PARAM_MAGIC 0xA5A5
#define PARAM_PAGE_ADDR 0x0800F800 // 最后一页起始地址(需根据芯片Flash大小调整)
#define PARAM_PAGE_SIZE 2048 // 页大小(大容量型号为2KB)
typedef struct {
uint16_t magic; // 魔数,用于识别有效参数
uint16_t checksum; // 校验和(对data区域逐字节累加)
uint32_t device_id; // 设备ID
float calib_coef; // 校准系数
uint8_t enable_flag; // 功能使能标志
} UserParam;
// ---------- Flash操作函数 ----------
// 擦除指定页(地址需页对齐)
void Flash_ErasePage(uint32_t page_addr) {
FLASH_Unlock();
while (FLASH_ErasePage(page_addr) != FLASH_COMPLETE);
FLASH_Lock();
}
// 写入半字(16位)
void Flash_WriteHalfWord(uint32_t addr, uint16_t data) {
FLASH_Unlock();
while (FLASH_ProgramHalfWord(addr, data) != FLASH_COMPLETE);
FLASH_Lock();
}
// ---------- 参数存储与读取 ----------
// 计算校验和(对param结构体中data区域累加)
uint16_t CalcChecksum(const UserParam *param) {
uint16_t sum = 0;
const uint8_t *p = (const uint8_t *)¶m->device_id;
for (uint8_t i = 0; i < sizeof(UserParam) - 4; i++) { // 跳过magic和checksum
sum += p[i];
}
return sum;
}
// 将参数写入Flash指定页
void Param_Save(const UserParam *param) {
UserParam temp = *param;
temp.magic = PARAM_MAGIC;
temp.checksum = CalcChecksum(&temp);
Flash_ErasePage(PARAM_PAGE_ADDR);
const uint16_t *p = (const uint16_t *)&temp;
uint16_t words = sizeof(UserParam) / 2;
for (uint16_t i = 0; i < words; i++) {
Flash_WriteHalfWord(PARAM_PAGE_ADDR + i * 2, p[i]);
}
}
// 从Flash读取参数,成功返回1,失败返回0
uint8_t Param_Load(UserParam *param) {
memcpy(param, (void *)PARAM_PAGE_ADDR, sizeof(UserParam));
if (param->magic != PARAM_MAGIC) return 0;
if (param->checksum != CalcChecksum(param)) return 0;
return 1;
}
// ---------- 应用示例 ----------
int main(void) {
UserParam param;
UserParam default_param = {
.magic = PARAM_MAGIC,
.checksum = 0,
.device_id = 0x12345678,
.calib_coef = 1.05f,
.enable_flag = 1
};
// 启动时尝试读取参数
if (!Param_Load(¶m)) {
// 读取失败(首次上电或数据损坏),使用默认值并回写
param = default_param;
Param_Save(¶m);
}
// 此时param中即为恢复后的用户参数
// 应用代码中使用param.device_id, param.calib_coef, param.enable_flag...
while (1) {
// 主循环
}
}
关键说明
- 地址选择:PARAM_PAGE_ADDR必须指向实际存在的Flash页且不与其他代码/数据冲突。对于64KB容量的STM32F103RBT6,Flash地址范围0x08000000~0x0800FFFF,最后一页起始地址为0x0800F800(2KB页)。若芯片不同,需按数据手册调整。
- 写入限制:Flash只能将1写为0,因此写入前必须整页擦除(擦除后全为0xFF)。写入操作以半字(16位)为最小单位。
- 校验机制:magic字段用于识别是否写入过有效参数;checksum用于检测数据是否损坏(如部分写入或位翻转)。
- 编译选项:需在工程中启用Flash读写库,并确保代码段不占用所选参数页。
验证方法
将程序下载至开发板,按以下步骤验证:
- 首次上电测试:烧录程序后复位运行,通过调试器观察param变量,应等于default_param中的值(device_id=0x12345678, calib_coef=1.05, enable_flag=1)。
- 修改参数测试:在main中手动修改param(如device_id改为0xDEADBEEF),调用Param_Save(¶m),然后按下复位键。再次运行后观察param,应恢复为修改后的值,证明Flash存储生效。
- 掉电保持测试:保存参数后断开电源,重新上电,参数应保持为上次保存的值。
判定标准:复位后读取到的参数与上次保存值一致,且首次上电时能自动写入默认参数,即视为通过。
注意事项
频繁擦写Flash会损耗寿命(STM32F103典型擦写次数1万次),实际产品中应避免每次关机都写入,可增加写入次数限制或仅在参数变化时保存。本案例为最小实现,未包含磨损均衡策略。