This library provides a convenient way to store information as key-value pairs in non-volatile storage. It targets embedded systems using internal flash (NVM) or external serial memory and is designed to be resilient to power failures and to promote even wear of the underlying storage.
- Supports any storage modeled as a block device, including internal flash or external flash (e.g., via QSPI).
- Allows partitioning storage by instantiating multiple independent library instances.
- Designed to be resilient to power failures.
- Designed to promote even wear of the storage.
- Optional thread-safe operation in RTOS environments.
Use this library when your embedded application needs to:
- Persist configuration settings, calibration data, or device state across power cycles.
- Store data in internal NVM or external serial memory with a simple key-based lookup.
- Handle reliable storage with automatic wear leveling and power-failure resilience.
- Manage multiple independent key-value stores within the same application.
This library is not intended for large data storage or file system use cases. It is optimized for small-to-moderate values where resilience and simplicity are the priorities.
The library works with any block-device-backed storage via the block-storage interface. Choose the usage model that matches your hardware and follow only that path end-to-end.
1. Include the header
#include "mtb_kvstore.h"2. Initialize the HAL NVM driver
HAL API 3.0:
mtb_hal_nvm_t nvm_obj;
mtb_hal_nvm_region_info_t block_info;
cy_rslt_t result = mtb_hal_nvm_setup(&nvm_obj, NULL);
CY_ASSERT(result == CY_RSLT_SUCCESS);
mtb_hal_nvm_info_t nvm_info;
mtb_hal_nvm_get_info(&nvm_obj, &nvm_info);
/* Select the flash region by its explicit index.
* Do NOT use regions[region_count - 1]: on some devices the HAL unconditionally
* appends extra regions (e.g. a dual-bank region on a single-bank device) whose
* start_address may be 0. Passing that address to mtb_kvstore_init will cause a
* HardFault when the library reads the storage during initialization. */
block_info = nvm_info.regions[0]; /* main flash region */HAL API 2.0:
cyhal_nvm_t nvm_obj;
cyhal_nvm_region_info_t block_info;
cy_rslt_t result = cyhal_nvm_init(&nvm_obj);
CY_ASSERT(result == CY_RSLT_SUCCESS);
cyhal_nvm_info_t nvm_info;
cyhal_nvm_get_info(&nvm_obj, &nvm_info);
/* Select the flash region by its explicit index.
* Do NOT use regions[region_count - 1] — see note above. */
block_info = nvm_info.regions[0]; /* main flash region */3. Create the block storage interface
HAL 3.0:
mtb_block_storage_t nvm_bsd;
mtb_block_storage_create_hal_nvm(&nvm_bsd, &nvm_obj);HAL 2.0:
mtb_block_storage_t nvm_bsd;
mtb_block_storage_nvm_create(&nvm_bsd);4. Set the storage region and initialize
The region must be aligned to erase sector boundaries and contain an even number of erase sectors. Only half the provided space is available for key-value data (the other half is the swap area). This example places kv-store in the last 16 pages of the selected region:
uint32_t length = 16U * block_info.block_size;
uint32_t start_addr = block_info.start_address + block_info.size - length;
mtb_kvstore_t obj;
result = mtb_kvstore_init(&obj, start_addr, length, &nvm_bsd, NULL, NULL);
CY_ASSERT(result == CY_RSLT_SUCCESS);NVM-specific pitfalls
- Region selection: Always select the flash region by its explicit index. Never use
regions[region_count - 1]blindly — on some devices the HAL reports extra regions whosestart_addressis0, which causes a HardFault insidemtb_kvstore_init.- Linker script: The flash range allocated to kv-store must be excluded from the application image. Verify that the BSP linker script leaves a matching gap at the top of the flash region so application code is never placed there.
- Non-overlapping placement: Ensure the kv-store region does not overlap the application image, especially when using main flash rather than a dedicated data-flash partition.
Prerequisite: This model requires the serial-memory library. Add it to your project via the ModusToolbox Library Manager, or add the dependency manually to your
deps/folder before building.
1. Include the headers
#include "mtb_kvstore.h"
#include "mtb_serial_memory.h"2. Initialize the serial memory driver
cy_rslt_t result;
#if defined(MTB_SERIAL_MEMORY_VERSION_MAJOR) && (MTB_SERIAL_MEMORY_VERSION_MAJOR == 3)
/* Serial memory version 3 */
mtb_serial_memory_t serial_memory_obj;
cy_stc_smif_mem_context_t mem_context;
cy_stc_smif_mem_info_t mem_info;
/* base_address, sm_clock and smif_block_obj are defined by the user or
automatically generated by the ModusToolbox configurators */
result = mtb_serial_memory_setup(
&serial_memory_obj, MTB_SERIAL_MEMORY_CHIP_SELECT_0,
&base_address, &sm_clock, &mem_context, &mem_info, &smif_block_obj);
#else
/* Serial memory version 2 */
mtb_serial_memory_t serial_memory_obj;
cy_stc_smif_context_t context;
result = mtb_serial_memory_setup(
&serial_memory_obj, MTB_SERIAL_MEMORY_CHIP_SELECT_0,
&base_address, &sm_clock, &context, &smif_block_obj);
#endif3. Create the block storage interface
mtb_block_storage_t serial_memory_bsd;
result = mtb_block_storage_create_serial_memory(&serial_memory_bsd, &serial_memory_obj);
CY_ASSERT(result == CY_RSLT_SUCCESS);4. Set the storage region and initialize
This example allocates 2 erase sectors starting at address 0:
uint32_t start_addr = 0;
uint32_t sector_size = serial_memory_bsd.get_erase_size(serial_memory_bsd.context, start_addr);
uint32_t length = sector_size * 2;
mtb_kvstore_t obj;
result = mtb_kvstore_init(&obj, start_addr, length, &serial_memory_bsd, NULL, NULL);
CY_ASSERT(result == CY_RSLT_SUCCESS);Once initialized, the API is identical regardless of the storage model used.
Write:
uint8_t data[10];
result = mtb_kvstore_write(&obj, "hello", data, sizeof(data));
CY_ASSERT(result == CY_RSLT_SUCCESS);Read:
uint8_t read_data[10];
uint32_t data_size = sizeof(read_data);
result = mtb_kvstore_read(&obj, "hello", read_data, &data_size);
CY_ASSERT(result == CY_RSLT_SUCCESS);Delete:
result = mtb_kvstore_delete(&obj, "hello");
CY_ASSERT(result == CY_RSLT_SUCCESS);Keys and Values
- Keys are null-terminated ASCII strings. Maximum length is
MTB_KVSTORE_MAX_KEY_SIZE(default: 64). Override withDEFINES+=MTB_KVSTORE_MAX_KEY_SIZE=<value>in the application Makefile. - Values are arbitrary-length binary data. By default, each value plus the ~20-byte record
header must fit within one
erase_sizeblock. For larger values, defineMTB_KVSTORE_MAX_VALUE_SIZEin the application Makefile.Note:
MTB_KVSTORE_MAX_VALUE_SIZEis not currently enforced by the library but is reserved for a future update. Defining it now ensures forward compatibility.
RTOS Integration
Enable thread safety by adding COMPONENTS+=RTOS_AWARE or DEFINES+=CY_RTOS_AWARE. This wraps
all API calls in a mutex.
- The library must be initialized after the RTOS kernel has started.
- The default mutex timeout is
MTB_KVSTORE_MUTEX_TIMEOUT_MS. Override withDEFINES+=MTB_KVSTORE_MUTEX_TIMEOUT_MS=<value>. - Write and delete operations may take significantly longer when garbage collection is triggered.
Do not call them from timing-critical code. Use
mtb_kvstore_ensure_capacityproactively in a non-time-sensitive context.
Custom Memory Allocation
Pass non-NULL alloc_memory and free_memory function pointers to mtb_kvstore_init to direct
the library to use a specific memory region for internal allocations instead of the default
malloc/free.
- block-storage library
- serial-memory library (only when using External Serial Memory)
- abstraction-rtos library (only when
CY_RTOS_AWARE/RTOS_AWAREis enabled)
The address space is divided into two equal areas: active and swap. All records are written to the active area. The first record is an area header used during initialization. One half of the provided storage is available for key-value data.
active area swap area
+-----------------------------+ +-----------------------------+
| area header record | | |
+-----------------------------+ | |
| key value record 1 | | |
+-----------------------------+ | |
| key value record 2 | | |
+-----------------------------+ | |
| key value record 3 | | |
+-----------------------------+ | |
| free space | | |
+-----------------------------+ +-----------------------------+
Records are stored sequentially. Each record contains a header with metadata (key/value sizes and CRC), followed by key and value data, padded to the program size. Updates append a new record; the latest record for a key takes precedence. Deletes append a record with a "deleted" flag.
+---------------------+-------------------------+--------------------------------+---------------+
| Record Header | Key | Data | prog size pad |
+---------------------+-------------------------+--------------------------------+---------------+
A RAM table maps a hash of each key to the offset of its latest record in storage. It is built from storage during initialization and updated on every subsequent operation. When a key lookup is performed, the actual key string is verified against storage to resolve hash collisions.
Garbage collection copies all non-obsolete records to the swap area, promotes it to active, and erases the former active area. It is triggered when:
- The active area lacks space for a new modification and contains obsolete records.
- A corrupted record is detected during initialization.
mtb_kvstore_ensure_capacityis called and sufficient free space is not immediately available.
Note: Write and delete operations may be significantly slower when garbage collection runs. Avoid calling them from timing-critical code.
- RELEASE.md - Detailed release notes for all versions
- Reference Manual
- Infineon GitHub
- ModusToolbox Software Environment, Quick Start Guide, Documentation, and Videos
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
© 2021-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG. All rights reserved.