我正在开发一个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 ]
问题
如何解决上述问题?有没有更好的方法来跟踪下一个元素应该写在哪里?
关于一般EEPROM寿命延长的一些想法:
-
EEPROM单元通常(由硬件(通过两步操作写入:首先,擦除单元,即设置为所有1(0b11111111 = 0xff(,然后实际写入要写入的位(实际上只有那些为0的位(。位只能通过实际写入操作设置为 0。将位从 0 更改为 1 需要擦除整个单元格,然后重写新值。
-
如果EEPROM单元已经包含要写入的相同值(可能或多或少的数据被(重新(写入(,则根本不需要写入单元,从而将该写入操作的磨损减少到0。人们可能想要检查单元格内容以决定是否需要写入它,而不是总是向其写入新值。
-
上述组合导致了一种方法,即仅在写入之前擦除单元格,如果新值中有任何 1 位,其中存储的值具有 0 位(即,如果
StoredValue & NewValue != NewValue
(。如果新值 (StoredValue & NewValue == NewValue
( 不需要 0 -> 1 位转换,则无需擦除单元格。 -
AVR 分别提供用于擦除和写入 EEPROM 单元的单独指令,以支持上述机制。
当然,当执行读-比较-擦除- 写操作而不仅仅是擦除-写入操作时,将数据传输到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
的存储值转换为新值0011
要写入的数据应该1011
(data = ( newValue XOR oldValue ) XOR 0xff
(,除了我们实际想要更改的单个位外,所有位保持不变。
一旦状态缓冲区耗尽(全部为 0(,它就会被完全擦除,一切重新开始。
这里一个明确的优点是,每个参数缓冲区单位只需要保持一位状态,与 Atmel 应用笔记相比,该缓冲区仅消耗 1/8 的内存。此外,查找下一个写入位置也会快得多,因为只需要对状态缓冲区执行 1/8 的读取操作。(编辑:不正确,因为EEPROM读数在性价比方面几乎为零,而所需的位移可能需要几十个周期。
不过,另一个注意事项:您认为使用 256+ 参数缓冲区单元真的有用吗?例如,在处理设备上1024字节的总可用EEPROM时,这些单元将变得非常小。- 100000 个周期乘以 256 是相当多的写入操作,如果似乎需要这么大的数字,则算法可能有问题,或者根本不应该将 EEPROM 用于此目的。作为替代方案,在某些情况下,外部NVRAM将是一个不错的选择。
访问时间也可能是一个方面:当尝试在具有 256 字节状态缓冲区的参数缓冲区中查找和读取 3 字节大小的元素时,在最坏的情况下将需要 256 (+3( 次读取操作 - 巨大的开销!
有一份关于EEPROM细胞工作原理的非常说明性的文件,包括恶化的方式和原因:
意法半导体:"设计人员如何充分利用意法半导体系列产品EEPROM",应用笔记AN2014<</p>
我建议您在数据元素上使用简单的"脏"位而不是计数器。除非扫描最后一个写入的元素太慢,或者你想做一些复杂的事情,比如跟踪坏的EEPROM单元,否则使用计数器和目录是没有意义的。
算法非常简单:在你写的每个元素中设置一个"脏"位,并在需要读取最后一个元素或写入一个新元素时扫描这个位。当您用完干净的点时,要么擦除所有元素,要么(在 Arduino 的情况下(翻转"脏"位的值并从头开始。如果您需要,我在这里写了一个详细的解释。