Non-volatile storage (NVS) for Zephyr

Elements, represented as id-data pairs, are stored in flash using a FIFO-managed circular buffer. The flash area is divided into sectors. Elements are appended to a sector until storage space in the sector is exhausted. Then a new sector in the flash area is prepared for use (erased). Before erasing the sector it is checked that identifier - data pairs exist in the sectors in use, if not the id-data pair is copied.

The id is a 16-bit unsigned number, where two values are reserved:

  • 0xFFFF is used to determine free locations in flash
  • 0xFFFE is used to close a sector

NVS ensures that for each id there is at least one id-data pair stored in flash at all time.

NVS allows storage of binary blobs, strings, integers, longs, and any combination of these. As each element is stored with a header (containing the id and length) and a footer (containing the crc16) it is less suited to store only integers.

NVS has a configuration option to enable extra flash protection (CONFIG_NVS_FLASH_PROTECTION=y), when selected NVS does a extra check before writing data to flash. If the id-data pair is unchanged no write to flash is performed. The down-side of this protection is that NVS needs to read the storage system to check if the id-data pair is unchanged. If you are already performing such a check you can disable the extra flash protection (CONFIG_NVS_FLASH_PROTECTION=n).

To protect the storage system against frequent sector erases the size of id-data pairs is limited to CONFIG_NVS_MAX_ELEM_SIZE. This limit is by default set to 1/4 of the sector size.

For NVS the file system is declared as:

static struct nvs_fs fs = {
.sector_size = NVS_SECTOR_SIZE,
.sector_count = NVS_SECTOR_COUNT,
.offset = NVS_STORAGE_OFFSET,
.max_len = NVS_MAX_ELEM_SIZE,
};

where

  • NVS_SECTOR_SIZE is the sector size, it has to be a multiple of the flash erase page size and a power of 2.
  • NVS_SECTOR_COUNT is the number of sectors, it is at least 2, one sector is always kept empty to allow copying of existing data.
  • NVS_STORAGE_OFFSET is the offset of the storage area in flash.
  • NVS_MAX_ELEM_SIZE is the maximum item size.

Flash wear

When writing data to flash a study of the flash wear is important. Flash has a limited life which is determined by the number of times flash can be erased. Flash is erased one page at a time and the pagesize is determined by the hardware. As an example a nRF51822 device has a pagesize of 1024 bytes and each page can be erased about 20,000 times.

Calculating expected device lifetime

Suppose we use a 4 bytes state variable that is changed every minute and needs to be restored after reboot. NVS has been defined with a sector_size equal to the pagesize (1024 bytes) and 2 sectors have been defined. Each sector in NVS has a sector header of 8 bytes.

Each write of the state variable requires 4 bytes for the variable, but also 4 bytes for the data header and 4 bytes for the data slot, so in total 12 bytes. When storing the data the first sector will be full after (1024-8)/12 = 84 minutes. After another 84 minutes, the second sector is full. When this happens, because we’re using only two sectors, the first sector will be used for storage and will be erased after 168 minutes of system time. With the expected device life of 20,000 writes, with two sectors writing every 168 minutes, the device should last about 168*20,000 minutes, or about 6.5 years.

More generally then, with

  • NS as the number of storage requests per minute,
  • DS as the data size in bytes,
  • SECTOR_SIZE in bytes, and
  • PAGE_ERASES as the number of times the page can be erased,

the expected device life (in minutes) can be calculated as:

SECTOR_COUNT * (SECTOR_SIZE-8) * PAGE_ERASES / (NS * (DS+8)) minutes

From this formula it is also clear what to do in case the expected life is too short: increase SECTOR_COUNT or SECTOR_SIZE.

Sample

A sample of how NVS can be used is supplied in samples/subsys/nvs.

API

The NVS subsystem APIs are provided by nvs.h:

group nvs_data_structures

Non-volatile Storage Data Structures.

struct nvs_fs
#include <nvs.h>

Non-volatile Storage File system structure.

Parameters
  • magic: File system magic, repeated at start of each sector
  • write_location: Next write location for entry header
  • offset: File system offset in flash
  • sector_id: Counter for sector
  • sector_size: File system is divided into sectors each sector should be multiple of pagesize and also a power of 2
  • free_space: Indicator for available space in the filesystem
  • max_len: Maximum size of storage items
  • sector_count: Amount of sectors in the file systems
  • entry_sector: Oldest sector in used
  • write_block_size: Alignment size in bytes_to_copy
  • nvs_lock: Mutex
  • flash_device: Flash Device

group nvs_high_level_api

Non-volatile Storage APIs.

Functions

int nvs_init(struct nvs_fs *fs, const char *dev_name, u32_t magic)

nvs_init

Initializes a NVS file system in flash.

Parameters
  • fs: Pointer to file system
  • dev_name: Pointer to flash device name
  • magic: Magic number used in file system
Return Value
  • 0: Success
  • -ERRNO: errno code if error

int nvs_clear(struct nvs_fs *fs)

nvs_clear

Clears the NVS file system from flash.

Parameters
  • fs: Pointer to file system
Return Value
  • 0: Success
  • -ERRNO: errno code if error

ssize_t nvs_write(struct nvs_fs *fs, u16_t id, const void *data, size_t len)

nvs_write

Write an entry to the file system.

Return
Number of bytes written. On success, it will be equal to the number of bytes requested to be written. Any other value, indicates an error. Will return -ERRNO code on error.
Parameters
  • fs: Pointer to file system
  • id: Id of the entry to be written
  • data: Pointer to the data to be written
  • len: Number of bytes to be written

int nvs_delete(struct nvs_fs *fs, u16_t id)

nvs_delete

Delete an entry from the file system

Parameters
  • fs: Pointer to file system
  • id: Id of the entry to be deleted
Return Value
  • 0: Success
  • -ERRNO: errno code if error

ssize_t nvs_read(struct nvs_fs *fs, u16_t id, void *data, size_t len)

nvs_read

Read an entry from the file system.

Return
Number of bytes read. On success, it will be equal to the number of bytes requested to be read. Any other value, indicates an error. When the number of bytes read is larger than the number of bytes requested to read this indicates not all bytes were read, and more data is available. Return -ERRNO code on error.
Parameters
  • fs: Pointer to file system
  • id: Id of the entry to be read
  • data: Pointer to data buffer
  • len: Number of bytes to be read

ssize_t nvs_read_hist(struct nvs_fs *fs, u16_t id, void *data, size_t len, u16_t cnt)

nvs_read_hist

Read a history entry from the file system.

Return
Number of bytes read. On success, it will be equal to the number of bytes requested to be read. Any other value, indicates an error. When the number of bytes read is larger than the number of bytes requested to read this indicates not all bytes were read, and more data is available. Return -ERRNO code on error.
Parameters
  • fs: Pointer to file system
  • id: Id of the entry to be read
  • data: Pointer to data buffer
  • len: Number of bytes to be read
  • cnt: History counter: 0: latest entry, 1:one before latest …