Hello Nordic Support Team,
I am working on a project using the nRF52832 microcontroller with NRF5 SDK version 13. I have implemented the Flash Data Storage (FDS) module to store and manage user data, but it is not working as expected. Specifically, the FDS initialization completes, but write/update operations often fail with errors (e.g., FDS_ERR_NO_SPACE_IN_FLASH or timeouts), or the data is not persisted correctly across resets. Events like FDS_EVT_WRITE or FDS_EVT_UPDATE sometimes don't trigger, and garbage collection seems unreliable.
Additionally, after building and flashing the firmware into the nRF52832, the device is not responding at all. It appears to be bricked or stuck in an unresponsive state, with no UART output or other signs of activity. This happens consistently after flashing the code that includes the FDS implementation.
Setup Details:
- Microcontroller: nRF52832
- SDK Version: NRF5 SDK v13
- FDS Memory Allocation: 80KB dedicated to FDS, ranging from 0x60000 to 0x73FFF (exclusive end at 0x74000).
- Configuration:
- Virtual pages: 20
- Virtual page size: 1024 words (4KB per page, matching physical page size)
- Total FDS flash usage: 20 pages * 4KB = 80KB
- Linker Script: Custom placement for FDS in flash_placement.ld (see attached code).
- SDK Config: Relevant defines from sdk_config.h (see attached code).
- FStorage Config: Configured in fds.c with start/end addresses (see attached code).
- User Data Structures: Defined in user_data.h (see attached code).
- Implementation: Handlers and functions in user_data.c (see attached code).
I have a custom flash placement because the application code and other sections occupy lower flash addresses. FDS is placed higher up to avoid conflicts. The code initializes FDS, registers an event handler, and attempts to write/load/update records in specific files (e.g., file ID 0x0001 for user data, 0x0002 for EIS data).
volatile bool is_fds_initialized = false, is_fds_write_complete = false, is_fds_update_complete = false;
volatile bool is_fds_file_delete_complete = false, is_fds_gc_complete = false;
volatile bool is_fds_record_delete_complete = false;
// fds_record_t f_record;
/**
* @brief A function to handle the events generated by user data storage module
*
* @params[in] - [1] p_evt - A pointer to the user data storage event
*
* @return - None
*
*/
void user_data_event_handler(fds_evt_t const * p_evt)
{
switch(p_evt->id)
{
case FDS_EVT_INIT:
is_fds_initialized = true;
break;
case FDS_EVT_WRITE:
is_fds_write_complete = true;
break;
case FDS_EVT_UPDATE:
is_fds_update_complete = true;
break;
case FDS_EVT_DEL_RECORD:
is_fds_record_delete_complete = true;
break;
case FDS_EVT_DEL_FILE:
is_fds_file_delete_complete = true;
break;
case FDS_EVT_GC:
is_fds_gc_complete = true;
break;
}
}
/**
* @brief A function to flash storage module
*
* @params[in] - None
* @params[out] - None
*
* @return - error_code
*
*/
void flash_storage_init(void)
{
uint32_t fds_error_code;
// memset(&f_record, 0x00, sizeof(f_record));
memset(&nv_user_data, 0x00, sizeof(nv_user_data));
#ifdef EXIT_SIGN
memset(&exit_sign_data,0x00,sizeof(exit_sign_data));
#endif
// Register event handler
fds_error_code = fds_register(user_data_event_handler);
if (fds_error_code == FDS_SUCCESS) {
uart_logf("FDS event handler registered successfully\r\n");
} else {
uart_logf("FDS register failed: %d\r\n", fds_error_code);
}
// Initialize FDS
fds_error_code = fds_init();
if (fds_error_code == FDS_SUCCESS) {
uart_logf("FDS initialized, waiting for completion\r\n");
} else {
uart_logf("FDS init failed: %d\r\n", fds_error_code);
}
// Wait for initialization to complete
while (!is_fds_initialized) {
sd_app_evt_wait();
}
uart_logf("User data module initialization completed\r\n");
}
/**
* @brief A function to store user data
*
* @params[in] - None
*
* @params[out] - [1] p_desc - The descriptor of the record that was written. Pass NULL if you do not
* need the descriptor.
*
* @return - error_code
*
*/
uint32_t save_fds_data(uint16_t file_id, uint16_t record_key)
{
uint32_t fds_error_code;
fds_record_desc_t temp_desc;
fds_record_t f_record;
fds_record_chunk_t record_chunk; // ✅ Create a chunk
memset(&temp_desc, 0x00, sizeof(fds_record_desc_t));
memset(&f_record, 0x00, sizeof(fds_record_t));
memset(&record_chunk, 0x00, sizeof(fds_record_chunk_t));
f_record.file_id = file_id;
f_record.key = record_key;
switch (file_id)
{
case USER_DATA_FILE_ID:
{
switch (record_key)
{
case USER_DATA_RECORD_KEY:
memset(&nv_user_data, 0x00, sizeof(nv_user_data));
memcpy(&nv_user_data, &default_nv_user_data, sizeof(nv_user_data));
record_chunk.p_data = &nv_user_data;
record_chunk.length_words = sizeof(nv_user_data) / 4;
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
break;
}
} break;
#ifdef EXIT_SIGN
case EIS_FILE_ID:
{
switch (record_key)
{
case EIS_AQI_KEY:
memset(&exit_sign_data.aqi_part, 0x00, sizeof(exit_sign_data.aqi_part));
memcpy(&exit_sign_data.aqi_part, &default_exit_sign_data.aqi_part, sizeof(exit_sign_data.aqi_part));
record_chunk.p_data = &exit_sign_data.aqi_part;
record_chunk.length_words = sizeof(exit_sign_data.aqi_part) / 4;
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
break;
case EIS_NODE_KEY:
memset(&exit_sign_data.node_part, 0x00, sizeof(exit_sign_data.node_part));
memcpy(&exit_sign_data.node_part, &default_exit_sign_data.node_part, sizeof(exit_sign_data.node_part));
record_chunk.p_data = &exit_sign_data.node_part;
record_chunk.length_words = sizeof(exit_sign_data.node_part) / 4;
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
break;
case EIS_LSD_KEY:
memset(&exit_sign_data.lsd_part, 0x00, sizeof(exit_sign_data.lsd_part));
memcpy(&exit_sign_data.lsd_part, &default_exit_sign_data.lsd_part, sizeof(exit_sign_data.lsd_part));
record_chunk.p_data = &exit_sign_data.lsd_part;
record_chunk.length_words = sizeof(exit_sign_data.lsd_part) / 4;
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
break;
case EIS_POST_KEY:
memset(&exit_sign_data.post_part, 0x00, sizeof(exit_sign_data.post_part));
memcpy(&exit_sign_data.post_part, &default_exit_sign_data.post_part, sizeof(exit_sign_data.post_part));
record_chunk.p_data = &exit_sign_data.post_part;
record_chunk.length_words = sizeof(exit_sign_data.post_part) / 4;
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
break;
case EIS_MOTION_KEY:
memset(&exit_sign_data.motion_part, 0x00, sizeof(exit_sign_data.motion_part));
memcpy(&exit_sign_data.motion_part, &default_exit_sign_data.motion_part, sizeof(exit_sign_data.motion_part));
record_chunk.p_data = &exit_sign_data.motion_part;
record_chunk.length_words = sizeof(exit_sign_data.motion_part) / 4;
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
break;
}
} break;
#endif
}
fds_error_code = fds_record_write(&temp_desc, &f_record);
if (fds_error_code != FDS_SUCCESS)
return fds_error_code;
if (fds_error_code != FDS_SUCCESS)
return fds_error_code;
while (!is_fds_write_complete);
fds_error_code = fds_gc();
if (fds_error_code != FDS_SUCCESS)
return fds_error_code;
while (!is_fds_gc_complete);
return FDS_SUCCESS;
}
/**
* @brief A function to updated stored user data
*
* @params[in] - None
*
* @params[out] - None
*
* @return - error_code
*
*/
uint32_t update_fds_data(uint16_t file_id, uint16_t record_key)
{
uint32_t fds_error_code;
fds_record_desc_t desc;
fds_find_token_t token;
fds_flash_record_t record;
fds_record_t f_record;
fds_record_chunk_t record_chunk; // ✅ Local chunk
memset(&desc, 0x00, sizeof(desc));
memset(&token, 0x00, sizeof(token));
memset(&f_record, 0x00, sizeof(f_record));
memset(&record_chunk, 0x00, sizeof(record_chunk));
memset(&record, 0x00, sizeof(record));
is_fds_update_complete = false;
// Optional: run garbage collection before update
fds_gc_run();
// Find existing record
fds_error_code = fds_record_find(file_id, record_key, &desc, &token);
if (fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
f_record.file_id = file_id;
f_record.key = record_key;
switch (file_id)
{
case USER_DATA_FILE_ID:
{
switch (record_key)
{
case USER_DATA_RECORD_KEY:
record_chunk.p_data = &nv_user_data;
record_chunk.length_words = sizeof(nv_user_data) / 4;
break;
}
} break;
#ifdef EXIT_SIGN
case EIS_FILE_ID:
{
switch (record_key)
{
case EIS_AQI_KEY:
record_chunk.p_data = &exit_sign_data.aqi_part;
record_chunk.length_words = sizeof(exit_sign_data.aqi_part) / 4;
break;
case EIS_NODE_KEY:
record_chunk.p_data = &exit_sign_data.node_part;
record_chunk.length_words = sizeof(exit_sign_data.node_part) / 4;
break;
case EIS_LSD_KEY:
record_chunk.p_data = &exit_sign_data.lsd_part;
record_chunk.length_words = sizeof(exit_sign_data.lsd_part) / 4;
break;
case EIS_POST_KEY:
record_chunk.p_data = &exit_sign_data.post_part;
record_chunk.length_words = sizeof(exit_sign_data.post_part) / 4;
break;
case EIS_MOTION_KEY:
record_chunk.p_data = &exit_sign_data.motion_part;
record_chunk.length_words = sizeof(exit_sign_data.motion_part) / 4;
break;
}
} break;
#endif
}
// ✅ Assign the chunk pointer and number of chunks
f_record.data.p_chunks = &record_chunk;
f_record.data.num_chunks = 1;
// Update record
fds_error_code = fds_record_update(&desc, &f_record);
if (fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
while (!is_fds_update_complete)
{
sd_app_evt_wait();
}
// Optional garbage collection
fds_error_code = fds_gc();
if (fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
while (!is_fds_gc_complete){
sd_app_evt_wait();
}
return FDS_SUCCESS;
}
/**
* @brief A function to load stored user data into config_data
*
* @params[in] - None
*
* @params[out] - None
*
* @return - error_code
*
*/
uint32_t load_fds_data(uint16_t file_id, uint16_t record_key)
{
uint8_t i = 0;
uint32_t fds_error_code;
fds_record_desc_t desc;
fds_flash_record_t record;
fds_find_token_t token;
memset(&desc, 0x00, sizeof(fds_record_desc_t));
memset(&record, 0x00, sizeof(fds_flash_record_t));
memset(&token, 0x00, sizeof(fds_find_token_t));
fds_error_code = fds_record_find(file_id, record_key, &desc, &token);
if(fds_error_code == FDS_SUCCESS)
{
fds_error_code = fds_record_open(&desc, &record);
if(fds_error_code == FDS_SUCCESS)
{
switch (file_id)
{
case USER_DATA_FILE_ID:{
if(record_key == USER_DATA_RECORD_KEY){
memcpy(&nv_user_data, record.p_data, sizeof(nv_user_data));
}
}break;
#ifdef EXIT_SIGN
case EIS_FILE_ID:{
switch(record_key)
{
case EIS_AQI_KEY:
memcpy(&exit_sign_data.aqi_part, record.p_data, sizeof(exit_sign_data.aqi_part));
break;
case EIS_NODE_KEY:
memcpy(&exit_sign_data.node_part, record.p_data, sizeof(exit_sign_data.node_part));
break;
case EIS_LSD_KEY:
memcpy(&exit_sign_data.lsd_part, record.p_data, sizeof(exit_sign_data.lsd_part));
break;
case EIS_POST_KEY:
memcpy(&exit_sign_data.post_part, record.p_data, sizeof(exit_sign_data.post_part));
break;
case EIS_MOTION_KEY:
memcpy(&exit_sign_data.motion_part, record.p_data, sizeof(exit_sign_data.motion_part));
break;
}
}break;
#endif
}
fds_error_code = fds_record_close(&desc);
if(fds_error_code == FDS_SUCCESS)
{
return FDS_SUCCESS;
}
}
}
return fds_error_code;
}
/**
* @brief A function to delete user data
*
* @params[in] - None
*
* @params[ou] - None
*
* @return error_code
*
*/
uint32_t fds_delete_record(uint16_t file_id, uint16_t record_key)
{
uint32_t fds_error_code;
fds_record_desc_t desc;
fds_find_token_t token;
memset(&desc, 0x00, sizeof(fds_record_desc_t));
memset(&token, 0x00, sizeof(fds_find_token_t));
is_fds_record_delete_complete = false;
is_fds_gc_complete = false;
fds_error_code = fds_record_find( file_id, record_key, &desc, &token);
if(fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
else
{
fds_error_code = fds_record_delete(&desc);
if(fds_error_code != FDS_SUCCESS)
uart_logf("FDS ERR: %d, Line:%d, File:%s\r\n",fds_error_code,__LINE__,__FILE__);
while(!is_fds_record_delete_complete);
fds_error_code = fds_gc();
if(fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
while(!is_fds_gc_complete);
}
return FDS_SUCCESS;
}
/**
* @brief A function to delete user file
*
* @params[in] - None
*
* @params[ou] - None
*
* @return error_code
*
*/
uint32_t fds_delete_file(uint16_t file_id, uint16_t record_key)
{
uint32_t fds_error_code;
fds_record_desc_t desc;
fds_find_token_t token;
memset(&desc, 0x00, sizeof(fds_record_desc_t));
memset(&token, 0x00, sizeof(fds_find_token_t));
is_fds_record_delete_complete = false;
is_fds_gc_complete = false;
fds_error_code = fds_record_find( file_id, record_key, &desc, &token);
if(fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
else
{
fds_error_code = fds_file_delete(file_id);
while(!is_fds_file_delete_complete);
fds_error_code = fds_gc();
if(fds_error_code != FDS_SUCCESS)
{
return fds_error_code;
}
while(!is_fds_gc_complete);
}
return FDS_SUCCESS;
}
/**
* @brief A function to check if the data is already written
*
* @parmas[in] - None
*
* @params[out] - None
*
* @return - true/false
*
*/
bool is_record_available(uint16_t file_id, uint16_t record_key)
{
uint32_t fds_error_code;
fds_record_desc_t data_record_desc;
fds_find_token_t data_token;
memset(&data_record_desc, 0x00, sizeof(fds_record_desc_t));
memset(&data_token, 0x00, sizeof(fds_find_token_t));
fds_error_code = fds_record_find( file_id, record_key, &data_record_desc, &data_token );
if(fds_error_code != FDS_SUCCESS)
{
return false;
}
return true;
}
/**
* @brief Garbage collection
*/
void fds_gc_run(void)
{
uint32_t fds_error_code;
is_fds_gc_complete = false;
fds_error_code = fds_gc();
while(!is_fds_gc_complete);
}
/**@brief A function to load/store data all the configuration data
**/
void load_all_fds_data(void)
{
uint16_t i = 0x00, j = 0x00;
uint32_t error_code;
if(!is_record_available(USER_DATA_FILE_ID,USER_DATA_RECORD_KEY))
{
error_code = save_fds_data(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY);
if(error_code != FDS_SUCCESS)
NVIC_SystemReset();
} else {
error_code = load_fds_data(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY);
if(error_code != FDS_SUCCESS)
NVIC_SystemReset();
}
#ifdef EXIT_SIGN
for(i = EIS_AQI_KEY; i <= EIS_MOTION_KEY; i++ )
{
if(!is_record_available(EIS_FILE_ID, i))
{
error_code = save_fds_data(EIS_FILE_ID, i);
if(error_code != FDS_SUCCESS)
NVIC_SystemReset();
j += 1;
}
else
{
error_code = load_fds_data(EIS_FILE_ID, i);
if(error_code != FDS_SUCCESS)
NVIC_SystemReset();
if(i == EIS_AQI_KEY)
{
if(exit_sign_data.aqi_part.valid_signature != FLASH_PAGE_VALID)
{
fds_delete_file(EIS_FILE_ID, i);
fds_gc_run();
error_code = save_fds_data(EIS_FILE_ID, i);
if(error_code != FDS_SUCCESS)
NVIC_SystemReset();
}
}
}
}
#endif
#if 0
if(FW_VERSION != config_data.fw_version_num)
{
config_data.fw_version_num = FW_VERSION;
error_code = user_data_module_update_stored_data(CONFIG_FILE_ID, CONFIG_RECORD_KEY);
if(error_code != NRF_SUCCESS)
NVIC_SystemReset();
}
#endif
if(j > 0) // reset if the data is stored for the first time
NVIC_SystemReset();
// global_unix_time = nv_user_data.last_correct_unix_time;
// uart_logf("loaded time from the flash: %d\r\n", global_unix_time);
}
FS_REGISTER_CFG(fs_config_t fs_config) = {
.callback = fs_event_handler,
.num_pages = 20, // 4 pages * 4KB = 16KB
.priority = 0xFF,
.p_start_addr = (uint32_t*)0x60000,
.p_end_addr = (uint32_t*)0x74000 // Exclusive: ends at 0x78FFF
};