185***527

NVDS(Non-volatile Data Storage)是一个轻量级逻辑数据Tag-Value存储系统,使用Flash硬件抽象层(Flash HAL)提供的Flash读写接口,将数据存储于Flash中,因此,掉电时数据也不会丢失。开发者也可以在custom_config.h中指定NVDS在Flash中的起始地址和扇区数量,但是需要注意不要和其他已规划为其它用途的Flash冲突。另外,NVDS起始地址需要和Flash扇区的大小对齐。NVDS系统适合存储小块,非频繁更新的数据,例如应用程序的配置参数、校准数据、状态和用户信息等。BLE协议栈也会使用NVDS存储设备绑定等参数。
Flash中的存储结构如下图,占用连续的若干个Sector的Flash空间,其中第一个sector的起始位置会放置特殊magic,用于标识这块区域是否也被初始化为NVDS。NVDS的每个Item是由Item Header和Item Data组成。


如上介绍,每个Item的Header会记录其Data的长度,因此由Item n可查找到Item n+1,即Item n的地址 + Header固定长度 + Item n数据长度,可以理解为“单链表”查找数据的逻辑。因此,每次Read操作从NVDS的起始位置挨个读取Item Header,并确定其tag是否为目标tag,若是则把其数据读出。
当需要写入Item至NVDS时,会确认是否已存在对应tag的Item:
已知Flash数据写入时,只能由1变0,因此Item更新时并不能在原Item位置更新,而是在尾部追加,那么就会产生垃圾/碎片数据(Tag为0x0000的Item),随着Item不断追加直至NVDS End Address,则需要回收垃圾/碎片数据,腾出空闲空间出来。
在进行GC时,依次将NVDS每个Sector数据读至中转区,将此Sector Erase后,把中转区的有效数据写入已Erase的Sector,忽略掉垃圾/碎片数据,GC无需用户主动进行,而是在Write过程中,发现可用空间足够(垃圾/碎片数据会被计算到可用空间中),但是尾部空间不足时会自动触发。
NVDS的实现具备以下局限性:
以上介绍最主要的是GC过程对BLE连接稳定性的影响,细节介绍如下:
下图是4组IO时序信号(红、绿、蓝、紫)分别代表NVDS GC、Flash Write、BLE Stack IRQ、BLE Event。


此次配置NVDS GC 4KB空间,供花费~874ms,也可看到在每次Flash Write结束后,BLE Stack IRQ有响应,但BLE Event并没有执行,空中数据包也可看出没有回复手机数据包。
这是因为BLE事件交互是有严格时序要求的,虽然BLE Stack IRQ Pending后有执行,但是对于BLE时机已错过,因此回复不了对方包。若此时BLE连接超时时间小于874ms或者NVDS配置的区域较大,GC耗时更久,那么就会导致BLE产生超时断链。
如上,我们可以总结由于NVDS自身的实现逻辑和限制,推荐NVDS使用原则如下:
1. 不存放高频次更新数据,不存放大量数据;
2. 用于存储如产品PID,SN等仅读的配置信息数据;
3. 线程非安全,使用尽量应用层做保护;
4. 尽量不要分配较大存储空间;
5. 如有其他存储需求,可选择littlefs等存储系统;
LittleFS是一个为嵌入式系统设计的轻量级文件系统。它专为低内存和闪存设备而设计,提供高效的存储解决方案,具备低资源消耗、掉电保护、磨损均衡等特性。以下着重列举几点与NVDS的区别以及可以规避NVDS局限性的点:
1. 每个Item会有一个revision字段,越大越新,更新Item时不会更新旧Item的信息,即使在更新过程中掉电,根据revision使用次新的Item数据;
2. GC时并不是一次性回收整个存储区域碎片,而是根据存储情况,整理最小需求Sector数,并且不需要中转区,类似乒乓Sector数据搬运;
依此在LittleFS之上抽象了一层Key-Value API:app_fds_init,app_fds_value_write,app_fds_value_read,app_fds_value_delete,app_fds_traverse,可直接替换原NVDS的存储需求实现,可使用int或string Tag。
详细文件见附件。以下是基于GR551x平台的使用示例,如用任何使用问题,欢迎随时本帖反馈。
Code size: ~16kB Ram:~1.8kB
app_fds_init(0x010a0000, 4);
写入数据:
if ((ret = app_fds_value_write(record_id[i], (uint8_t *)&record_val[i], 4)) < 0)
{
printf("fail [%d] %d\r\n", __LINE__, ret);
}if ((ret = app_fds_value_read(record_id[i], (uint8_t *)&read_val, 4)) < 0)
{
printf("fail [%d] %d\r\n", __LINE__, ret);
}if ((ret = app_fds_value_delete(record_id[i]) < 0)
{
printf("fail [%d] %d\r\n", __LINE__, ret);
}app_fds_traverse(app_fds_traverse_cb)
void app_fds_traverse_cb(uint32_t key, uint32_t length, bool* is_continue)
{
printf("key: %d, length:%d\r\n", key, length);
*is_continue = true;
if (app_fds_value_delete(key))
{
while(1);
}
}
185***527

如用任何使用问题,欢迎随时本帖反馈
打开微信,使用“扫一扫”即可关注