微控制器EEPROM耗损均衡是否有通用算法?

时间:2012-05-19 17:42:26

标签: arduino avr eeprom

我正在开发一个Arduino库,它可以最大限度地延长AVR EEPROM的使用寿命。它需要您想要存储的变量数量,其余部分。这是我的尝试,在所有情况下都不起作用。

背景资料

Atmel表示,每个存储器单元的额定写入/擦除周期为100,000次。它们还提供application note,其中描述了如何进行磨损均衡。以下是应用笔记的摘要。

通过在两个存储器地址之间交替写入,我们可以将擦除/写入增加到200,000个周期。三个存储器地址为您提供300,000个擦除/写入周期,依此类推。要自动执行此过程,将使用状态缓冲区来跟踪下一次写入的位置。状态缓冲区的长度也必须与参数缓冲区的长度相同,因为也必须对其进行磨损均衡。由于我们无法存储下一个写入的索引,因此我们在状态缓冲区中增加相应的索引。

这是一个例子。

   <------------------- EEPROM -------------------->  
   0                                               N
   -------------------------------------------------
       Parameter Buffer    |     Status Buffer     |
   -------------------------------------------------

   Initial state.
   [ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 ]

   First write is a 7. The corresponding position
   in the status buffer is changed to previous value + 1.
   Both buffers are circular.
   [ 7 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 ]

   A second value, 4, is written to the parameter buffer 
   and the second element in the status buffer becomes
   the previous element, 1 in this case, plus 1.
   [ 7 | 4 | 0 | 0 | 0 | 0 | 1 | 2 | 0 | 0 | 0 | 0 ]

   And so on
   [ 7 | 4 | 9 | 0 | 0 | 0 | 1 | 2 | 3 | 0 | 0 | 0 ]

为了确定下一次写入应该发生的位置,我们来看一下元素之间的区别。如果前一个元素+ 1不等于下一个元素,那么下一个写入应该发生。例如:

   Compute the differences by starting at the first
   element in the status buffer and wrapping around. 
   General algo: previous element + 1 = current element
   1st element:  0 + 1 = 1 = 1st element (move on)
   2nd element:  1 + 1 = 2 = 2nd element (move on)
   3rd element:  2 + 1 = 3 = 3rd element (move on)
   4th element:  3 + 1 = 4 != 4th element (next write occurs here)

   [ 7 | 4 | 9 | 0 | 0 | 0 | 1 | 2 | 3 | 0 | 0 | 0 ]
                 ^                       ^
                 |                       |

   Another edge case to consider is when the incrementing values
   wrap around at 256 because we are writing bytes. With the
   following buffer we know the next write is at the 3rd element 
   because 255 + 1 = 0 != 250 assuming we are using byte arithmetic.

   [ x | x | x | x | x | x | 254 | 255 | 250 | 251 | 252 | 253 ]
                                          ^
                                          | 

   After we write at element 3 the status buffer is incremented 
   to the next value using byte arithmetic and looks like this.
   255 + 1 = 0 (wrap around)
   0 + 1 != 251 (next write is here)

   [ x | x | x | x | x | x | 254 | 255 |  0  | 251 | 252 | 253 ]
                                                ^
                                                | 

以上这些示例显示了如何为一个变量延长EEPROM的寿命。对于多个变量,想象一下将EEPROM分成具有相同数据结构的多个段,但缓冲区较小。

问题

我有上面描述的工作代码。我的问题是当缓冲区长度> = 256时算法不起作用。这是发生的事情

   Buffer length of 256. The last zero is from
   the initial state of the buffer. At every index
   previous element + 1 == next element. No way to
   know where the next write should be.

   <-------------- Status Buffer ------------>
   [  1  |  2  | ... | 253 | 254 | 255 |  0  ]


   A similar problem occurs when the buffer length
   is greater than 256. A lookup for a read will think
   the next element is at the first 0 when it should be at 
   255.
   <-------------------- Status Buffer ------------------>
   [  1  |  2  | ... | 253 | 254 | 255 |  0  |  0  |  0  ]

问题

如何解决上述问题?有没有更好的方法来跟踪下一个元素的写入位置?

2 个答案:

答案 0 :(得分:14)

