diff --git a/doc/source/develop/driver/flash_introduction.rst b/doc/source/develop/driver/flash_introduction.rst index afefbc22722af024accfe86e260bbfc41f693379..9d6885650ee194633a857d6f0a8b195418575878 100644 --- a/doc/source/develop/driver/flash_introduction.rst +++ b/doc/source/develop/driver/flash_introduction.rst @@ -1,4 +1,4 @@ -.. _develop_driver_flash_introduction: +.. _driver_flash_introduction: Zephyr Flash驱动之驱动模型 ############################## diff --git a/doc/source/develop/driver/index.rst b/doc/source/develop/driver/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..31f7c163a8b49155f4d18c8168e94277bb720364 --- /dev/null +++ b/doc/source/develop/driver/index.rst @@ -0,0 +1,11 @@ +.. _driver: + +驱动 +##### + +分析Zephyr驱动使用和实现方法。 + +.. toctree:: + :maxdepth: 1 + + flash_introduction.rst diff --git a/doc/source/develop/index.rst b/doc/source/develop/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..829405a7db0dd9cda10ab026237c36538a654f3f --- /dev/null +++ b/doc/source/develop/index.rst @@ -0,0 +1,10 @@ +.. _develop: + +开发者参考 +########## + +.. toctree:: + :maxdepth: 2 + + subsys/index.rst + driver/index.rst diff --git a/doc/source/develop/subsys/index.rst b/doc/source/develop/subsys/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..ec5c25ed9cec0bf353f23f4f42c4f3d328726835 --- /dev/null +++ b/doc/source/develop/subsys/index.rst @@ -0,0 +1,14 @@ +.. _subsys: + +子系统 +########## + +Zephyr子系统使用方法和实现原理。 + +.. toctree:: + :maxdepth: 1 + + storage/flash_map.rst + storage/flash_stream.rst + storage/nvs.rst + storage/nvs_analyze.rst diff --git a/doc/source/develop/subsys/storage/flash_map.rst b/doc/source/develop/subsys/storage/flash_map.rst index 4440ef0bf72dd9c478ad8f0af2958a805317f5c3..f0e1fee3eab5893e9bc276d100eea7915838e61c 100644 --- a/doc/source/develop/subsys/storage/flash_map.rst +++ b/doc/source/develop/subsys/storage/flash_map.rst @@ -1,4 +1,4 @@ -.. _develop_subsys_storage_flash_map: +.. _storage_flash_map: Zephyr Flash Map实现分析 ######################### diff --git a/doc/source/develop/subsys/storage/flash_stream.rst b/doc/source/develop/subsys/storage/flash_stream.rst index aca54667337fe066a0f832485e1e46846414ed31..f739082cda70272f9a0b748b398aacbd5d844aab 100644 --- a/doc/source/develop/subsys/storage/flash_stream.rst +++ b/doc/source/develop/subsys/storage/flash_stream.rst @@ -1,4 +1,4 @@ -.. _develop_subsys_storage_flash_stream: +.. _storage_flash_stream: Zephyr flash流式操作 ######################### diff --git a/doc/source/develop/subsys/storage/nvs.rst b/doc/source/develop/subsys/storage/nvs.rst new file mode 100644 index 0000000000000000000000000000000000000000..2baca1e49364ab844713bdf6d77885a0cfe5e6dd --- /dev/null +++ b/doc/source/develop/subsys/storage/nvs.rst @@ -0,0 +1,205 @@ +.. _storage_nvs: + +Zephyr NVS模块使用说明 +######################### + +NVS是非易失性存储的简写, 而Zephyr中的NVS单只是对Flash功能进行包装,并提供一个“识别号-数据”对的访问接口,用于存储系统中经常改变的数据。例如播放器的音量,当前的环境噪声,设备重启的次数。我们知道在Flash上写入数据是需要先擦除,而Flash的擦除次数是有限制的,引入NVS的目的是通过Flash的空间换取Flash不被频繁的擦除,并均衡使用Flash的磨损。 + +Zephyr的NVS基本构成如下: + +NVS以扇区(sector)为单位管理Flash,一个扇区包含一个或以上的Flash页,扇区必须按照Flash的页大小对齐。NVS至少要2个扇区,NVS永远维持一个空扇区做记录回收用,NVS写入记录的数据小于一个扇区。 + +NVS数据以记录为单位,一条记录就是一个id-data对。在NVS中以唯一id来标识记录。id的长度为16bit,最大0xFFFF被作为特殊用途,也就是说NVS可以存储的数据条目不能超过65535条。 + +NVS写入记录时不会擦除原来的记录,而是先检查NVS上已有记录,如果有相同的id-data对就不进行写入,如果没有则进行写入。所以NVS内可能会存在多条id相同的记录。 + +NVS读出记录时,会读出指定id最近的一条写入记录。 + +接口 +==== + +基于\ ``include/fs/nvs.h``\进行注释分析 + +.. code:: c + + /** + * @brief nvs_init + * + * 在Flash上初始化NVS文件系统. + * + * @param fs 指向nvs + * @param dev_name Flash设备名 + * @retval 0 表示成功 + * @retval 负数为失败返回的errno + */ + int nvs_init(struct nvs_fs *fs, const char *dev_name); + + /** + * @brief nvs_clear + * + * 清除NVS文件系统 + * @param fs 指向nvs + * @retval 0 表示成功 + * @retval 负数为失败返回的errno + */ + int nvs_clear(struct nvs_fs *fs); + + /** + * @brief nvs_write + * + * 向nvs写入一条记录. + * + * @param fs 指向nvs + * @param id 记录的ID + * @param data 要被写入记录的数据 + * @param len 被写入的长度 + * @return 返回实际的写入数据长度,应该和len相等,当返回0时表示找到相同的id-data,无需再次写入。负数为失败返回的errno。 + */ + ssize_t nvs_write(struct nvs_fs *fs, uint16_t id, const void *data, size_t len); + + /** + * @brief nvs_delete + * + * 从nvs中删除一条记录 + * + * @param fs 指向nvs + * @param id 删除记录的ID + * @retval 0 表示成功 + * @retval 负数为失败返回的errno + */ + int nvs_delete(struct nvs_fs *fs, uint16_t id); + + /** + * @brief nvs_read + * + * 从nvs中读出一条记录. + * + * @param fs 指向nvs + * @param id 要读出记录的ID + * @param data 被读出的数据 + * @param len 读出数据的长度 + * + * @return 返回实际的读出数据长度,应该和len想等。当返回值大于len时表示该条记录还有数据没有读完。负数为失败返回的errno。 + */ + ssize_t nvs_read(struct nvs_fs *fs, uint16_t id, void *data, size_t len); + + /** + * @brief nvs_read_hist + * + * 读NVS内的历史记录 + * + * @param fs 指向nvs + * @param id 要读出记录的ID + * @param data 被读出的数据 + * @param len 读出数据的长度 + * @param cnt 历史计数器,0表示最近的一个 + * + * @return 返回实际的读出数据长度,应该和len想等。当返回值大于len时表示该条记录还有数据没有读完。负数为失败返回的errno。 + */ + ssize_t nvs_read_hist(struct nvs_fs *fs, uint16_t id, void *data, size_t len, uint16_t cnt); + + /** + * @brief nvs_calc_free_space + * + * 计算NVS剩余可用空间. + * + * @param fs 指向nvs + * + * @return 返回NVS上可用空间的大小.如果返回负数则为错误的errno + */ + ssize_t nvs_calc_free_space(struct nvs_fs *fs); + + +使用 +==== + +默认情况下NVS是被禁用的,通过下面配置项可以启用NVS + +.. code:: kconfig + + # 启用NVS + CONFIG_NVS=y + + # NVS依赖Flash,需要启用Flash驱动 + CONFIG_FLASH=y + CONFIG_FLASH_PAGE_LAYOUT=y + + # 当使用的flash地址空间被MPU保护时,需要配置允许对应地址空间被写入 + CONFIG_MPU_ALLOW_FLASH_WRITE=y + + +以下示例代码展示了如何使用nvs记录系统启动的次数和检查系统是否正常关机。 + +.. code:: c + + #define REBOOT_COUNT_ID 0 + #define POWER_NORMAL_ID 1 + #define FLASH_LABLE "FLASH_ESP32C3" + struct nvs_fs fs; + struct flash_pages_info info; + uint16_t reboot_cnt = 0; + bool power_normal = false; + + const struct device *flash_dev = device_get_binding(FLASH_LABLE); + + flash_get_page_info_by_offs(flash_dev, fs.offset, &info); + + // nvs放到flash的0x250000处,每个扇区使用一个flash页面,nvs总计3个扇区 + fs.offset = 0x250000 + fs.sector_size = info.size; + fs.sector_count = 3U; + + // 初始化nvs + rc = nvs_init(&fs, FLASH_LABLE); + if (rc) { + printk("Flash Init failed\n"); + return; + } + + // 读出上一次reboot count,如果存在做一次加1 + rc= nvs_read(&fs, REBOOT_COUNT_ID, &reboot_cnt, sizeof(reboot_cnt)); + if(rc == sizeof(reboot_cnt)){ + reboot_cnt++; + }else{ + reboot_cnt = 0; + } + + // 更新重启的次数 + nvs_write(&fs, REBOOT_COUNT_ID, &reboot_cnt, sizeof(reboot_cnt)); + + //读出关机记录 + rc= nvs_read(&fs, POWER_NORMAL_ID, &power_normal, sizeof(power_normal)); + if(rc == sizeof(power_normal)){ + //删除关机记录 + nvs_delete(&fs, POWER_NORMAL_ID); + } + + if(power_normal == true){ + // 正常关机处理 + }else{ + // 异常关机处理 + } + + // 处理 + switch(status){ + case DO_SOMTING: + break; + case POWER_OFF: + //关机流程,写入正常关机标记 + nvs_write(&fs, POWER_NORMAL_ID, &power_normal, sizeof(power_normal)); + //进行关机处理 + break; + case NVS_FORMAT: + //清除NVS文件系统 + nvs_clear(&fs); + break; + default: + break; + } + + + +参考 +==== + +https://docs.zephyrproject.org/latest/reference/storage/nvs/nvs.html \ No newline at end of file diff --git a/doc/source/develop/subsys/storage/nvs_analyze.rst b/doc/source/develop/subsys/storage/nvs_analyze.rst new file mode 100644 index 0000000000000000000000000000000000000000..afa9c28bbc1f9b26841ee41da4cb77ffa0e8842a --- /dev/null +++ b/doc/source/develop/subsys/storage/nvs_analyze.rst @@ -0,0 +1,227 @@ +.. _storage_nvs_analyze: + +Zephyr NVS原理分析 +######################### + +基本概念 +======== + +Zephyr NVS基于Flash之上提供一个简易的文件系统。其目标是利用Flash的特性,解决Flash在存储数据上的缺点。 + +Flash的特性: + +* 有数据的位置,再次写入前必须先擦除 +* 擦除总次数有限制 +* 擦除单位为页 +* 不同的Flash会有写入对齐要求 +* 读出数据无限制 + +由Flash的特性导致的缺点: + +* 无论写多少数据,都要先进行页擦除 +* 擦除耗时,导致写入耗时 +* 频繁的擦除会降低Flash的使用寿命 +* 写入对齐对写入数据的位置和长度有限制 + + +因此设计NVS的时候,主要是目标: + +* 不是每次写入数据时都做擦除动作 +* 降低擦除Flash的次数,增加Flash的寿命 +* 屏蔽写入对齐的限制 + +NVS扇区 +~~~~~~~ + +NVS以扇区sector进行管理: + +* 一个NVS由2个或以上的扇区组成 +* 一个扇区由Flash的1个或者多个页组成。 + +NVS和Flash的关系如下图: + +.. image:: ../../../images/develop/subsys/storage/nvs-flash.png + +由于扇区和Flash的页是对齐的,所以对扇区进行擦除动作时可以直接使用Flash擦除API,而无需额外的代码。 + +NVS记录 +~~~~~~~ + +写入NVS的数据叫做NVS记录,写入时用ID标识记录,一个ID标识一条记录,ID的长度为16bit,因此NVS内的记录不会超过65536条。在NVS中一条记录被分为两部分 + +* 记录分配表 ate: Allocation Table Entry,用于记录数据的ID和数据在扇区中的信息。 +* 记录的数据 + +在\ ``zephyr\subsys\fs\nvs\nvs_priv.h``\中定义ate的结构 + +.. code:: c + + struct nvs_ate { + uint16_t id; /* 数据的ID */ + uint16_t offset; /* 数据在扇区内的偏移 */ + uint16_t len; /* 数据的长度 */ + uint8_t part; /* */ + uint8_t crc8; /* crc8 check of the entry */ + } __packed; + + +从ate的结构体可以看出NVS记录的数据长度用`uint16_t`记录,因此NVS一条记录的数据长度最多为64K。 + +当NVS写入一条记录时ate和其指向的数据在物理位置上是分开的,ate从扇区的最后向前生长,而数据从扇区的最前面向后生长。 + +.. image:: ../../../images/develop/subsys/storage/ate1.png + +特殊定义 +~~~~~~~~ + +基于NVS的设计,为了方便后续的说明,这里约定一些特殊定义: + +ate相关 +^^^^^^^^ + +* last ate + + * 最近一次写入的ate + +* close ate + + * \ ``id=0xFFFF``\ + * \ ``len=0``\ + * \ ``offset``\指向该扇区最后写入的ate + * close ate固定位于扇区的最末尾,不指向任何数据,用于标识扇区已被关闭 + +* gc ate + + * \ ``id=0xFFFF``\ + * \ ``len=0``\ + * \ ``offset``\指向gc ate自己 + * gc ate固定位于close ate前,不指向任何数据,用于标识该扇区的数据是被垃圾回收过的 + +* delete ate + + * \ ``id``\为被删除数据的id + * \ ``len=0``\ + * \ ``offset=0``\ + * delete ate用于标识某个id的数据被删除 + +扇区相关 +^^^^^^^^^ + +* open sector 不含有有效close ate的扇区 +* close sector 含有有效close ate的扇区 +* write sector 当前可写入的扇区 +* empty sector 被擦除后,内容为全FF的扇区 + +原理说明 +======== + +NVS将扇区当作循环缓冲进行管理,写入记录被添加到扇区内,当一个扇区被写满后切换到下一个扇区。 + +写入 +~~~~ + +NVS写入记录时,不会擦除老的记录,而在最新的位置写入记录,ID的有效数据只是最新记录的这一条。这种写入时不擦除的操作大大的降低了对Flash的擦除次数。 + +常规写入 +~~~~~~~~ + +NVS写入记录时对write sector进行写入遵循下面步骤: + +1. 检查是否有相同记录:ID相同且数据相同,不需要再次写入 +2. 如果没有相同记录,写入新的记录 + +.. note:: + + 判断相同的标准时ID和数据都相同,就算是查到有相同的ID,数据不同也需要写入 + +.. image:: ../../../images/develop/subsys/storage/nvs-write.png + +切换扇区 +~~~~~~~~ + +当写入数据时发现write sector的空闲空间无法容纳新的记录时,进行扇区切换,遵循下面步骤: + +1. write sector剩余空间小于写入记录的data len+ate size,写入close ate +2. write sector切换为下一个sector,写入记录 + +.. image:: ../../../images/develop/subsys/storage/nvs-write-switch.png + + +NVS在写入ate和数据时,会参考Flash写入对齐的要求,分别对ate和数据进行对齐补齐。 + + + +读出 +~~~~~~~~ + +从写入分析可以知道,NVS中会存在多条相同的ID的记录,只需要找出最近一次与ID匹配的写入记录既可。下图示例要读出ID1的记录 + +.. image:: ../../../images/develop/subsys/storage/nvs-read.png + +删除 +~~~~~~~~ + +同样为了避免删除记录导致Flash擦除,删除时只是写入一个delete ate + +.. image:: ../../../images/develop/subsys/storage/nvs-delete.png + +次数如果再去读ID1的记录,会去找最近写入的ID1 的ate,也就是delete ate,表示ID1的记录已经从NVS内删除而读不到数据。 + +垃圾回收 +~~~~~~~~ + +由于对同ID的多次操作将导致NVS中含有大量的冗余数据 + +* 从写入机制可知:NVS内可能会存在多笔同ID的数据,但只有最后一笔有效。 +* 从删除机制可知:即使ID的数据从NVS内删除,但任然占用Flash空间。 + +NVS引入了垃圾回收机制处理这些冗余: + +* NVS永远保留一个扇区为empty sector +* 当写NVS引发扇区切换时进行垃圾回收 +* 一次垃圾回收只处理一个扇区 +* 垃圾回收的扇区是即将要写入扇区的下一个扇区 + +一次写入引发的垃圾回收过程如下图,其中虚线框中的2,3,4是垃圾回收过程: + +.. image:: ../../../images/develop/subsys/storage/nvs-gc.png + +1. 写入的ID1记录比扇区B的剩余空间大 +2. 将B扇区关闭,切换到C扇区 +3. 对A扇区进行垃圾回收 + 1. A扇区中ID2的数据为最新数据,搬运到C扇区 + 2. A扇区中的ID1在B扇区中有更新的数据,不需要搬运 + 3. 其它A扇区中的数据都是冗余数据,无需搬运 +4. A扇区被回收完后将A扇区擦除,将C扇区标识为已完成垃圾回收 +5. C扇区的数据可以容纳要写入ID1的数据,写入ID1最新的数据 +6. 当C扇区被写满后,下一次将对B扇区进行垃圾回收,然后擦除B扇区。这样循环往复。 + +从上面分析可以看到NVS的擦除是在其管理的扇区中循环进行的,这种机制可以均衡对Flash的磨损,从整体上延长Flash的寿命。垃圾回收付出的代价是要浪费一个扇区无法写数据,因此在创建NVS时需要评估写入单笔记录数据的最大长度,尽量配置小的扇区减少浪费。 + +NVS创建与恢复 +~~~~~~~~~~~~~~~~ + +NVS本身不存在创建格式化的问题,只要指定了NVS在Flash中的位置以及扇区大小和个数,NVS会按照初始化的信息进行操作。如果Flash中之前没有写过NVS,NVS会自己进行擦除操作。如果Flash之前有NVS,初始化的时候会通过下面步骤进行恢复: + +1. 遍历扇区,找到close sector之后的open sector,将其设置为write sector +2. 如果全部是open sector,那么第一个扇区作为write sector +3. 检查write sector之后是否为empty sector,如果不是 + 1. write sector如果无gc ate,擦除write sector,做gc + 2. write sector如果有gc ate,擦除下一个sector + + + +总结 +===== + +Zephyr NVS代码实现相对复杂,通过本文可以理解Zephyr NVS的设计思想和工作原理,为分析Zephyr NVS打基础。Zephyr NVS对Flash进行封装,通过冗余扇区减轻Flash的两个缺点带来的影响: + +* 写入/删除时只写不擦除,降低Flash磨损,增加Flash使用寿命。 +* 垃圾回收时循环擦除,均衡Flash磨损,增加Flash整体寿命。 + + + +参考 +===== + +https://docs.zephyrproject.org/latest/reference/storage/nvs/nvs.html \ No newline at end of file diff --git a/doc/source/images/develop/subsys/storage/ate1.png b/doc/source/images/develop/subsys/storage/ate1.png new file mode 100644 index 0000000000000000000000000000000000000000..21063175d527dee68261699a60a2dabc6a336ab5 Binary files /dev/null and b/doc/source/images/develop/subsys/storage/ate1.png differ diff --git a/doc/source/images/develop/subsys/storage/nvs-delete.png b/doc/source/images/develop/subsys/storage/nvs-delete.png new file mode 100644 index 0000000000000000000000000000000000000000..8e08992a85dd74d6804b8306cb96a2bb71181827 Binary files /dev/null and b/doc/source/images/develop/subsys/storage/nvs-delete.png differ diff --git a/doc/source/images/develop/subsys/storage/nvs-flash.png b/doc/source/images/develop/subsys/storage/nvs-flash.png new file mode 100644 index 0000000000000000000000000000000000000000..29186fca4b5d510f6e33cc5e6d940d40368aabb5 Binary files /dev/null and b/doc/source/images/develop/subsys/storage/nvs-flash.png differ diff --git a/doc/source/images/develop/subsys/storage/nvs-gc.png b/doc/source/images/develop/subsys/storage/nvs-gc.png new file mode 100644 index 0000000000000000000000000000000000000000..29ecbbd28c698698657874010e34337a1b6ff66f Binary files /dev/null and b/doc/source/images/develop/subsys/storage/nvs-gc.png differ diff --git a/doc/source/images/develop/subsys/storage/nvs-read.png b/doc/source/images/develop/subsys/storage/nvs-read.png new file mode 100644 index 0000000000000000000000000000000000000000..48aba941bd0eb98aa2471233267c6c62763585d1 Binary files /dev/null and b/doc/source/images/develop/subsys/storage/nvs-read.png differ diff --git a/doc/source/images/develop/subsys/storage/nvs-write-switch.png b/doc/source/images/develop/subsys/storage/nvs-write-switch.png new file mode 100644 index 0000000000000000000000000000000000000000..86cbe2f57d1babbd4e3fca39e8b5515267c89612 Binary files /dev/null and b/doc/source/images/develop/subsys/storage/nvs-write-switch.png differ diff --git a/doc/source/images/develop/subsys/storage/nvs-write.png b/doc/source/images/develop/subsys/storage/nvs-write.png new file mode 100644 index 0000000000000000000000000000000000000000..06331346f98f669fb5353d98591e0ce4d9b7e5be Binary files /dev/null and b/doc/source/images/develop/subsys/storage/nvs-write.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index 71de3476cf5f9ecaca5076c58896e7175f9d9202..d271866d43d4736bd02b4a66c1ba75ebedb51937 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -18,10 +18,7 @@ Zephyr Project中文资料站 develop/windows.rst develop/vscode.rst develop/write_doc.rst - develop/driver/flash_introduction.rst - develop/subsys/storage/flash_map.rst - develop/subsys/storage/flash_stream.rst - + develop/index.rst Indices and tables