7

我需要将不同长度的项目存储在闪存芯片的循环队列中。每个项目都有它的封装,所以我可以弄清楚它有多大以及下一个项目从哪里开始。当缓冲区中有足够的项目时,它将换行到开头。

将循环队列存储在闪存芯片中的好方法是什么?

我想存储数以万计的物品的可能性。所以从头开始读取到缓冲区的末尾并不理想,因为搜索到末尾需要时间。

另外,因为它是循环的,我需要能够区分第一个项目和最后一个项目。

最后一个问题是它存储在闪存中,因此擦除每个块既耗时又只能为每个块执行一定次数。

4

5 回答 5

14

一、区块管理:

在每个块的开头放置一个较小的标题。您需要跟踪“最旧”和“最新”的主要内容是块号,它只是增加模kk必须大于您的总块数。理想情况下,使k小于您的 MAX 值(例如 0xFFFF),这样您就可以轻松分辨出什么是已擦除块。

在启动时,您的代码依次读取每个块的标题,并按照 n i+1 = (n i + 1) MODULO k 的顺序定位第一个和最后一个块。注意不要被已擦除的块(例如,块号为 0xFFFF)或以某种方式损坏的数据(例如,不完全擦除)弄糊涂。

每个区块内

每个块最初都是空的(每个字节都是 0xFF)。每条记录只是一个接一个地写入。如果您有固定大小的记录,那么您可以使用简单的索引来访问它。如果您有可变大小的记录,那么要读取它,您必须从块的开头进行扫描,链表样式。

如果您想拥有可变大小的记录,但避免线性扫描,那么您可以在每条记录上都有一个明确定义的标题。例如,使用 0 作为记录分隔符,并对每个记录进行COBS编码(或COBS /R编码)。或者使用您选择的字节作为分隔符,如果该字节出现在每条记录中,则“转义”该字节(类似于PPP 协议)。

在启动时,一旦你知道你的最新区块,你就可以对最新记录进行线性扫描。或者,如果您有固定大小的记录或记录分隔符,您可以进行二分搜索。

擦除调度

对于某些闪存芯片,擦除一个块可能需要相当长的时间——例如 5 秒。考虑“提前”将擦除安排为后台任务。例如,当当前块已满 x% 时,则开始擦除下一个块。

记录编号

您可能想要对记录进行编号。我过去的做法是在每个块的标题中放置第一条记录的记录号。然后软件必须计算块内每条记录的数量。

校验和或 CRC

如果您想检测损坏的数据(例如,由于意外电源故障导致的不完整写入或擦除),那么您可以将校验和或 CRC 添加到每条记录,也许还可以添加到块头。请注意,块头 CRC 只会覆盖头本身,而不是记录,因为在写入每个新记录时它不能被重写。

于 2009-11-03T23:58:12.303 回答
2

保留一个单独的块,其中包含指向第一条记录的开头和最后一条记录的结尾的指针。您还可以保留更多信息,例如记录总数等。

在您最初用完空间之前,添加记录就像将它们写入缓冲区的末尾并更新尾指针一样简单。

当您需要回收空间时,请删除足够的记录以适应当前记录。删除记录时更新头指针。

您需要跟踪释放了多少额外空间。如果保留指向最后一条记录末尾的指针,下次需要添加记录时,可以将其与指向第一条记录的指针进行比较,以确定是否需要删除更多记录。

此外,如果这是 NAND,您或闪存控制器将需要进行去块和磨损均衡,但这都应该位于比为循环缓冲区分配空间更低的层。

于 2009-11-03T22:59:26.050 回答
1

闪存中的“循环”可以根据块大小来完成,这意味着您必须声明为该缓冲区分配了多少闪存块。

缓冲区的实际大小将在 n-1(n 是块数)和 n 之间的每个特定时间。

每个块都应以包含序列号或时间戳的标头开头,可用于确定哪个块比另一个块更旧。

每个项目都封装有页眉和页脚。默认标题包含您想要的任何内容,但根据此标题,您必须知道项目的大小。默认页脚为 0xFFFFFFFF。此值指示空终止。

