Emergency data storage

Overview

The emergency data storage (EMDS) library provides persistent storage functionality designed to prevent the wear and tear of the flash memory. Its intended use is for storing of data undergoing frequent changes during runtime.

Implementation

The CONFIG_EMDS Kconfig option enables the emergency data storage.

The application must initialize the pre-allocated flash area by using the emds_init() function. The CONFIG_EMDS_SECTOR_COUNT option defines how many sectors should be used to store data.

The allocated storage space must be larger than the combined data stored by the application. Allocating a larger flash area will demand more resources, but also increase the life expectancy of the flash. The chosen size should reflect the amount of data stored, the available flash resources, and how the application calls the emds_store() function. In general, it should not be necessary to allocate a large flash area, since only a limited set of data should be stored to ensure swift completion of writing the flash on shutdown.

The memory location that is going to be stored must be added on initialization. All memory areas must be provided through entries containing an ID, data pointer, and data length, using the emds_entry_add() function and the EMDS_STATIC_ENTRY_DEFINE macro. Entries to be stored when the emergency data storage is triggered need their own unique IDs that are not changed after a reboot.

When all entries are added, the emds_load() function restores the entries into the memory areas from the flash.

After restoring the previous data, the application must run the emds_prepare() function to prepare the flash area for receiving new entries. If the remaining empty flash area is smaller than the required data size, the flash area will be automatically erased to increase the available flash area.

The storage is done in deterministic time, so it is possible to know how long it takes to store all registered entries. However, this is chip-dependent, so it is important to measure the time. Find timing values under the “Electrical specification” section for the non-volatile memory controller in the Product Specification for the relevant SoC or the SiP you are using. For example, for the nRF9160 SiP, see the Electrical specification of nRF9160 page.

The following Kconfig options can be configured:

When configuring these values, consider the time for erase when doing garbage collection in NVS. If partial erase is not enabled or supported, the time of a complete sector erase has to be included in the CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US. When partial erase is enabled and supported by the hardware, include the time it takes for the scheduler to trigger, which is depending on the time defined in CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS. When changing the CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US option, it is important that the worst time is considered.

The application must call the emds_store() function to store all entries. This can only be done once, before the emds_prepare() function must be called again. When invoked, the emds_store() function stores all the registered entries. Invocation of this call should be performed when the application detects loss of power, or when a reboot is triggered.

Note

Before calling the emds_store() function, the application should try shutting down the application-specific features that consume a lot of power. Shutting down these features may prolong the time the CPU is alive, and improve the storage time. For example, if Bluetooth is used, disabling Bluetooth before shutdown will save power, and stopping the MPSL scheduler will shorten the total time required to complete the store operation.

The emds_is_ready() function can be called to check if EMDS is prepared to store the data.

Once the data storage has completed, a callback is called if provided in emds_init(). This callback notifies the application that the data storage has completed, and can be used to reboot the CPU or execute another function that is needed.

After completion of emds_store(), the emds_is_ready() function call will return error, since it can no longer guarantee that the data will fit into the flash area.

The above described process is summarized in a message sequence diagram.

msc {
hscale = "1.3";
Application, EMDS;
--- [ label = "Application initialization started" ];
Application=>EMDS         [ label = "emds_init(emds_store_cb_t)" ];
--- [ label = "Initialization of all functionality that does emds_entry_add()" ];
Application=>EMDS         [ label = "emds_entry_add(1)" ];
Application=>EMDS         [ label = "emds_entry_add(2)" ];
...;
Application=>EMDS         [ label = "emds_entry_add(n)" ];
--- [ label = "All emds_entry_add() executed" ];
Application=>EMDS         [ label = "emds_load()" ];
Application=>EMDS         [ label = "emds_prepare()" ];
--- [ label = "Application initialization ended" ];
...;
Application->Application  [ label = "Interrupt calling emds_store()" ];
Application=>EMDS         [ label = "emds_store()" ];
Application<<=EMDS        [ label = "emds_store_cb_t callback" ];
Application->Application [ label = "Reboot/halt" ];
}

Requirements

To prevent frequent writes to flash memory, the EMDS library can write data to flash only when the device is shutting down. EMDS restores the application data to RAM at reboot.

