微控制器的EEPROM上的磨损均衡


15

例如:ATtiny2313的数据表(与大多数Atmel AVR数据表一样)指出:

128字节系统内可编程EEPROM耐久性:100,000个写入/擦除周期

想象一个程序只需要两个字节来存储一些配置,其他126个字节实际上被浪费了。让我担心的是,两个配置字节的定期更新可能会使设备的EEPROM失效,并使其失效。整个设备将变得不可靠,因为在某个时刻您无法跟踪EEPROM中的哪些字节不可靠。

当您仅有效使用可用128个字节中的一个或两个字节时,是否存在一种明智的方法来对微控制器的EEPROM进行损耗均衡?


1
如果100k个写周期是一个约束,那么使用其他一些技术代替有意义吗?是内部整合了调平的机制,还是具有更高数量级或更高耐用性的某种机制?
Anindo Ghosh 2013年

1
@AnindoGhosh我只是不想浪费我的一小部分微控制器,只是因为我通过测试概念验证而使EEPROM耗尽了。我不想担心在重用控制器时我在上一个项目中一直使用哪个字节。知道我可以充分利用可用的硬件,真是太好了。
jippie 2013年

3
这可能会有所帮助:AVR101:高耐用性EEPROM存储
m.Alin

1
也许看看我对stackoverflow的回答。
JimmyB

看看TI的MSP430 FRAM系列... 10 ^ 13写道!!!
geometrikal

Answers:


19

我通常使用的技术是为数据添加一个4字节的滚动序列号,其中最大的数字代表最新的/当前的值。在存储2个字节的实际数据的情况下,总共6个字节,然后形成一个循环队列排列,因此对于128个字节的EEPROM,它将包含21个条目,并增加了21倍的耐力。

然后,启动时可以使用最大序列号来确定要使用的下一个序列号和队列的当前尾部。以下C伪代码演示,这是假定在初始编程时EEPROM区域已被擦除为0xFF的值,因此我忽略了0xFFFF的序列号:

struct
{
  uint32_t sequence_no;
  uint16_t my_data;
} QUEUE_ENTRY;

#define EEPROM_SIZE 128
#define QUEUE_ENTRIES (EEPROM_SIZE / sizeof(QUEUE_ENTRY))

uint32_t last_sequence_no;
uint8_t queue_tail;
uint16_t current_value;

// Called at startup
void load_queue()
{
  int i;

  last_sequence_no = 0;
  queue_tail = 0;
  current_value = 0;
  for (i=0; i < QUEUE_ENTRIES; i++)
  {
    // Following assumes you've written a function where the parameters
    // are address, pointer to data, bytes to read
    read_EEPROM(i * sizeof(QUEUE_ENTRY), &QUEUE_ENTRY, sizeof(QUEUE_ENTRY));
    if ((QUEUE_ENTRY.sequence_no > last_sequence_no) && (QUEUE_ENTRY.sequence_no != 0xFFFF))
    {
      queue_tail = i;
      last_sequence_no = QUEUE_ENTRY.sequence_no;
      current_value = QUEUE_ENTRY.my_data;
    }
  }
}

void write_value(uint16_t v)
{
  queue_tail++;
  if (queue_tail >= QUEUE_ENTRIES)
    queue_tail = 0;
  last_sequence_no++;
  QUEUE_ENTRY.sequence_no = last_sequence_no;
  QUEUE_ENTRY.my_data = v;
  // Following assumes you've written a function where the parameters
  // are address, pointer to data, bytes to write
  write_EEPROM(queue_tail * sizeof(QUEUE_ENTRY), &QUEUE_ENTRY, sizeof(QUEUE_ENTRY));
  current_value = v;
}

对于较小的EEPROM,3字节序列将更有效,尽管需要一点位切片而不是使用标准数据类型。


+1,不错的方法。是否可以通过使用较少的“标记”字节来稍微优化存储,并可能取决于某种形式的哈希存储桶机制来提供其他分配?没有找平和您的方法之间的混合?
Anindo Ghosh

@AnindoGhosh,是的,我相信可以。为了简化代码,我通常在小型Micros上使用此方法,而我个人主要在DataFLASH等较大的设备上使用了此方法。我想到的另一个简单的想法是,可以定期降低序列号以使其保持较小的值。
PeterJ 2013年

通过@ m.Alin提到Atmel的应用笔记有一个聪明的简化:复位后,然后可以通过看的[...]缓冲区,找到最后一个[...]缓冲器元素通过寻找位置发生改变,其中,缓冲区元素与下一个缓冲区元素之间的差大于1
jippie 2013年

不应该write_value()将条目放在queue_tail * sizeof(QUEUE_ENTRY)吗?我第一次是正确的,但是如果有多次写入,它是否应该继续前进?我不会在load_queue()之外递增。
马歇尔·尤班克斯

2
@ DWORD32:是的,这在技术上是正确的,但在实践中无关紧要。到发生这种情况时,EEPROM的磨损极限将超过2000倍!
戴夫·特威德

5

以下是一种使用存储桶的方法,每个存储桶大约有一个开销字节。桶字节和开销字节的磨损量大致相同。在当前示例中,给定128个EEPROM字节,此方法分配了42个2字节存储桶和44个状态字节,从而将磨损能力提高了约42倍。

方法:

划分EEPROM地址空间分成ķ水桶,其中ķ =⌊ ë /(Ñ +1)⌋,与Ñ =设定数据阵列大小=桶大小,和Ë = EEPROM大小(或者,更一般地,EEPROM的数量专用于此数据结构的单元)。

初始化目录,将m个字节的数组都设置为k,其中m = En·k。设备启动时,它会遍历目录,直到找到当前条目为止,该条目是不等于k的字节。[如果所有目录条目都等于k,则将第一个目录条目初始化为0,然后从那里继续。]

当前目录条目包含j时,存储桶j包含当前数据。当您需要写入新的设置数据条目时,请将j +1 存储到当前目录条目中;如果它等于k,则将下一个目录条目初始化为0,然后从那里继续。

注意,目录字节获得大约相同的磨损量作为桶字节,因为2· ķ > ķ

(我根据我对Arduino SE问题34189的回答改编了以上内容,即如何延长EEPROM的寿命?。)


2

我为此使用了滚动序列号(类似于Peter的回答)。如果提示中的元素数为奇数,则序列号实际上可以少至1位。然后用2个连续的1或0标记头部和尾部

例如,如果要滚动5个元素,则序列号为:

{01010}(写入0){11010}(写入1){10010}(写入2){10110}(写入3){10100}(写入4){10101}(写入5)


1

有两种选择,具体取决于您拥有的EEPROM类型和数据大小。

  1. 如果您的EEPROM具有可单独擦除的页面,并且您使用一页(或更多),则只需擦除除正在使用的页面之外的所有页面,然后以循环方式重复使用页面。

  2. 如果仅使用必须立即擦除的一部分页面,则将该页面划分为数据条目。每次编写时都使用一个干净的条目,用完所有干净的条目后将其擦除。

如有必要,请使用“脏”位在干净条目和脏条目之间进行区分(通常,您至少要保证一个字节与0xFF不同,该字节可用于跟踪脏条目)。

如果您的EEPROM库未提供擦除功能(如Arduino),这是算法2 的一个巧妙窍门:由于始终使用第一个EEPROM条目,因此可以通过读取它来确定“脏”位的值。然后,一旦您用完了干净的条目,就可以从第一个条目重新开始,将“脏”位取反,其余的条目会自动标记为“干净”。

序列号和目录会浪费空间,除非您希望能够独立跟踪不良页面或更新EEPROM数据的不同部分。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.