10

我正在开发一个 Arduino 库,它将最大限度地延长 AVR 的 EEPROM 的寿命。它需要您要存储的变量数量并完成其余的工作。这是我的尝试,并非在所有情况下都有效。

背景资料

Atmel 表示,每个存储单元的额定写入/擦除周期为 100,000 次。它们还提供了应用说明,描述了如何执行磨损均衡。这是应用笔记的摘要。

通过在两个内存地址上交替写入,我们可以将擦除/写入次数增加到 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  ]

问题

我该如何解决上述问题?有没有更好的方法来跟踪下一个元素应该写在哪里?

4

2 回答 2

17

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

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

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

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

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

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

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

例子:

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

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

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

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

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

然后

Value:      0011 1111 1111 1111

然后

Value:      0001 1111 1111 1111

等等。

这可以在擦除整个单元格的情况下完成,因此每次更新只会“磨损”状态缓冲区的一位 - 如果写入的数据也只有一个 0 位。
例如,将存储的值转换为要写入的数据应为( )0111的新值,除了我们实际想要更改的单个位之外,所有位都保持不变。00111011data = ( newValue XOR oldValue ) XOR 0xff

一旦状态缓冲区用完(全为 0),它就会被完全擦除,然后重新开始。

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

另一个注意事项:你认为使用 256+ 参数缓冲单元真的有用吗?例如,在处理设备上 1024 字节的总可用 EEPROM 时,这些单元会变得非常小。- 100000 个周期乘以 256 是相当多的写操作,如果似乎需要这么大的数字,则可能是算法有问题,或者根本不应该使用 EEPROM。作为替代方案,在某些情况下,外部NVRAM将是一个不错的选择。

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

关于 EEPROM 单元的工作原理,有一个非常说明性的文档,包括恶化的方式和原因:

STMicroelectronics:“设计人员如何充分利用 STMicroelectronics 串行 EEPROM”,应用笔记 AN2014

于 2012-05-19T23:34:33.527 回答
2

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

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

于 2017-05-09T16:43:40.707 回答