FDS Implementation Not Working on nRF52832 with NRF5 SDK v13 - Device Not Responding After Flashing

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);
}
user_data.h7752.sdk_config.h
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
};
flash_placement.ld

Parents
  • Hi,

    Looking at your code and configuration this seems sensible to me, with the modified FS_REGISTER_CFG in fds.c. I think the next step is debugging. You write that the device is in an unresponsive state, so some error or similar has happend, and it should be possible to catch that with a debugger. When you debug, what do you find? At which point does things go wrong?

    PS: I do not see your full sdk_config.h, but if you have deferred logging you can turn that off by setting NRF_LOG_DEFERRED to 0, and dependin on where he first error happens, you may see it in the log.

    PSS: I assume this is updating an old project? If not, SDK 13 should not be used as it is an old SDK and there has been imporrtant updates and fixes since then.

  • My device is getting restarted continuously.
    No error logs
    DO you have any reference code for sdkv13
    Yes I am updating the old project.

  • Jay said:
    My device is getting restarted continuously.

    I understand. But something resets it, and deubgging is needed to understand where.

    Jay said:
    DO you have any reference code for sdkv13

    I do not have any reference for this use case. As you have seen, you need to modify the FDS implementation to relocate the FDS pages so this is not direclty supported. It should work, though.

Reply
  • Jay said:
    My device is getting restarted continuously.

    I understand. But something resets it, and deubgging is needed to understand where.

    Jay said:
    DO you have any reference code for sdkv13

    I do not have any reference for this use case. As you have seen, you need to modify the FDS implementation to relocate the FDS pages so this is not direclty supported. It should work, though.

Children
  • Can you please checck my updated files for fds operations
    it is workin fine but after few hours of running (12 hours) after it throws below errors
    what is the reasonf?

    https://drive.google.com/drive/folders/1uVXCjmgdoOuHXuI7CpMnUEIkBTUbt67o?usp=drive_link

    00> No flash space - running GC
    00> No flash space - running GC
    00> No flash space - running GC
    00> Failed to save User data: 0x1001 (error: 7)
    00> No flash space - running GC
    00> No flash space - running GC
    00> No flash space - running GC
    00> Failed to save EIS data: 0x2001 (error: 7)
    00> No flash space - running GC
    00> No flash space - running GC
    00> No flash space - running GC
    00> Failed to save EIS data: 0x2002 (error: 7)
    00> No flash space - running GC
    00> No flash space - running GC
    00> No flash space - running GC
    00> Failed to save EIS data: 0x2003 (error: 7)
    00> No flash space - running GC
    00> No flash space - running GC
    00> No flash space - running GC
    00> Failed to save EIS data: 0x2004 (error: 7)

  • Hi,

    I can only base this on what I see in the log, and based on that it is clear that there is no more room in FDS. I do not know the timing heer or what happens in between logging these lines, though. Are there a lot of dirty records that can be garbage collected, so that you can store data in the end, or is there not? Is it simply that you are attemptint to store more data than what there is room to fit?

    It could make sense to expand the logging a bit to show more context, and particularily the data you get from fds_stat(). This will tell you important parameters, such as the number of freeable_words. If that is low, garbage collection will not help much and you will either need to allocate more pages for FDS or delete more records (which will then make space available after the next garbage collection).

Related