STM32 Flash 掉电保存数据:为什么直接写 Flash 会越写越慢还容易丢数据?从扇区擦除到双区轮换的工程做法

举报
yd_223794387 发表于 2026/11/04 09:11:42 2026/11/04
【摘要】 一、需求很常见,做法却不能照搬 RAMSTM32 需要在掉电后保留参数时,常见做法是把参数写进片内 Flash:校准系数、累计运行时间、用户设置、故障计数。这些数据平时很少改,掉电后必须还在,于是很多人第一反应是把 Flash 当成 EEPROM 用,哪里要改就往哪里写一个字。这个做法在演示程序里能跑起来,跑到产品上迟早出问题。Flash 和 RAM 的写规则完全不同:RAM 可以按字节反复...

一、需求很常见,做法却不能照搬 RAM

STM32 需要在掉电后保留参数时,常见做法是把参数写进片内 Flash:校准系数、累计运行时间、用户设置、故障计数。这些数据平时很少改,掉电后必须还在,于是很多人第一反应是把 Flash 当成 EEPROM 用,哪里要改就往哪里写一个字。这个做法在演示程序里能跑起来,跑到产品上迟早出问题。Flash 和 RAM 的写规则完全不同:RAM 可以按字节反复改写,Flash 只能把 1 写成 0,要把 0 变回 1 必须整扇区擦除;而且擦除次数有限,写操作还可能被掉电打断。直接按 RAM 的思路写 Flash,等于把这三个约束同时踩中。

二、先算清三笔账:擦除粒度、擦写寿命、掉电窗口

第一笔是擦除粒度。STM32 的 Flash 按扇区组织,擦除的最小单位是一个扇区,少则 16KB,多则 128KB,具体看型号。改一个 4 字节的参数,底层也要先把整个扇区擦掉,再把扇区里其他数据原样写回去。如果参数区和代码区共用一个扇区,一次擦除就可能把代码擦掉,所以参数必须单独占一个或两个扇区,绝不能和程序混放。

第二笔是擦写寿命。片内 Flash 的擦写次数通常是 1 万次量级。参数每秒写一次,一天就是 86400 次,几天就能把一个扇区写报废。就算每分钟写一次,一年也有 50 多万次,同样超过寿命。所以高频变化的数据不能直接落 Flash,要先在 RAM 里攒着,按变化量或者按固定周期批量写,或者只在掉电前、设置确认时写一次。写频率是设计时就要定下来的指标,不是写完再测的。

第三笔是掉电窗口。Flash 写操作不是原子的,写一个字需要几十微秒,擦一个扇区需要几百毫秒甚至更久。如果在擦除或者写入中途掉电,扇区里的数据可能是新旧混合的,读出来既不是旧值也不是新值。更麻烦的是,正在擦除的扇区如果掉电,下次上电可能读到全 1 或者校验失败。所以凡是掉电保存,都必须假设写操作会在任意时刻被打断,设计上要能区分写完和没写完。

三、双区轮换加 CRC:让掉电打断变得可检测、可恢复

工程上常用的做法是双区轮换加校验。准备两个参数扇区 A 和 B,每个扇区头部放版本号和 CRC 校验值,尾部放写完成标志。写参数时,先擦除较旧的那个扇区,把新参数连同递增的版本号和 CRC 一起写进去,最后写完成标志。上电时先读两个扇区,挑版本号更新、CRC 正确、完成标志完整的那个用;另一个扇区哪怕写了一半,也因为 CRC 不对或者标志不完整而被丢弃。这样掉电打断最多丢掉最新一次写入,旧参数还在,系统不会读到半新半旧的数据。

写 Flash 的代码本身也要守规矩:写之前解锁,写完立即上锁;擦除和写入期间关闭可能干扰的中断,或者至少保证写流程不被重入;写完后回读校验,CRC 不对就换区重写。下面的示例只表达流程,寄存器配置按所用型号的手册调整:

void SaveParam(uint32_t new_value)
{
uint32_t sector = SelectOlderSector();
Flash_Unlock();
Flash_EraseSector(sector);
Flash_ProgramWord(ParamAddress(sector), new_value);
Flash_ProgramWord(VersionAddress(sector), GetNextVersion());
Flash_ProgramWord(CrcAddress(sector), CalcCrc(new_value));
Flash_ProgramWord(DoneFlagAddress(sector), DONE_FLAG);
Flash_Lock();
}

这段代码里最容易漏的是最后的回读校验。写完不校验,等于默认 Flash 一定写对了;实际上电压跌落、写时序被打断都会让写入静默失败。回读校验失败时不要原地重试擦写同一个扇区,换到另一个扇区重写,避免在已经受损的扇区上反复消耗寿命。

四、三个工程坑

第一,频繁写同一个地址。把计数器每次加一都写 Flash,是最典型的寿命杀手。正确做法是计数在 RAM 里累加,达到阈值或者收到保存命令时再写一次,写之前先判断值确实变了,没有变化就不擦不写。

第二,擦除时不考虑执行位置。擦除操作执行期间,CPU 不能从正在擦除的 Flash 扇区取指。如果代码本身就跑在被擦的扇区里,擦除一开始程序就跑飞了。参数扇区必须和代码扇区分开,擦写函数要放到 RAM 里执行,或者确保执行代码位于另一个不受影响的存储区。

第三,只存一份数据还不校验。单份存储加无校验,掉电打断后读到错误参数,系统可能按错误校准值运行,比没有保存更危险。双区轮换的成本只是多一个扇区,换来的是掉电后总有一份完整可用的旧数据。校验值要用 CRC,不要只用简单的累加和,累加和发现不了字节顺序调换这类错误。

五、结论

Flash 掉电保存的关键不在写函数本身,而在三件事:单独扇区、控制写频率、掉电可恢复。先算擦除粒度和寿命,决定多久写一次;再用双区轮换加 CRC,让任意时刻掉电都有完整可用的旧数据;最后写完回读校验,失败换区重写。按这个流程做,掉电保存才算工程上合格,而不是演示程序里碰巧能跑。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。