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(&param)) {
        // 读取失败(首次上电或数据损坏),使用默认值并回写
        param = default_param;
        Param_Save(&param);
    }

    // 此时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读写库,并确保代码段不占用所选参数页。

验证方法

将程序下载至开发板,按以下步骤验证:

  1. 首次上电测试:烧录程序后复位运行,通过调试器观察param变量,应等于default_param中的值(device_id=0x12345678, calib_coef=1.05, enable_flag=1)。
  2. 修改参数测试:在main中手动修改param(如device_id改为0xDEADBEEF),调用Param_Save(&param),然后按下复位键。再次运行后观察param,应恢复为修改后的值,证明Flash存储生效。
  3. 掉电保持测试:保存参数后断开电源,重新上电,参数应保持为上次保存的值。

判定标准:复位后读取到的参数与上次保存值一致,且首次上电时能自动写入默认参数,即视为通过。

注意事项

频繁擦写Flash会损耗寿命(STM32F103典型擦写次数1万次),实际产品中应避免每次关机都写入,可增加写入次数限制或仅在参数变化时保存。本案例为最小实现,未包含磨损均衡策略。

Flash参数存储方案结构 Flash 地址空间 代码区(主程序) 0x08000000 ~ 0x0800F7FF 参数存储页 0x0800F800 ~ 0x0800FFFF UserParam 结构体 magic (2B) checksum (2B) device_id (4B) calib_coef (4B) enable (1B) 启动恢复流程 系统上电复位 Param_Load() 读取Flash magic & checksum 有效? 是 使用 参数 否 使用默认值并Param_Save()回写 写入前必须先擦除整页 | 最小写入单位:半字(16bit) | 校验和覆盖data区域