3. Flash编程原理:STM32 Flash的擦除、写入、读取操作,以及编程时序和电压要求

STM32 内部 Flash 是固件升级、参数存储和启动引导的核心载体。理解其编程原理,是设计可靠 OTA 升级方案的前提。本章将深入剖析 STM32 Flash 的物理特性、擦除/写入/读取操作的本质区别、编程时序约束以及电压要求,帮助你在模型更新方案中规避底层风险。

Flash 存储单元 编程操作链路 时序与电压约束 Sector 0 16KB Sector 1 16KB Sector 2 16KB Sector 3 16KB Sector 4 64KB 擦除单位 擦除 (Erase) 按扇区/批量,置 0xFF 写入 (Program) 按字/半字,1→0 转变 读取 (Read) 直接寻址,零等待/等待周期 编程时序 FLASH_CR / FLASH_SR 寄存器 电压范围 VDD = 2.0V ~ 3.6V 编程电压需在范围内 等待周期 HCLK 频率决定 WS 0/1/2/3 等待周期 操作时间 擦除: ~20ms~2s 写入: ~几十 μs/字 关键寄存器 FLASH_CR / FLASH_SR BSY / PGERR / WRPRTERR 地址映射 约束

上图展示了 STM32 Flash 编程的三个核心维度:存储单元结构、操作链路和时序电压约束。下面逐一展开。

3.1 Flash 存储单元结构与操作本质

核心原理: STM32 内部 Flash 基于 NOR 型浮栅晶体管技术。每个存储单元通过控制栅和浮栅之间的电荷存储来表征逻辑状态。出厂时所有位均为逻辑 1(0xFF)。编程操作只能将 1 变为 0,而将 0 恢复为 1 的唯一方式是执行擦除操作。这一特性决定了 Flash 的读写策略:先擦除,后写入。

STM32 的 Flash 组织为多个扇区(Sector),不同型号扇区大小不同。以 STM32F103 系列为例,主存储区分为 4 个 16KB 扇区、1 个 64KB 扇区和 7 个 128KB 扇区。擦除操作的最小单位是扇区,而写入操作的最小单位是半字(16 位)或字(32 位),取决于具体型号和配置。

操作类型最小单位数据方向典型耗时注意事项
擦除 (Erase)扇区 (Sector)全部置 0xFF20ms ~ 2s(视扇区大小)擦除期间不可中断,否则数据损坏
写入 (Program)半字/字 (16/32bit)1 → 0 转变约 20~50μs/字写入前必须已擦除
读取 (Read)字节/半字/字直接寻址零等待或 1~3 等待周期与 CPU 时钟频率相关

关键认知: Flash 的“写”并不是覆盖写。如果你向一个已写入 0x5A 的地址再次写入 0xA5,结果不是 0xA5,而是 0x00(因为 0x5A & 0xA5 = 0x00)。因此,任何修改操作都必须先擦除整个扇区,再重新写入完整数据。

3.2 擦除操作:流程与风险控制

操作流程: STM32 标准库和 HAL 库均提供了封装好的擦除函数,但底层原理一致。以 HAL 库为例,擦除一个扇区的典型流程如下:

  1. 检查 Flash 是否处于忙状态(BSY 位)。
  2. 解锁 Flash 控制器(写入 KEY1 和 KEY2 到 FLASH_KEYR)。
  3. 设置 FLASH_CR 寄存器的 SER 位(扇区擦除)和选择扇区号。
  4. 设置 STRT 位启动擦除操作。
  5. 等待 BSY 位清零,检查 EOP(结束操作)标志。
  6. 重新锁定 Flash 控制器。

案例拆解: 某物联网设备在 OTA 升级时,需要擦除 128KB 的应用程序扇区。工程师使用 HAL_FLASHEx_Erase() 函数,但未检查返回值。结果在擦除过程中,系统因看门狗超时复位,导致 Flash 处于半擦除状态,固件损坏。复盘发现:擦除 128KB 扇区耗时约 1~2 秒,远超看门狗默认超时时间。解决方案是:在擦除前暂停看门狗,或使用独立看门狗并延长超时时间,同时增加擦除完成标志位。

风险提示: 擦除操作不可中断。如果在擦除过程中发生复位或掉电,该扇区可能处于不确定状态(部分 0、部分 1)。因此,OTA 设计中必须使用双备份方案或至少保留一个可用的引导扇区。

3.3 写入操作:编程时序与数据对齐