EMDS can store data within a guaranteed time, based on the amount of data being stored. EMDS can be used to store data in memory in situations of critical power shortage, for example before the device battery is depleted. It is important that the hardware has the appropriate functionality to sustain power long enough for the storage to be completed before the power source is fully discharged.

Configuration

To initialize the emergency data storage, complete the following steps:

  1. Enable the CONFIG_EMDS Kconfig option.

  2. Include the emds/emds.h file in your main.c file.

  3. Create the callback function emds_store_cb_t() that can execute functions after storage has completed. This is optional.

  4. Call the emds_init() function.

  5. Add RAM areas that shall be loaded/stored through emds_entry_add() calls.

  6. Call emds_load().

  7. Call emds_prepare().

  8. Create interrupt or other functionality that will call emds_store().

Application integration

When using EMDS in an application, you need to know the worst case scenario for how long power is required to be available. This knowledge makes it possible for you to make good design choices ensuring enough backup power to reach this time requirement.

The easiest way of computing an estimate of the time required to store all entries, in a worst case scenario, is to call the emds_store_time_get() function. This function returns a worst-case storage time estimate in microseconds (µs) for a given application. For this to work, Kconfig options CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US, CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US and CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US need to be set as described in the Implementation section. The emds_store_time_get() function estimates the required worst-case time to store \(n\) entries using the following formula:

\[t_\text{store} = t_\text{base} + \sum_{i = 1}^n \left(t_\text{entry} + t_\text{word}\left(\left\lceil\frac{s_\text{ate}}{s_\text{block}}\right\rceil + \left\lceil\frac{s_i}{s_\text{block}}\right\rceil \right)\right)\]

where \(t_\text{base}\) is the value specified by CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US, \(t_\text{entry}\) is the value specified by CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US and \(t_\text{word}\) is the value specified by CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US. \(s_i\) is the size of the \(i\)th entry in bytes and \(s_\text{block}\) is the number of bytes in one word of flash. These can be found by looking at datasheets, driver documentation, and the configuration of the application. \(s_\text{ate}\) is the size of the allocation table entry used by the EMDS flash module, which is 8 B.

Example of time estimation

Using the formula from the previous section, you can estimate the time required to store all entries for the Bluetooth Mesh: Light fixture sample running on the nRF52840. The following values can be inserted into the formula:

  • Set \(t_\text{base}\) = 9000 µs. This is the worst case overhead when a store is triggered in the middle of an erase on nRF52840 with CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE enabled in the sample by default, and should be adjusted when using other configurations.

  • Set \(t_\text{entry}\) = 300 µs and \(t_\text{word}\) = 41 µs. Note: These values are valid only for this specific chip and configuration, and should be computed for the specific configuration whenever using EMDS.

  • The sample uses two entries, one for the RPL with 255 entries (\(s_i\) = 2040 B) and one for the lightness state (\(s_i\) = 3 B).

  • The flash write block size \(s_\text{block}\) in this case is 4 B, and the ATE size \(s_\text{ate}\) is 8 B.

This gives the following formula to compute estimated storage time:

\[\begin{split}\begin{aligned} t_\text{store} = 9000\text{ µs} &+ \left( 300\text{ µs} + 41\text{ µs} \times \left( \left\lceil\frac{8\text{ B}}{4\text{ B}}\right\rceil + \left\lceil\frac{2040\text{ B}}{4\text{ B}}\right\rceil \right) \right) \\ &+ \left( 300\text{ µs} + 41\text{ µs} \times \left( \left\lceil\frac{8\text{ B}}{4\text{ B}}\right\rceil + \left\lceil\frac{3\text{ B}}{4\text{ B}}\right\rceil \right) \right) \\ &= 30715\text{ µs} \end{aligned}\end{split}\]

Calling the emds_store_time_get() function in the sample automatically computes the result of the formula and returns 30715.

Limitations

The power-fail comparator for the nRF528xx cannot be used with EMDS, as it will prevent the NVMC from performing write operations to flash.

Dependencies

The emergency data storage is dependent on these Kconfig options:

API documentation

Header file: include/emds/emds.h
Source file: subsys/emds/emds.c
Emergency Data Storage Library