☰
低成本 MCU 没 EEPROM 怎么办?聊聊Flash 模拟实现思路全解析
2026/10/20 7:51:52 网站建设 项目流程
今天咱们来轻松聊聊嵌入式开发中一个超级实用的技术:用 Flash 模拟 EEPROM。这个话题其实挺接地气的,尤其当你在搞智能设备、传感器节点或者物联网项目时,经常会遇到存储小数据的难题。假设哈,你做了一个智能门铃,需要记住用户的设置偏好,比如门铃音量、WiFi 连接记录,还有开门次数统计。这些数据可能每天改几次,但一关机就得持久保存。要是芯片没有专门的 EEPROM 模块,怎么办?直接硬上 Flash?那可就头疼了。厂商们早就想到了这个痛点,提供了“Flash 模拟 EEPROM”的示例方案,让我们用软件巧妙地“骗过”硬件限制。今天我就从头到尾,把这个东西讲得明明白白,希望能帮到正在摸索的你。
前言
先说说为什么这个话题值得聊。嵌入式系统里,存储器是“灵魂”之一。程序代码、运行数据、用户配置,都得有个靠谱的地方放。早期的单片机(如老的 8051 系列)往往有 EEPROM,但随着芯片工艺的进步和成本压力增大,很多现代 MCU 选择了简化设计,只留大容量的 Flash。这不是偷懒,而是为了让产品更小、更省电、更便宜。比如在消费电子、工业控制或可穿戴设备中,这种设计很常见。但问题来了:小数据频繁更新时,Flash 的“脾气”就暴露了。咱们先从基础区别说起。
1、EEPROM 和 Flash 的主要区别
要理解模拟的必要性,得先搞清楚 EEPROM 和 Flash 到底哪里不一样。它们都是非易失性存储器,也就是关机后数据不丢的“持久存储”。但设计理念和实现方式天差地别。EEPROM 全称是 Electrically ErasableProgrammable Read-Only Memory,电可擦除可编程只读存储器;Flash 则是闪存,名字就暗示了它“闪电般”快速批量操作的特点。下面用表格直观对比一下,后面我再细说每个点。
项目
EEPROM
Flash(闪存)
最小擦除粒度
字节级(随便改一个字节,只动一个字节)
页/块级(几百字节到几 KB,一次擦一大片)
擦写循环寿命
通常 10 万 ~ 100 万次
通常几千 ~ 10 万次
存储容量
较小(几 KB ~ 几十 KB)
较大(几百 KB ~ 几 MB,甚至更大)
制造成本
较高
较低
读写速度
读快,写相对慢但灵活
读快,写批量高效但粒度粗
典型应用场景
频繁小数据更新(如配置参数、计数器、ID)
大容量存储(如程序固件、文件系统)