核心原理: 写入操作本质上是将数据“与”到目标地址。STM32 的 Flash 编程接口要求数据宽度为半字(16 位)或字(32 位),且地址必须对齐。写入时序由硬件自动控制,但软件必须遵循以下步骤:

// HAL 库写入示例(以 STM32F4 为例)
uint32_t address = 0x08020000;  // 目标地址
uint64_t data = 0x123456789ABCDEF0;
HAL_FLASH_Unlock();
FLASH_WaitForLastOperation(1000);  // 等待上次操作完成
FLASH_Program_Word(address, (uint32_t)data);
FLASH_WaitForLastOperation(1000);
HAL_FLASH_Lock();

时序要求: 写入操作期间,CPU 会暂停执行 Flash 中的代码(因为 Flash 控制器忙)。如果代码本身运行在 Flash 中,写入过程中会出现短暂的“卡顿”。对于实时性要求高的系统,应将写入函数放在 RAM 中执行,或使用 Flash 双体结构(如 STM32F7/H7 系列)。

参数最小值典型值最大值说明
VDD 电压2.0V3.3V3.6V低于 2.0V 时禁止编程
编程时间 (半字)—20μs50μs与温度、电压相关
擦除时间 (扇区)20ms—2s大扇区耗时更长
等待周期 (WS)0—3由 HCLK 频率决定

3.4 读取操作:等待周期与性能优化

核心原理: 读取 Flash 是 CPU 取指和数据的必经之路。STM32 的 Flash 接口支持预取缓冲和等待周期(Wait States)配置。当 CPU 主频提高时,Flash 读取速度跟不上 CPU 速度,必须插入等待周期。例如,STM32F103 在 72MHz 主频下需要 2 个等待周期,而 STM32F407 在 168MHz 下需要 5 个等待周期。

判断方法: 等待周期配置错误会导致程序运行异常或随机死机。正确做法是:在系统时钟初始化时,先设置 Flash 等待周期,再切换系统时钟源。HAL 库的 HAL_RCC_ClockConfig() 函数会自动处理这一顺序。

经验总结: 在 OTA 升级中,写入新固件时建议将 Flash 等待周期临时调大(如从 2 调到 3),以确保写入操作的稳定性。写入完成后再恢复原配置。虽然会略微降低性能,但能显著减少写入错误。

3.5 电压要求与低电压保护

核心原理: Flash 编程对供电电压有严格要求。VDD 低于 2.0V 时,Flash 控制器可能无法产生足够的编程电压,导致写入不完整或数据保持时间缩短。STM32 内置了 低电压检测器(LVD),当 VDD 降至阈值以下时,可产生复位或中断。

实战场景: 某便携设备使用锂电池供电,OTA 升级时电池电量不足,电压跌至 2.8V。虽然高于 2.0V 的最低要求,但写入过程中电池内阻导致瞬时压降,实际电压可能低于 2.0V,造成 Flash 写入失败。解决方案:

  • 升级前检查 VDD 电压,低于 3.0V 时禁止升级。
  • 写入期间关闭大电流外设(如射频模块、LED 驱动)。
  • 使用外部电容或电源管理芯片稳定电压。

严重警告: 在电压不稳定的情况下进行 Flash 编程,不仅可能导致本次写入失败,还可能缩短 Flash 的寿命(数据保持时间从 10 年降至数月)。对于关键设备,务必增加电压监测和掉电保护逻辑。

3.6 常见误区与复盘总结

误区后果正确做法
直接向已写入的地址再次写入数据变成两者相与的结果先擦除整个扇区再写入
忽略擦除时间,看门狗超时系统复位,Flash 半擦除擦除前暂停看门狗或延长超时
写入时未检查 BSY 标志写入冲突,数据错误每次操作前等待 BSY 清零
电压不足时强行编程写入失败,Flash 寿命缩短增加电压检测,低于阈值禁止操作
等待周期配置错误随机死机、程序跑飞时钟切换前正确配置 WS

复盘总结: Flash 编程看似简单,实则暗藏诸多陷阱。设计 OTA 升级方案时,必须将 Flash 操作视为一个完整的状态机:从解锁、擦除、写入、校验到重新锁定,每一步都要有超时保护和错误处理。建议在工程中封装统一的 Flash 驱动层,屏蔽底层寄存器操作,并提供以下接口:

  • Flash_EraseSector(sector_id) — 带超时和状态返回
  • Flash_WriteData(addr, buf, len) — 自动处理对齐和分页
  • Flash_ReadData(addr, buf, len) — 支持任意地址读取
  • Flash_Verify(addr, buf, len) — 写入后校验