在您的 RAM 中,您必须保存指向最旧块和最新块的指针以及指向最旧项和最新项的指针。上电时,您会遍历所有块,找到相关块并加载此成员。

当你想存储一个新项目时,你检查最新的块是否包含足够的空间来存储这个项目。如果是,则将项目保存在上一个项目的末尾,并将上一个页脚更改为指向该项目。如果它不包含足够的空间,您需要擦除最旧的块。在擦除此块之前,将最旧的块成员 (RAM) 更改为指向下一个块,并将最旧的项目指向该块中的第一个项目。然后您可以将新项目保存在此块中,并将最新项目的页脚更改为指向该项目。

我知道解释可能听起来很复杂,但过程非常简单,如果你写得正确,你甚至可以让它断电安全(永远记住写的顺序)。

请注意,缓冲区的循环不会保存在闪存中,而是闪存仅包含一个带有项目的块,您可以根据块头和项目头来决定这些项目的顺序是什么

于 2009-11-04T08:29:29.277 回答
1

我想我现在明白了。似乎您最大的问题是,已经填满了可用的录制空间,接下来会发生什么?新数据应该覆盖最旧的数据,我相信你所说的循环缓冲区是什么意思。但由于数据不是固定长度的,您可能会覆盖多条记录。

我假设长度的可变性足够高,以至于无法将所有内容填充到固定长度。

您的写入段需要跟踪代表下一条要写入的记录开始的地址。如果您提前知道要写入的块的大小,则可以判断您是否将在逻辑缓冲区的末尾结束并从“0”重新开始。我不会把唱片分成一些在结尾和一些在开头。

一个单独的寄存器可以跟踪开始;这是尚未被覆盖的最旧数据。如果你去读出数据,这就是你要开始的地方。

然后数据写入器将检查,给定写入起始地址和即将提交的数据长度,是否应该碰撞读取寄存器,读取寄存器将检查第一个块并查看长度,然后前进到下一个记录,直到有足够的空间来写任何数据。在写入数据的结尾和最旧数据的开头之间可能会有一段垃圾数据的间隙。但是这样一来,你可以只写一个或两个地址作为开销,而不是重新排列块。

至少,这可能是我会做的。高温高压

于 2009-11-03T20:02:57.070 回答
0

我看到三个选项:

选项1:是将所有内容填充到相同的大小,这很简单,存储指向缓冲区头部和尾部的指针,以便您知道从哪里写入以及从哪里开始读取,使用每个对象的大小来获取偏移量到下一个,这意味着您需要像使用链表一样遍历缓冲区,如果您需要项目 5000,它也很慢。

选项2:是仅将指向实际数据的指针存储在循环缓冲区中,这样当您循环时,您不必处理大小不匹配的问题。如果您将真实数据存储在一个循环缓冲区中并且不将其填充出来,您可能会遇到这样一种情况,即您过度使用 1 个新数据对象的多个项目,我认为这是不行的。

将实际数据存储在闪存中的其他位置,大多数闪存将内置某种磨损均衡,如果是这样,您无需担心多次覆盖同一位置,IC 将确定将其实际存储在芯片上的位置,只需写入下一个可用空间。

这意味着您需要为循环缓冲区选择最大大小,具体操作方式取决于数据的可变性。如果数据的大小变化很大,比如只有几个字节,那么你应该把它填满并使用选项 1。如果大小变化很大且不可预测,请选择它可能的最大大小并计算出有多少对象该大小将适合您的闪存,将其用作缓冲区中的最大条目数。这意味着您浪费了大量空间。

选项3:如果对象真的可以是任何大小,那么您应该只使用文件系统,按顺序命名文件并在您完全记住时循环回来,如果您的新条目很大,您可能必须删除多个旧条目以适应它。这实际上只是选项 2 的扩展,因为选项 2 在许多方面都是一个简单的文件系统。

于 2009-11-03T22:02:23.930 回答