目录
[一、Flash 基础核心特性](#一、Flash 基础核心特性)
[二、Flash 分区规划(IAP 必备)](#二、Flash 分区规划(IAP 必备))
[三、Flash 基础读写代码](#三、Flash 基础读写代码)
[五、IAP 固件升级 Flash 操作逻辑](#五、IAP 固件升级 Flash 操作逻辑)
[六、Flash 开发高频坑点](#六、Flash 开发高频坑点)
[七、Flash 寿命优化策略](#七、Flash 寿命优化策略)
前言
本篇我们攻克嵌入式量产开发核心存储能力 ------ 片内 Flash 操作。
几乎所有量产设备都离不开 Flash 应用:设备参数掉电保存、校准数据存储、日志记录、OTA 固件升级、开机配置固化。
很多新手对 Flash 认知只有简单读写,实际项目中频繁遇到各种疑难问题: 直接写 Flash 不擦除导致写入失败;频繁擦写损耗 Flash 寿命;跨扇区存储数据异常;IAP 分区规划不合理引发固件覆盖;程序运行时擦 Flash 造成卡死。
Flash 有一条铁则:只能将 bit1 改成 bit0,不能直接把 bit0 改成 bit1;想要恢复 bit1,必须执行扇区擦除。
一、Flash 基础核心特性
- 存储单元最小操作:按半字 / 字 / 双字写入,按扇区擦除
- 擦除后扇区所有数据为 0xFF
- 擦写次数有上限,STM32 片内 Flash 一般万次级别
- 程序运行时,可读写同芯片 Flash,但擦写期间禁止访问被操作扇区代码
- 掉电瞬间操作 Flash,极易造成存储数据损坏
二、Flash 分区规划(IAP 必备)
以 512KB Flash 芯片举例
- 0x08000000 ~ 0x08002FFF:Bootloader 分区,IAP 升级引导程序
- 0x08003000 ~ 0x0800BFFF:App 分区,设备业务主程序
- 0x0800C000 ~ 0x0800CFFF:参数存储分区,保存配置、校准参数
业务代码和参数分区物理隔离,升级固件不会覆盖设备配置
三、Flash 基础读写代码
#include "stm32f10x.h"
//Flash擦除单个扇区
uint32_t Flash_EraseSector(uint32_t addr)
{
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
uint32_t ret = FLASH_ErasePage(addr);
FLASH_Lock();
return ret;
}
//写入半字(2字节)
uint32_t Flash_WriteHalfWord(uint32_t addr, uint16_t data)
{
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
uint32_t ret = FLASH_ProgramHalfWord(addr, data);
FLASH_Lock();
return ret;
}
//批量写入多字节数据
uint32_t Flash_WriteBuf(uint32_t start_addr, uint8_t *buf, uint16_t len)
{
uint16_t i = 0;
uint32_t res;
if(len % 2 != 0) return 1;
while(i < len)
{
uint16_t temp = (buf[i+1] << 8) | buf[i];
res = Flash_WriteHalfWord(start_addr + i, temp);
if(res != FLASH_COMPLETE) return res;
i += 2;
}
return FLASH_COMPLETE;
}
//读取Flash数据,直接指针访问
uint8_t Flash_ReadByte(uint32_t addr)
{
return *(volatile uint8_t*)addr;
}
四、参数掉电存储工程方案
不要每次修改参数就擦写 Flash,频繁擦写快速损耗寿命。
方案:内存缓存参数,定时保存 / 修改确认后再写入 Flash。
#define PARAM_ADDR 0x0800C000
typedef struct
{
uint16_t para1;
uint16_t para2;
uint32_t crc;
}param_t;
param_t g_param_cache;
//保存参数到Flash
void Param_Save(void)
{
Flash_EraseSector(PARAM_ADDR);
g_param_cache.crc = crc16_calc((uint8_t*)&g_param_cache, sizeof(param_t)-4);
Flash_WriteBuf(PARAM_ADDR, (uint8_t*)&g_param_cache, sizeof(param_t));
}
//开机加载参数
void Param_Load(void)
{
param_t *p = (param_t *)PARAM_ADDR;
uint16_t crc_check = crc16_calc((uint8_t*)p, sizeof(param_t)-4);
if(crc_check == p->crc)
{
memcpy(&g_param_cache, p, sizeof(param_t));
}
else
{
//校验失败,加载默认参数
g_param_cache.para1 = 100;
g_param_cache.para2 = 200;
}
}
增加 CRC 校验,防止掉电半写导致参数损坏。
五、IAP 固件升级 Flash 操作逻辑
- Bootloader 运行,检测是否存在新固件
- 将收到的固件包写入 App 备份 Flash 分区
- 固件全部接收完成,校验整个固件 CRC
- 校验通过,擦除原 App 分区,将备份固件拷贝到 App 区域
- 跳转 App 程序;校验失败,保留旧程序,不执行覆盖
六、Flash 开发高频坑点
- 忘记解锁 Flash,直接擦写,操作全部失败
- 不做 CRC 校验,掉电中断写入,参数变成乱码
- 循环频繁擦写同一扇区,Flash 提前磨损失效
- 擦写 Flash 时,代码在当前扇区运行,程序直接 HardFault
- 地址没有对齐,写入半字 / 字时报错
- 分区规划重叠,升级时误擦除 Bootloader,设备变砖
七、Flash 寿命优化策略
- 减少擦除次数,参数改动批量一次性写入
- 循环轮换多个备份扇区,分散擦写压力
- 重要参数做双备份,A 扇区主存,B 扇区备用
- 禁止在循环内反复执行 Flash 擦写
小结
片内 Flash 操作核心就三点:遵守硬件读写规则、合理分区、增加校验与备份机制。
单纯读写只是基础,量产项目重点在于保护数据安全、控制擦写损耗,避免设备变砖与参数丢失。