咱们一个个拆解。
1. 最小擦除粒度是最核心的区别。EEPROM 像个精密的橡皮擦,你想改哪个字节,就直接擦掉那个字节,重写新值,不会波及其他值。这得益于它的电路设计,每个存储单元都有独立的控制线路,能单独施加电压进行擦除。相比之下,Flash 为了提高密度和降低成本,采用了共享电路的方式——擦除时必须整页(或整块)一起操作。比如一页 2KB,你改 1 字节,就得先把整页擦成全 1(或全 0,取决于型号),然后再把整页数据(包括你没动的部分)重新编程写回去。这就像用大扫帚扫地,不能只扫一个角落,得整间屋子一起。
2. 擦写循环寿命也很关键。EEPROM 的寿命长,因为擦除操作精细,晶体管磨损小,能承受 10 万到 100 万次循环。Flash 呢?块级擦除让每个晶体管承受的压力大,寿命通常只有几千到 10 万次。实际项目中,如果你的计数器每天增 1,几年下来 Flash 就可能“老化”了。
3. 存储容量上,Flash赢在规模:它用更紧凑的工艺堆叠单元,能轻松做到几 MB 甚至更大,适合存海量数据如固件镜像。EEPROM 则受限于成本,容量小得多。制造成本方面,Flash 工艺成熟,量产便宜;EEPROM 需要额外的高压电路和浮栅结构,贵上不少。
4. 典型应用场景也反映了它们的“性格”。EEPROM 适合那些“琐碎但重要”的小活儿,比如遥控器里的按键映射、传感器里的校准表、汽车 ECU 里的里程记录。这些数据改动频繁,但总量不大。Flash 则主打“大而全”,像手机里的系统文件、U 盘里的照片视频。总的来说,EEPROM 是精细工匠,Flash 是高效搬运工。理解这些区**,你就明白为什么很多 MCU 要“模拟”了——硬件上省了 EEPROM,但软件得接棒。
2、为什么很多 MCU 需要 Flash 模拟EEPROM
现在说说模拟的背景和必要性。现代 MCU 的设计趋势是“高集成、低成本”。比如 ST 公司的 STM32G0 系列、NXP的 LPC 家族,或者国产的 GD32、APM32,它们更多的是主打物联网和低功耗市场。内置 EEPROM 会增加芯片的 die 面积(也就是硅片大小)和功耗,因为需要额外的电荷泵电路来产生擦除所需的高电压。这在电池供电的设备中是个大忌。所以厂商选择砍掉它,留更大的 Flash 给程序和数据。
但现实需求不会消失。例如这些场景:一个无线传感器节点,每小时更新一次环境数据(温度、湿度),存到非易失区;一个智能家居控制器,记录用户习惯如灯光亮度;甚至医疗设备里的配置日志。这些数据量小(往往不到 1KB),但修改频率高——可能一天几十次。如果没有模拟,直接操作 Flash,后果很严重:
-性能瓶颈:每次写 1字节,都要擦整页(假设 2KB),再重写 2KB 数据。擦除时间可能几毫秒,编程也慢。在实时系统中,这会卡顿。
-寿命隐患:Flash的擦写限额有限。假设一页能擦 1 万次,你每天改 10 次小数据,半年就到头了。设备坏了,用户得换。
-可靠性风险:擦除过程中掉电(比如电池没电),整页数据可能全毁。想想智能锁,配置丢了,用户得重配,多麻烦。
-开发成本:外部加独立EEPROM 芯片(如 I2C 接口的 24C02),又得多焊一个器件,增加 PCB 面积和 BOM 成本。
厂商看在眼里,提供“Flash 模拟EEPROM”的 Demo,就是为了解决这些痛点。
好处显而易见:成本零增加(用现有 Flash),兼容性高(API 像真 EEPROM:read/writebyte),寿命延长(通过算法,实际擦除次数少 100 倍以上)。在实际项目中,这能让产品寿命从几个月延长到几年。
除了实用性,这个技术还体现了嵌入式开发的精髓:软件补硬件短板。很多开源社区如GitHub 上,也有通用的模拟库,基于 ARM Cortex-M 或 RISC-V 平台。总之,模拟不是“临时工”,而是标准实践。
3、模拟的核心原理:软件如何“变魔术”
Flash 的硬件限制铁打不动:擦除必须整页。我们没法改这个物理规则,但可以绕道而行。核心原理就是“日志式存储”——像日记本一样,只追加不覆盖,读时智能查找,空间紧张时再大扫除。
简单比喻:Flash 是一本厚笔记本,规则是“只能整页撕掉重写,不能改单个字”。你想改“张三电话号码”这一行?别撕整页!直接在最后空白处写新一行:“张三电话:新号码 138xxxx”。旧的还留在那里,但我们逻辑上忽略它。读时呢?从笔记本最后往前翻,第一个找到的“张三电话”就是最新的。笔记本快写满时(比如只剩几行),你做一次“大清理”:把所有最新有效的条目(每个联系人只留一条)抄到另一本新笔记本上,然后把旧本子整页擦掉(这才真正执行一次 Flash 擦除)。以后就用新本子继续追加。

这个“追加写入 + 后向前查找 + 延迟擦除”的组合,是模拟的灵魂。为什么有效?因为 Flash 的擦除是最耗时的操作(几 ms 到几十 ms),而追加写入和查找相对快(微秒级)。假设一页能存 200 条记录(每条 10 字节),那你可能写 200 次小数据,才真正擦一次页。寿命放大倍数 = 页容量 / 条目大小,大概几十到几百倍。再用多页轮换(磨损均衡),就能轻松到百万级。
原理还有几个关键点:
-记录格式:每条数据不是裸字节,而是结构化:虚拟地址(用户看到的逻辑位置) + 数据值 + 状态标志(有效/无效) + 校验码(防出错)。这样读时能辨别新旧。
-从后往前扫:因为追加是顺序的,新记录总在页的末尾。从尾开始扫,能最快找到最新值,避免浪费时间。
-垃圾回收:不是实时擦,而是阈值触发(页剩 10% 空间时)。这推迟了昂贵操作。
-掉电保护:写记录时,先写地址和数据,最后写“有效”标志。启动时扫描,如果标志不对,就忽略半写记录。
不同实现有变体。两页版最简单(一个当前,一个备用);多页版(如 4 页)更均衡寿命,但代码稍复杂。高级版还加时间戳或序号,确保多版本共存时取最新。
4、典型的两页版实现流程:步步详解
咱们以两页版为例,假设 Flash 页大小 2KB,模拟区占最后 4KB(两页)。这个方案在 STM32、APM32 上很常见,代码量小,调试易。