关于一般EEPROM寿命延长的一些想法:

  1. EEPROM单元通常由(通过硬件)以两步操作写入:首先,单元被擦除,即设置为全1(0b11111111 = 0xff),然后写入的位(有效)只有那些0)实际上是写的。只能通过实际写操作将位设置为0。将位从0更改为1需要擦除整个单元格,然后重新写入新值。

  2. 如果EEPROM单元已经包含要写入的相同值 - 可能是或多或少要写入(重新)写入数据的情况 - 则无需写入根本就是单元格,将写入操作的磨损减少到0.可能需要检查单元格内容以确定是否需要写入单元格而不是总是向其写入新值。

  3. 上述组合导致一种方法,即在写入之前仅擦除单元格,如果新值中有任何1位,则存储的值为0位(即,如果{{ 1}})。如果没有0 - >,则不需要擦除单元格。新值(StoredValue & NewValue != NewValue)需要1位转换。

  4. AVR分别提供擦除和写入EEPROM单元的单独指令,以支持上述机制。

  5. 当执行读取 - 比较 - 擦除 - 写入而不仅仅是擦除 - 写入操作时,数据传输到EEPROM的最坏情况速度当然会下降。但是,这有可能完全跳过某些/大多数单元的擦写操作,这可能会降低相对速度的损失。

  6. 针对您目前的问题,请考虑以上几点:为什么不使用单个位来存储下一个写入位置?

    示例:

    状态缓冲区初始化为全部:

    StoredValue & NewValue == NewValue

    在访问EEPROM中的值之前,找到状态缓冲区中的前1位。该位的数字表示(循环)参数缓冲区的当前“头”的地址。

    每次推进参数缓冲区时,将状态缓冲区中的下一位设置为0:

    Bit number: 0123 4567 89AB CDEF
    Value:      1111 1111 1111 1111
    

    然后

    Bit number: 0123 4567 89AB CDEF
    Value:      0111 1111 1111 1111
    

    然后

    Value:      0011 1111 1111 1111
    

    等等。

    这可以在没有擦除整个单元格的情况下完成,因此每次更新只会“磨损”一个状态缓冲区 - 如果写入的数据也只有一个0位。
    例如,要将Value: 0001 1111 1111 1111 的存储值转换为新值0111,要写入的数据应为00111011),除了单身,我们实际上想要改变。

    一旦状态缓冲区耗尽(全部为0),它将被完全擦除,并且全部重新开始。

    这里明确的一点是,每个参数缓冲区只需要保持一位状态,与Atmel应用笔记相比,它只消耗1/8的内存。 此外,找到下一个写入位置也会快得多,因为只需要状态缓冲区上的1/8读取操作。(编辑:不正确,因为EEPROM读取的成本几乎为零 - 所需的位移可能需要几十个周期。)

    另一个注意事项:您认为使用256+参数缓冲单元实际上有用吗?例如,当处理设备上1024字节的总可用EEPROM时,单元将变得非常小。 - 并且100000个周期乘以256是相当大量的写入操作,如果需要大量数字,则算法中可能存在错误,或者根本不应将EEPROM用于此目的。作为替代方案,在某些情况下,外部NVRAM将是一个不错的选择。

    访问时间也可能是这里的一个方面:当尝试查找并读取参数缓冲区中具有256字节状态缓冲区的3字节大小的元素时, 256 (+ 3)在最坏的情况下需要读取操作 - 这是一个巨大的开销!

    有一个关于EEPROM单元工作原理的非常具有说明性的文件,包括变质的原因和原因:

    STMicroelectronics: "How a designer can make the most of STMicroelectronics Serial EEPROMs", application note AN2014

答案 1 :(得分:2)

我建议你在数据元素而不是计数器上使用一个简单的“脏”位。除非扫描最后写入的元素太慢,或者你想做一些复杂的事情,比如跟踪坏的EEPROM单元,否则有计数器和目录是没有意义的。

算法非常简单:在您编写的每个元素中设置“脏”位,并在需要读取最后一个元素或写入新元素时扫描该位。当你用完干净的斑点时,要么擦除所有元素,要么(在Arduino的情况下)只需翻转“脏”位的值并从头开始。如果您需要,我已经写了详细的解释here