SPI NOR Flash 擦除模式:从“先擦后写”讲起(新手友好版)
你有没有遇到过这种情况——想往 Flash 里写点新数据,结果发现旧数据还在“赖着不走”,甚至写进去的内容完全不对?
如果你用的是 SPI NOR Flash,那很可能是因为你跳过了一个关键步骤:擦除(erase)。
别急,这不是你的错。很多刚接触嵌入式存储的新手都会踩这个坑:以为 Flash 和 RAM 一样,可以直接覆盖写入。但事实是——Flash 有个铁律:只能把 1 变成 0,不能把 0 变回 1。
要翻盘?唯一的办法就是先“格式化”一下——也就是我们说的erase。
今天我们就来掰开揉碎地聊聊:SPI NOR Flash 的 erase 到底是怎么回事?为什么必须这么做?sector、block、chip 这些擦除方式到底有什么区别?代码该怎么写才安全?
一、为什么 Flash 必须“先擦后写”?
我们先从最底层说起:Flash 是怎么存数据的?
它不是内存,它是“带闸的电子牢房”
你可以把 SPI NOR Flash 的每个存储单元想象成一个微型电容+开关结构(专业术语叫浮栅晶体管)。它的状态靠“有没有电子被困在牢里”决定:
- 没电荷 → 导通 → 表示
1 - 有电荷 → 截止 → 表示
0
编程(Program)操作,就是给它加电压,强行把电子塞进去,实现1 → 0的转变。
但问题来了:一旦电子进去了,就出不来——除非你施加一个更强的反向高压,把它们“吸”出来。这个过程就是擦除(Erase)。
所以结论很清晰:
✅ 写入前必须确保目标区域全是
1(即 0xFF)
❌ 直接往已有0的地方写数据 = 白忙活 + 数据错乱
这就引出了一个核心原则:
🔑所有写入操作之前,必须先执行 erase
而且更麻烦的是:你不能只擦一个字节或一页。Flash 规定了一组“最小擦除单位”——比如 4KB、64KB,甚至整颗芯片。这就是所谓的erase 模式。
二、三种常见擦除模式:扇区、块、整片,怎么选?
不同的场景需要不同粒度的操作。SPI NOR Flash 提供了多种擦除方式,就像你清理房间时可以选择“擦桌子”、“清衣柜”还是“大扫除”。
1. 扇区擦除(Sector Erase)—— 精细手术刀
- 大小:通常是4KB
- 命令码:
0x20 - 地址要求:必须 4KB 对齐(低 12 位为 0)
- 耗时:约 300ms
- 适用场景:修改一小段配置、更新日志头、OTA 升级中的局部刷新
这是最常用的擦除方式,因为它够小、够灵活。
实际代码长什么样?
void spi_nor_sector_erase(uint32_t addr) { // 第一步:打开写权限(Flash 默认是锁住的) spi_write_cmd(WRITE_ENABLE_CMD); // 命令 0x06 // 第二步:发送擦除命令 + 地址 uint8_t cmd[4] = { SECTOR_ERASE_CMD, // 0x20 (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF }; spi_send(cmd, 4); // 第三步:等待完成(Flash 正在忙) while (read_status_reg() & STATUS_BUSY); }📌 关键点提醒:
- 不发Write Enable?命令会被无视。
- 地址没对齐?可能擦错地方或者失败。
- 不等 busy 结束就继续操作?读到的数据可能是垃圾。
2. 块擦除(Block Erase)—— 批量处理利器
当你需要清空一大片空间时,一个个扇区去擦太慢了。这时候就可以用更大的单位:Block。
常见的有两种:
| 类型 | 大小 | 命令 | 地址对齐 |
|------|------|--------|------------|
| 32KB Block | 32KB |0x52| 32KB 对齐(低 15 位为 0) |
| 64KB Block | 64KB |0xD8| 64KB 对齐(低 16 位为 0) |
举个例子:
你想升级固件,大小是 128KB。如果用 4KB 扇区擦,得调用 32 次sector erase。
但如果用 64KB block erase,只需要两次就够了!
不仅减少了 SPI 通信次数,还降低了 CPU 开销和总时间。
⚠️ 注意:不是所有芯片都支持 32KB 模式。具体看 datasheet!例如 Winbond W25Q128JV 支持,但某些型号就不行。
3. 整片擦除(Chip Erase)—— 格式化全场
顾名思义,就是一次性把整个 Flash 芯片的所有内容全部清空为0xFF。
- 命令:
0x60或0xC7(不同厂商略有差异) - 无需地址参数
- 耗时很长:典型值在 20~100 秒之间
- 不可逆操作
什么时候会用到?
- 出厂测试:验证芯片是否能正常工作
- 安全擦除:防止敏感信息泄露
- 恢复出厂设置:设备返修前彻底清除用户数据
一段典型的 chip erase 代码:
void spi_nor_chip_erase(void) { spi_write_cmd(WRITE_ENABLE_CMD); spi_write_cmd(CHIP_ERASE_CMD); // 如 0xC7 while (read_status_reg() & STATUS_BUSY); }🚨 高风险警告:
- 绝大多数情况下,运行时不该调用 chip erase!
- 某些芯片还需要特殊解锁序列(比如先写0x60, 再写0x90),防止误触发。
- 如果中途断电,Flash 可能处于损坏状态,再也无法使用。
三、这些模式背后的设计逻辑是什么?
你可能会问:干嘛搞得这么复杂?就不能统一成一种擦除方式吗?
其实这背后是硬件设计与性能之间的权衡。
为什么不能按字节擦?
因为每次擦除都要施加高电压(通常由内部电荷泵产生),会对周围电路造成干扰。如果允许任意位置擦除,控制逻辑会极其复杂,成本飙升。
所以工程师做了折中方案:
把存储空间划分为固定大小的“管理单元”,每次只能整块操作。
这样既能简化控制电路,又能保证可靠性和寿命。
擦除会影响 Flash 寿命吗?
当然会!
每颗 Flash 都有擦写寿命限制,一般在1万到10万次 P/E cycle(Program/Erase Cycle)之间。超过之后,可能出现:
- 数据保持能力下降(掉电后内容丢失)
- 编程失败率升高
- 擦除不干净(残留 0 导致写入异常)
因此,在实际开发中要特别注意:
✅避免频繁擦同一块区域
✅采用磨损均衡(Wear Leveling)策略分散压力
✅尽量合并小擦除为大擦除,减少总次数
例如你在做日志系统,不要每次都擦同一个扇区记录最新状态,而是轮流使用多个扇区。
四、实战避坑指南:那些年我们踩过的雷
❌ 错误1:忘了发 Write Enable
// 错误示范 spi_write_cmd(SECTOR_ERASE_CMD, addr); // 直接发擦除命令结果?命令被忽略。因为 Flash 出厂默认是“写保护”状态,必须先通过0x06命令开启写权限。
✅ 正确做法:每次 erase/program 前都要发Write Enable
❌ 错误2:地址没对齐
想擦地址0x100100的扇区?没问题,它是 4KB 对齐的(0x100100 & 0xFFF == 0)
但你想擦0x100123?不行!虽然物理上 Flash 控制器会自动向下取整到0x100000,但你可能误伤无辜数据。
✅ 建议:封装函数时加入地址校验:
if (addr & 0xFFF) return -EINVAL; // 非 4KB 对齐❌ 错误3:忙等太久导致系统卡死
while (status & BUSY) { // 啥也不干,死循环轮询 }如果 Flash 出现故障或电源不稳,可能永远不退出,整个系统就挂了。
✅ 改进方法:加入超时机制
uint32_t timeout = 100000; while ((read_status() & BUSY) && --timeout) { delay_us(1); } if (!timeout) { // 报错处理:超时 }❌ 错误4:多任务并发访问冲突
两个线程同时尝试写 Flash,怎么办?很可能出现:
- A 擦完准备写,B 又开始擦
- 最终数据混乱
✅ 解决方案:
- 使用互斥锁(Mutex)保护 Flash 访问
- 设计统一的 Flash 抽象层(FAL),对外提供安全接口
- 引入缓存机制,减少真实擦写频率
五、典型应用场景:OTA 固件升级中的 erase 流程
假设你要做一个 OTA 升级功能,流程大致如下:
- 接收新固件包 → 存入缓冲区
- 找到应用分区起始地址(如
0x00100000) - 计算涉及哪些 4KB 扇区
- 逐个扇区擦除
- 擦完后按页(256B)编程写入新数据
- 全部写完后校验 CRC
- 更新启动标志,重启生效
🔍 关键点再次强调:没有 erase,program 就是无效操作!
六、总结:掌握 erase,才算真正入门 Flash 管理
到现在你应该明白了:
- erase 是 Flash 的物理特性决定的刚需操作
- sector / block / chip 是不同粒度的擦除工具,各有用途
- 正确的流程永远是:write enable → send command → wait busy
- 滥用 erase 会缩短寿命,设计时要考虑 wear leveling
- 工程实践中必须防误操作、加校验、设超时
对于初学者来说,记住一句话就够了:
💡想写先擦,擦前使能,擦完等待
只要你不跳这三步,基本就不会出大问题。
下一步学什么?
当你已经熟练掌握 erase 操作后,可以继续深入以下几个方向:
🔧状态寄存器详解:了解 WIP(Write In Progress)、WEL(Write Enable Latch)等标志位
🔒写保护机制:如何锁定部分区域防止误改
🚀快速编程模式:Dual SPI / Quad SPI 如何提升写入速度
📦文件系统基础:如何基于 Flash 构建可靠的 LittleFS 或 SPIFFS
🔁双分区 OTA:利用两份固件镜像实现无缝升级
这些内容,都是建立在你对erase 机制深刻理解的基础上的。
如果你正在开发嵌入式项目,不妨现在就打开你的 Flash datasheet,查一下这几个问题:
1. 它的最小擦除单位是多少?
2. 支持哪些 block erase 模式?
3. chip erase 命令是什么?
4. 典型擦除时间多长?
动手查一遍,印象才会更深。
欢迎在评论区分享你的使用经验或踩过的坑,我们一起交流进步!