背景
前些年在工作的时候,需要给一款车辆仪表的产品,从AVR平台升级到ST平台。当时遇到的问题是原先AVR芯片内部有EEPROM,但是ST芯片内部没有。产品需要动态保存数据,IO已经全部用完,没有多余的IO来外挂EEPROM芯片。后来发现ST官方提供了一种使用芯片内部Flash来模拟EEPROM的方案,尝试用在了产品上,表现挺稳定的。这种方案在汽车电子上有比较广泛的应用,在这里分享给大家。
为什么要用Flash模拟EEPROM?
首要原因,那肯定是因为穷。
- 因为Flash的空间可以做的比较大,一般我们的产品,不管是片内Flash或者外部的Flash,都不会把所有扇区全部用完,都有剩余的空间,如果能利用这部分空间就可以节省外部E2的成本。
- 有些产品对PCB空间要求非常高,没有多余的空间来外挂存储器,也可以省下PCB的空间。
例如:
- 报警器:就是设计成没有外部EEPROM,需要把内部Flash当做EEPROM使用,因为保存的都是配置参数,内部Flash的擦写寿命也能满足需求。
- 海外项目:因为要支持远程升级,所以设计成外挂一片EEPROM和一片Flash。如果能用Flash模拟EEPROM,就可以只要一片Flash就可以了。
Flash和EEPROM的差异
Flash(NOR FLASH)和EEPROM的特性有较大的差异,一般情况下不能直接等同使用,需要通过软件模拟来达到相同的效果。我们先来看一下他们的差异:
物理结构的不同
- EEPROM可以随机访问和修改任何一个字节,可以往每个bit写入0或者1。具有较高的可靠性,但是电路较复杂、成本较高。
- Flash不再以字节为最小单位,而是以块为最小单位,内部电路被设计成编程时只能将1写成0,而不能将0写成1。所以内部电路就简化了很多,数据密度可以做的更高,容量可以做的更大,成本也降低了。
擦除/编程时间的不同
-
EEPROM随机寻址编程周期5ms,无需擦除时间;
-
片内Flash双字(64bit)编程时间81us,页Page(2K)擦除时间22ms。(以STM32L476为例)
-
外部Flash单字节编程时间为8us,但是因为扇区较大(4KB),导致擦除时间达到100ms。
可以看到就编程速度而言,Flash的速度要远远高于EEPROM,很适用于在意外情况快速保存关键数据。但是Flash擦除需要较长时间,而EEPROM则不需要擦除。Flash的编程速度快,是建立在扇区已擦除的基础上。所以通过软件算法,尽可能减少擦除次数,就可以让Flash达到EEPROM一样的效率。
编程/擦除条件不同
- EEPROM和外部Flash,他们片内都有独立的CPU,一旦启动编程就无需MCU的CPU参与,只有电源掉电才会中断写操作,所以适当调整解耦电容的大小,就可以保证外部芯片的完整编程操作。
- 片内Flash的编程/擦除操作,需要CPU直接参与,受电源和CPU的影响。如果在编程/擦除期间CPU因为异常导致中断或者停止,会影响编程/擦除的正确性。
储存容量的不同
- 目前的EEPROM只能做到几十K或者几百K,很少有超过512K的。
- Flash一般都能做轻松到M级别的容量。
编程寿命的不同
- EEPROM的编程寿命最长,可达100w次,储存时间可达100年。
- 片内Flash一般寿命只有1w次。储存时间根据储存环境在10-30年之间
- 外部Flash编程寿命可以做到10w次,储存时间20年。
数据传输速度的不同
- EEPROM采用I2C通讯,标准速度为100K,高速为400K,如果是模拟可能速度更低。
- 外部Flash采用SPI,可以轻松达到M级别的传输速度,芯片最高支持104M的数据传输速度。
- 片内Flash的数据传输速度是CPU指令级别的。
使用Flash模拟EEPROM的方案
-
使用Flash模拟EEPROM的方式有很多种,这里只对比了其中的2种。
-
使用外部Flash和内部Flash只是介质上和一些性能上的差异,原理可以通用。
一般的Flash当做EEPROM的方案
把需要储存在EEPROM中的数据,直接储存在Flash扇区中。
为了避免在擦除/编程过程的异常导致写入数据失败,需要使用备份区域。这个方式和我们目前直接使用EEPROM一样。
优点
- 无需外挂EEPROM就可以实现掉电保存效果,充分利用了内部Flash空间。
- 如果使用的是内部Flash,因为内部Flash和RAM是统一编址,CPU可以直接访问Flash上的数据。因为参数储存的地址是确定的,可以通过取地址的方式读取参数。上电可以不用把参数从Flash中读取放到RAM中,在需要使用的时候,直接通过地址寻址的方式获取参数。Flash中的数据有编程保护,避免被意外修改。不会出像现在RAM中意外被破坏的问题。当需要修改变量时,从Flash中读取到RAM中,修改完成再写入Flash。这种方式很适合保存校准参数等一些不容易变动的厂内设置的参数。
缺点
- 寿命问题,Flash的擦写寿命远远不如EEPROM,直接这样存储必须考虑寿命问题。
- 因为Flash芯片的特性,任何一个变量的修改,都必须全部擦除,重新写一遍,会进一步降低Flash的寿命。
- 效率问题,每次的编程都需要先擦除,再写入,整个过程耗费较长时间,效率不如外部EEPROM,耗费较长时间会增加数据丢失的风险。
ST官方提供的实现Flash损耗均衡方案
ST官方提供了一种至少使用2个扇区来模拟EEPROM的方案。后来NXP也提供了模拟EEPROM的方案,实现方案有差异,但是原理是类似的。
实现原理
-
假设我们现在有4个变量需要动态储存,每个变量的类型都是uint16_t类型。
-
现在我们给每个变量,分配一个虚拟地址,虚拟地址要求每个变量对应的虚拟地址必须唯一,且不能为全FF(因为全FF是Flash擦除后的值)。假设约定虚拟地址是2字节组成,所以范围是0x0000~0xFFFE。
-
假设业务需求需要保存的参数顺序为:参数2→参数3→参数4→参数1→参数3→参数4→参数4→参数3
保存后数据在Flash的地址空间内的情况如图
在物理地址上,分别写入虚拟地址和参数值。和EEPROM中保存数据在固定的物理地址不一样,在Flash中保存数据的物理地址,每次都不一样,但是同一变量对应的虚拟地址是一样的,我们通过在物理地址中查询虚拟地址,来找到对应变量的值。 -
按照上面的思路,在Flash中储存数据,总会遇到Flash扇区存满的情况。存满后需要将Flash扇区擦除,才能重新写入数据。如果在擦除期间或者写入期间发生异常,将导致参数丢失。所以需要使用2个Flash扇区来保存数据。当发现当前扇区的剩余空间不足以写入下一次数据,就需要将数据迁移到另一个扇区。
在数据迁移的过程,需要根据参数虚拟地址列表,在原扇区中,从后往前查找最新的储存值,将其迁移到新的扇区,只有确保数据全部迁移到新扇区,才能擦除原扇区。2个扇区如此往复就完成了模拟EEPROM的效果。 -
上面描述的是写过程,在上电后需要读取Flash中的数据,必须从起始地址开始读,读到虚拟地址和参数值都是FF的时候,说明该地址是当前储存的最新地址,从该地址开始往前读取各个参数的最新值。
扇区的状态
前面提到了上电需要从扇区中读取最新的参数值,但是此时有2个扇区,该从哪个扇区读取数据呢?
ST提供的方案将每个扇区的前2个字节作为扇区的状态字节,将扇区分为3种状态:已擦除状态(状态值0xFFFF)、正在接收状态(状态值0xEEEE)、可用状态(状态值0x0000)
- 将当前正在写入数据的扇区的状态标记为可用状态(0x0000)
- 如果发生数据迁移,则先将新扇区状态标记为正在接收状态(0xEEEE),等待数据迁移完成后,将状态标记为可用状态(0x0000),然后将原扇区擦除,擦除后的状态自动变为已擦除状态(0xFFFF)
这里的状态切换巧妙地利用了Flash在不擦除的情况下,只能将数据位从1改为0的特点,来保存扇区在数据启动迁移的状态和迁移完成的状态。实现了Flash在不擦除情况下写2次数据,不产生错误的效果。
按照上面的每个扇区可能出现的3种状态,在2个扇区的情况下,上电后可能出现排列组合总共有9种情况
虽然有9种情况,但是可以归类一下:
正常情况下,不会出现2个扇区状态一样的情况。如果出现说明扇区异常,需要初始化扇区。
在所有的扇区中,有且只有1个扇区的状态是可用状态,才是正常状态。
当上电后发现一个扇区处于接收状态,且前一个扇区是可用状态,说明数据迁移过程中遇到异常,还未迁移完成,需要重新执行迁移操作。
总结
使用Flash模拟EEPROM的优缺点都很明显。
优点
-
使用这种方式最大的好处就是通过软件的方式延长了Flash的寿命,达到和E2一样的效果
以STM32L476为例,单个扇区大小2KB,储存字节数4字节(虚拟地址2字节+参数值2字节),扇区最大擦写次数1w,扇区数2个,可以实现1000w+次的使用寿命,比EEPROM正常的100w次要高出不少。
-
编程速度非常快,减少意外数据丢失发生的概率。扇区擦除的频次大大降低,而且不发生在写数据前,数据保存的效率大大提供。
-
不需要额外的器件成本,不增加PCB面积。
局限性
-
每次擦除需要耗费较长的时间(20ms),而且有一个点要注意:如果代码和数据在同一Bank中,内部Flash在擦除/编程期间,是不可读的,CPU无法取指令,导致CPU暂停,需要强制等待擦除/编程结束才能执行其他指令。
如果这期间发生的中断/异常事件没有得到执行,可能导致其他的问题。针对这个问题,STM32L476的设计成双Bank的方式,只要将代码和数据储存在不同的Bank,就可实现同步读写,但是低端的MCU或者国产的MCU,普遍都会有这个问题。
-
在iap升级的时候,如果代码和模拟EEPROM之间预留的扇区不足,新的固件可能覆盖到储存的扇区,导致出现数据丢失的问题。
按照这样的方式,编译器并不知道哪些地址用来模拟EEPROM,对于一些代码和数据界限把握不好的项目,可以告诉编译器模拟EEPROM的区域,编译器会帮我们管理这块区域。但是这样带来的代价就是,每次更新程序都会把模拟EEPROM区域的值初始化。
MDK的方式:const uint8_t para_flash_area[128*1024] __attribute__((at(0x08100000)));
IAR的方式:
#pragma location=0x08100000 const uint8_t para_flash_area[128*1024];
-
编程限制,比如STM32F1系列最小编程单位为字(2字节),且地址必须为偶数(2字节对齐),STM32L476最小编程单位为双字(8字节)且必须双字(8字节)地址对齐,可能带来操作的不方便和导致部分空间浪费。
-
官方的方案只针对uint16_t型的变量,储存其他类型需要自己拓展,而且没有校验的算法。
-
官方对上电后2个扇区如果状态一样,直接全部格式化。因为格式化后全部数据都清零了,其实数据中可能存在准确的数据,但是因为状态问题导致异常。可以尝试从数据区读取数据,做一定的数据还原规则。
-
因为数据储存的物理地址是不确定的,每次读取数据,都要从后往前查询,读取效率不如EEPROM,但是考虑到数据读取只在上电时执行,一般情况可以接受。
注意事项
- 模拟EEPROM的扇区可以定义从第2个扇区开始的任何一个扇区,但是不能定义在首扇区,因为这个扇区默认是Boot的启动地址,存放着系统的中断向量表。每次中断发生时都需要跳转到首地址查询中断向量,不可用于其他功能。
- 因为Flash擦除需要较长的时间,为了避免在保存数据时遇到存储区满,需要先擦除再存储的问题,要始终保证当前正在写入的扇区的下一扇区处于已擦除状态。
优化
-
一般我们保存的对象,都不会是单一类型,并且加入校验码。我们可以扩展一下,储存联合体对象,这样的做法是为了提高我们数据读取和数据迁移的效率,但是浪费了一些空间。
-
因为方案只提供了2个扇区来模拟EEPROM,为了损耗均衡,可以使用更多的扇区来模拟,模拟出来的扇区寿命可以成倍增加。
-
其实我们的产品中,不仅仅局限于运行一套这样的机制,只要Flash空间充足,我们可以运行多套这样的机制。比如针对容易变化的累积量、脉冲状态、故障记录,我们可以单独拿出来做一套这样的机制。这种情况也适合于参数和运行数据分开存储,因为运行数据更新频繁,会导致配置参数读取效率低。
-
因为只用了2个扇区,所以正常情况下,只有一个扇区处于可用状态,另一个扇区处于已擦除状态。如果我们使用更多的扇区来模拟,只需保证下一个扇区处于已擦除状态,其他扇区可以一直保持数据。利用这些历史数据,我们可以做数据的可靠性分析:
- 比如最新的数据出现异常,可以继续往前读取历史数据,保证数据不会出现较大的差异。
- 可以作为数据安全的依据,比如汽车的里程只可能增加不可能减少,表的累积量也只可能增加,不可能减少。
- 像之前其他项目组分享的,出现主区和备区的数据都异常,累积量会清零,后来改成主区、备区都异常直接卡死,等待人工上门处理。如果是模拟EEPROM的话,如果最后一次数据出现异常,可以继续往前读取,直到读取到正常的数据。数据值可能有偏差,但是不会出现直接清零的情况。可能有一定的改善效果。
-
因为有大量的历史数据,可以利用历史数据分析设备的一些行为,帮助我们定位bug。
总结
- 本文分享了一种利用Flash来模拟EEPROM的方法,利用Flash扇区空间大的特点,通过软件实现虚拟地址的方式,多次写入数据,来减少Flash擦除次数,规避了Flash擦写次数不足的问题。在扇区写满后做数据迁移操作,实现各个Flash扇区的损耗均衡。而且发挥编程速度快的特点,适用于在意外情况快速保存关键数据。达到和外部EEPROM一样的效果,节约了成本。
- 但是利用片内Flash一定要注意Flash擦除期间,CPU停止的问题。
- 每种方式都有各自的优缺点,没有最好的,只有合适的。可以将几种方式结合起来使用,让产品表现的更稳定。