1. 启动初始化
上电后,先扫描两页:检查哪页有有效记录(看头尾标志或擦除计数)。挑写偏移最大的当当前页(表示最近活跃)。记下基地址(比如 PAGE0 = 0x0801F000)和下一个写偏移(从页头算起)。如果两页都空,就擦干净初始化一个。如果掉电恢复,这里要重建内存映射表(用数组存最新值,加速后续读)。这一步确保系统重启后数据不丢。
2. 写数据流程
用户调用写函数,比如 EE_Write(virt_addr=0x05, value=123)。先判断当前页剩余空间:偏移 + 记录大小 < 页大小?有,就直接追加。构造记录:地址设为 0x05,值 123,状态0xAAAA(有效)。用 Flash 编程指令(寄存器操作)写入。更新偏移。没空间?别慌,先跳到垃圾回收。整个过程像“懒加载”:不急着擦,尽量追加。
3. 读数据流程
调用 EE_Read(virt_addr=0x05)。从当前页末尾(偏移处)往前逐条扫。每个记录检查:地址对吗?状态有效吗?是,就返回数据;不是,继续往前跳一个记录大小。为什么从后往前?因为新数据在后,先扫到它。没找到?返回默认值如 0xFFFF。这步超快,页小的话毫秒内搞定。实际项目中,为加速,可以建个 RAM 缓存表:启动时扫一次,之后读 O(1) 时间。
4. 垃圾回收流程
这是“关键”部分。当页满时:先从后往前扫描当前页,建一个临时表(数组或 map),每个虚拟地址只存最新值和状态。扫描完后,擦除备用页(设擦除寄存器,执行命令)。然后把临时表里的最新记录顺序写到备用页。切换指针:当前页变备用,偏移清零。整个过程可能花几 ms,但只在满时发生。为什么有效?因为一次回收对应了上百次逻辑写,摊薄成本。
5. 安全与优化小贴士
-防掉电:写记录分步:先数据,后状态。最后写状态,确保原子性。
-错误处理:加 CRC 校验,读时验证。出错就用备份页或默认值。
-寿命均衡:记录每页擦除次数,优先选少的页。
-扩展:小数据用小记录,大数据分片。实时系统禁用中断写。
这些设计让模拟接近真 EEPROM:可靠、灵活。
5、代码思路与关键示例(纯标准 C)
为了跨平台,我用纯标准 C 写思路。Flash操作用寄存器(看手册,如 APM32 的 FLASH->CR)。不贴全代码,只关键片段。
记录结构:
复制
  1. typedefstruct{
  2. uint16_taddr;// 虚拟地址
  3. uint16_tvalue;// 数据
  4. uint16_tstatus;// 0xAAAA 有效
  5. uint16_tpad;// 对齐
  6. } Record;
  7. #define REC_SIZE sizeof(Record)// 8 字节
  8. #define PAGE_SIZE 2048// 示例页大小
  9. #define EEPROM_BASE 0x0801F000UL// 调整为你的地址
  10. #define PAGE0 EEPROM_BASE
  11. #define PAGE1 (EEPROM_BASE + PAGE_SIZE)
读函数核心(从后往前扫):
复制
  1. uint16_tEE_Read(uint16_taddr) {
  2. uint32_tbase = current_base;
  3. uint32_tend = base + current_offset;
  4. uint32_tpos = end - REC_SIZE;
  5. while(pos >= base) {
  6. Record *r = (Record *)pos;
  7. if(r->addr == addr && r->status ==0xAAAA) {
  8. returnr->value;
  9. }
  10. pos -= REC_SIZE;
  11. }
  12. return0xFFFF;// 默认
  13. }


写思路:检查空间 → 追加(用 FLASH_Program 或寄存器写)→更新偏移。回收:收集表 → 擦页(FLASH->CR = PER | 页址)→ 写新 → 切换。
6、结尾小结:学这个值不值?
Flash 模拟 EEPROM 是嵌入式“资源有限下的智慧”。它不只解决存储痛点,还让你理解日志存储、磨损均衡这些高级概念。实际用它,项目更稳,开发更快。
本文到这里就结束了。
说实话,Flash 模拟 EEPROM这个技术虽然听起来有点“黑科技”,但用起来其实没那么玄乎。它本质上就是用软件的聪明劲儿,弥补了硬件的短板,让低成本 MCU 也能玩转那些需要频繁小改的持久数据场景。
感谢你看到这里,祝你的项目顺利,代码永不掉电~



需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询