Nordic NRF52840 Storage Part I
There are mainly two ways for persistent storage of data in NRF52840: flash and UICR. I’ll be discussing the flash mainly in this part I, and UICR in part II.
1. Flash Memory
Nordic SDK provides two libraries to handle flash memory storage, fstorage module and FDS module. They were experimental, but now a part of validated libraries in SDK 17.
The Flash Data Storage (FDS) module is more higher level mini file system. It uses the Flash Storage (fstorage) as backend to write to flash. SoftDevice is required to execute the write.
The module has been designed to provide the following benefits:
- Minimizing the risk of accessing corrupted data by constant validation: In case of power loss, data might be written incompletely. Validation ensures that FDS recognizes invalid data and never returns corrupted data to the user.
- Offering (optional) CRC verification when opening a record to ensure that data has not changed since it was written.
- Minimizing flash operations (updating and deleting): Instead of deleting full pages, FDS stores copies of new data and invalidates outdated data by a single word write.
- Basic wear leveling: Sequential writes and garbage collection provide for an even level of flash usage.
- Making it easy to access data without copying it, which makes the impact of accessing data independent of the size of the data. Minimizing memory usage by allowing for flexible data size.
- Impose no restrictions on the content of the data (which means that it can contain special characters).
With FDS, you will treat data as record and files, which is much clean and easy to handle. FDS will make sure of an even flash wear distribution but come with more overhead of each record writing. Only FDS will be discussed here.
2. Storage Format
FDS stores data as records, which has a header and a content.
Every record is identified by a key and a file ID it is assigned to, however, we need to understand:
Files are groups of records. Neither record keys nor file IDs must be unique, and files can contain several records with the same key. Records can be accessed through any combination of file ID and record Key.
For example, an application could use the following two files:
- File 1 with two records: 0x1111=“Phone1”, 0x2222=“data: 12345”
- File 2 with three records: 0x1111=“Tablet1”, 0x2222=“data: abcdef”, 0x2222=“data: 67890”
You could then iterate, for example, through all records in file 1, through all records with key 0x1111, or through all records with key 0x2222 in file 2.
Restrictions on keys and IDs:
- Record keys should be in the range 0x0001 - 0xBFFF.
- File IDs should be in the range 0x0000 - 0xBFFF.
The sole unique identifier is the Record ID, but is not assigned nor can it be manipulated by user. All of these may sound confusing which it is (see post here and here). The way to make sense of it is: records are the only physical structure of data stored, thus require an unique identifier, AKA record ID, while files are just some abstract collections.
You may very well have many same record keys or file IDs, but I would recommend treat them as keys in database records, so you can link the same key to different tables/files to make life easier.
The other important configuration is virtual page. The total amount of flash memory for use depends on the size (FDS_VIRTUAL_PAGE_SIZE) and number of virtual pages (FDS_VIRTUAL_PAGES). By default, a virtual page is the same size as a physical page. One of the virtual page is reserved by the system for garbage collection, and this post discussed some of underlining mechanism of page swapping during the garbage collection. Details of configuration options for the FDS can be found here.
3. Usage
Before read and write with FDS, it is required to:
- Make sure the SoftDevice is initialized,
- Register a FDS callback event handler with
fds_register()
- Call
fds_init()
to initialize the module.
ALL FDS operations are asynchronous, completion of the operation is reported through the even handler. The return code from the operation, e.g. read/write, only tells weather the operation is successfully queued. Examples can be found here.
Writing records
Use fds_record_write()
to write a new record, there’s also a function fds_record_update()
to update a record. To be clear, both of them of create a new record to write to the flash, the difference is updating invalidates the old one afterwards. This (as well as deleting) is designed so to run garbage as few as possible to prolong the life of the flash.
Reading Records
First use fds_record_find()
to search for the record. This return one match record descriptor at a time. Then using the found descriptor, you can read with fds_record_open()
and close afterwards with fds_record_close()
.
Closing a record with
fds_record_close
will not invalidate the recorddescriptor
or thefds_flash_record_t
structure. However, the data pointed to by the fds_flash_record_t structure might change at any time after the record is closed. So if you need to access the data after closing the record, you must open it again.
Deleting Records
With the descriptor returned by previous call of record operations, use fds_record_delete()
to delete a record. As with updating this call does not physically delete a record, but rather invalidates it so it can not be opened/read/write anymore. You must run garbage collection to free the space used by the invalidated records.
Garbage Collection
As stated previously, in order to reclaim flash space occupied by invalidated records either from deleting or updating, you’ll need to call garbage collection method fds_gc()
. This function won’t be run automatically by FDS, instead, must be stated by the application, as detailed in the library:
It is better to run garbage collection when necessary, i.e., when the space in flash is (nearly) full. When the space is exhausted, write requests return the error
FDS_ERR_NO_SPACE_IN_FLASH
, and you must run garbage collection and wait for completion before repeating the call to the write function …
You can also design your own event (like this post) to trigger the garbage collection, particularly with the help of function fds_stat()
, which retrieves useful file system information such as dirty_records, words_used, freeable_words, and corruption.
One thing to pay attention to is trying to run “garbage collection when BLE activity is low otherwise the operation might timeout”.