Hello everyone!
We are using nRF51822 SoC together with s130 Softdevice. nRF5 SDK v12.3.0 is used for firmware development together with the Segger Embedded Studio.
Among the other things, we have a need to store one 32-bit variable in non-volatile flash memory. Consequently, we decided to use Flash Data Storage (FDS) for that purpose.
We can properly initialize the FDS module by using:
ret_code_t ret = fds_register(fds_evt_handler);
if (ret != FDS_SUCCESS)
{
// Registering of the event handler has failed.
}
ret_code_t ret = fds_init();
if (ret != FDS_SUCCESS)
{
// Handle error.
}
as it is described here.
However, the trouble comes when we want to write a record to the flash with the fds_record_write() function (link). The function returns FDS_SUCCESS which implies that the write record operation was queued successfully but we never get the FDS_EVT_WRITE event inside the event handler (fds_evt_handler()).
Do you have any idea what we are missing here? We are using FDS in combination with the Softdevice. Perhaps that can be an issue? Do we need any particular SES settings that will enable FDS storage?
Here attached you can find nvs_storage.c/h modules that contain FDS-related code. As you can see, when we call the init_nvs_storage() function for the very first time, there will be no record in the flash, and create_blank_ouid_records() function will be called. Within that function, we try to call fds_record_write() that returns FDS_SUCCESS but FDS_EVT_WRITE event is never detected inside the event handler (fds_evt_handler()).
Thanks in advance for your time and efforts. Looking forward to hearing from you.
Cheers!
Bojan.
//--------------------------------- INCLUDES ----------------------------------
#include "nvs_storage.h"
#include "fds.h"
#include "bond3_watchdog.h"
#include "nrf_pwr_mgmt.h"
#include "main.h"
#define NRF_LOG_MODULE_NAME "nvs_storage"
#define NRF_LOG_LEVEL 3
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
//---------------------------------- MACROS -----------------------------------
#define FILE_ID 0x1111
#define REC_KEY 0x2222
#define OUID_FILE_ID (0x0020)
#define OUID_BACKUP_FILE_ID (0x0030)
#define OUID_REC_KEY (0x0010)
//---------------------- PRIVATE FUNCTION PROTOTYPES --------------------------
static void run_garbage_collector(void);
static void fds_evt_handler(fds_evt_t const *p_evt);
static void wait_for_fds_ready(void);
static void wait_for_gc_to_finish(void);
static void wait_for_fds_update(void);
static void wait_for_fds_write(void);
static void wait_for_fds_delete(void);
static uint32_t init_fds_and_wait_until_ready(void);
static uint32_t check_for_fds_corruption(fds_stat_t *p_fds_stat);
static uint32_t create_blank_ouid_records(void);
static uint32_t clear_ouid_data(void);
static uint32_t write_ouid_data_to_flash(void);
static uint32_t search_for_and_init_ouid_data(FDSSysDataComponents_t *p_fds_sys_data);
static uint32_t open_ouid_data_record(FDSSysDataComponents_t *p_fds_sys_data);
static uint32_t open_ouid_data_backup_record(FDSSysDataComponents_t *p_fds_sys_data);
static uint32_t handle_possible_ouid_data_corruptions(FDSSysDataComponents_t *p_fds_sys_data);
static void dump_all_system_data_to_console(void);
static void dump_ouid_data(void);
//------------------------- STATIC DATA & CONSTANTS ---------------------------
/* Flag to check fds initialization. */
static bool volatile m_fds_initialized = false;
static bool volatile m_gc_is_running = false;
static bool volatile m_fds_is_writing = false;
static bool volatile m_fds_is_updating = false;
static bool volatile m_fds_is_deleting = false;
static uint32_t ouid_ = 0;
static uint32_t ouid_backup_ = 0;
static bool ouid_is_stored_ = false;
static bool _is_ouid_data_valid = false;
static bool _is_ouid_data_backup_valid = false;
/* Array to map FDS return values to strings. */
//NOTE(bojankoce): this serves for debug only. Could be deleted to save some space, if needed.
char const *fds_err_str[] = {
"FDS_SUCCESS",
"FDS_ERR_OPERATION_TIMEOUT",
"FDS_ERR_NOT_INITIALIZED",
"FDS_ERR_UNALIGNED_ADDR",
"FDS_ERR_INVALID_ARG",
"FDS_ERR_NULL_ARG",
"FDS_ERR_NO_OPEN_RECORDS",
"FDS_ERR_NO_SPACE_IN_FLASH",
"FDS_ERR_NO_SPACE_IN_QUEUES",
"FDS_ERR_RECORD_TOO_LARGE",
"FDS_ERR_NOT_FOUND",
"FDS_ERR_NO_PAGES",
"FDS_ERR_USER_LIMIT_REACHED",
"FDS_ERR_CRC_CHECK_FAILED",
"FDS_ERR_BUSY",
"FDS_ERR_INTERNAL",
};
/* Array to map FDS events to strings. */
static char const *fds_evt_str[] = {
"FDS_EVT_INIT", "FDS_EVT_WRITE", "FDS_EVT_UPDATE",
"FDS_EVT_DEL_RECORD", "FDS_EVT_DEL_FILE", "FDS_EVT_GC",
};
static fds_record_desc_t ouid_fds_desc_ = {0}; // Descriptor for our ouid
static fds_record_desc_t ouid_backup_fds_desc_ = {0}; // Descriptor for our backup ouid
static const fds_record_chunk_t ouid_data_chunk = {
.p_data = &ouid_,
.length_words = 1
};
static const fds_record_chunk_t ouid_backup_data_chunk = {
.p_data = &ouid_backup_,
.length_words = 1
};
static fds_record_t const ouid_record = { // Record to hold our ouid
.file_id = OUID_FILE_ID,
.key = OUID_REC_KEY,
.data.p_chunks = &ouid_data_chunk,
.data.num_chunks = 1
};
static fds_record_t const ouid_backup_record = { // Record to hold our backup ouid
.file_id = OUID_BACKUP_FILE_ID,
.key = OUID_REC_KEY,
.data.p_chunks = &ouid_backup_data_chunk,
.data.num_chunks = 1
};
/* Keep track of the progress of a delete_all operation. */
static struct {
bool delete_next; //!< Delete next record.
bool pending; //!< Waiting for an fds FDS_EVT_DEL_RECORD event, to delete
//! the next record.
} m_delete_all;
//---------------------------- PRIVATE FUNCTIONS ------------------------------
static void fds_evt_handler(fds_evt_t const *p_evt) {
//NRF_LOG_DEBUG("Event: %s received (%s)", fds_evt_str[p_evt->id],
// fds_err_str[p_evt->result]);
switch (p_evt->id) {
case FDS_EVT_INIT:
NRF_LOG_INFO("FDS_EVT_INIT! 0x%X\r\n", p_evt->result);
if (p_evt->result == FDS_SUCCESS) {
m_fds_initialized = true;
NRF_LOG_INFO("FDS initialized!\r\n");
}
break;
case FDS_EVT_GC:
NRF_LOG_INFO("FDS_EVT_GC!\r\n");
m_gc_is_running = false;
break;
case FDS_EVT_WRITE: {
if (p_evt->result == FDS_SUCCESS) {
NRF_LOG_DEBUG("Record ID:\t0x%04x", p_evt->write.record_id);
NRF_LOG_DEBUG("File ID:\t0x%04x", p_evt->write.file_id);
NRF_LOG_DEBUG("Record key:\t0x%04x", p_evt->write.record_key);
}
NRF_LOG_INFO("FDS_EVT_WRITE! 0x%X\r\n", p_evt->result);
m_fds_is_writing = false;
} break;
case FDS_EVT_DEL_FILE:
case FDS_EVT_DEL_RECORD: {
if (p_evt->result == FDS_SUCCESS) {
NRF_LOG_DEBUG("Record ID:\t0x%04x", p_evt->del.record_id);
NRF_LOG_DEBUG("File ID:\t0x%04x", p_evt->del.file_id);
NRF_LOG_DEBUG("Record key:\t0x%04x", p_evt->del.record_key);
}
NRF_LOG_INFO("FDS_EVT_DEL_FILE/RECORD! 0x%X\r\n", p_evt->result);
m_fds_is_deleting = false;
m_delete_all.pending = false;
} break;
case FDS_EVT_UPDATE: {
if (p_evt->result == FDS_SUCCESS) {
NRF_LOG_DEBUG("Record ID:\t0x%04x", p_evt->write.record_id);
NRF_LOG_DEBUG("File ID:\t0x%04x", p_evt->write.file_id);
NRF_LOG_DEBUG("Record key:\t0x%04x", p_evt->write.record_key);
}
NRF_LOG_INFO("FDS_EVT_UPDATE!: 0x%X\r\n", p_evt->result);
m_fds_is_updating = false;
} break;
default:
break;
}
}
/**@brief Wait for fds to initialize. */
static void wait_for_fds_ready(void) {
while (!m_fds_initialized) {
//nrf_pwr_mgmt_run();
}
}
static void wait_for_gc_to_finish(void) {
while (m_gc_is_running)
;
}
static void wait_for_fds_update(void) {
while (m_fds_is_updating)
;
}
static void wait_for_fds_write(void) {
while (m_fds_is_writing)
;
}
static void wait_for_fds_delete(void) {
while (m_fds_is_deleting)
;
}
static void run_garbage_collector(void) {
uint32_t err_code;
NRF_LOG_DEBUG("Running garbage collector.\r\n");
m_gc_is_running = true;
feed_wdt();
err_code = fds_gc();
feed_wdt();
if (err_code) {
//NRF_LOG_WARNING("GC failure: %s", fds_err_str[err_code]);
NRF_LOG_WARNING("GC failure: %d", err_code);
return;
}
wait_for_gc_to_finish();
}
static uint32_t init_fds_and_wait_until_ready(void) {
NRF_LOG_INFO("init_fds_and_wait_until_ready::Starting Process\r\n");
uint32_t err_code;
/* Register first to receive an event when initialization is complete. */
err_code = fds_register(fds_evt_handler);
NRF_LOG_INFO("fds_register: 0x%X\r\n", err_code);
if (err_code) {
//APP_ERROR_CHECK(err_code);
return err_code;
}
err_code = fds_init();
NRF_LOG_INFO("fds_init: 0x%X\r\n", err_code);
if (err_code == FDS_ERR_NO_PAGES) {
NRF_LOG_ERROR("FDS found NO Valid pages. Attempting to erase ALL FDS pages.\r\n");
//TODO(bojankoce): check if it is needed to erase all FDS pages!
feed_wdt();
perform_system_reset();
} else if (err_code) {
//APP_ERROR_CHECK(err_code);
return err_code;
}
feed_wdt();
wait_for_fds_ready();
return err_code;
}
static uint32_t check_for_fds_corruption(fds_stat_t *p_fds_stat) {
NRF_LOG_INFO("check_for_fds_corruption::Starting Process\r\n");
uint32_t err_code;
bool gc_required = false;
if (!p_fds_stat) {
NRF_LOG_ERROR("Null Pointer\r\n");
return NRF_ERROR_NULL;
}
err_code = fds_stat(p_fds_stat);
if (err_code) {
//APP_ERROR_CHECK(err_code);
return err_code;
}
NRF_LOG_INFO("Found %d valid records.\r\n", p_fds_stat->valid_records);
NRF_LOG_INFO("Found %d dirty records.\r\n", p_fds_stat->dirty_records);
if (p_fds_stat->dirty_records >=
MAX_DIRTY_RECORDS_BEFORE_GARBAGE_COLLECTION) {
NRF_LOG_WARNING("Sufficient dirty records to run GC.\r\n");
gc_required = true;
}
if (gc_required) {
feed_wdt();
run_garbage_collector();
}
return err_code;
}
static uint32_t search_for_and_init_ouid_data(FDSSysDataComponents_t *p_fds_sys_data) {
uint32_t err_code;
if (!p_fds_sys_data) {
NRF_LOG_ERROR("Null Pointer\r\n");
return NRF_ERROR_NULL;
}
//APP_ERROR_CHECK(open_ouid_data_record(p_fds_sys_data));
//APP_ERROR_CHECK(open_ouid_data_backup_record(p_fds_sys_data));
err_code = open_ouid_data_record(p_fds_sys_data);
NRF_LOG_INFO("open_ouid_data_record: 0x%X\r\n", err_code);
err_code = open_ouid_data_backup_record(p_fds_sys_data);
NRF_LOG_ERROR("open_ouid_data_backup_record: 0x%X\r\n", err_code);
//err_code = (handle_possible_ouid_data_corruptions(p_fds_sys_data));
//APP_ERROR_CHECK(err_code);
err_code = handle_possible_ouid_data_corruptions(p_fds_sys_data);
NRF_LOG_INFO("handle_possible_ouid_data_corruptions: 0x%X\r\n", err_code);
return err_code;
}
static uint32_t open_ouid_data_record(FDSSysDataComponents_t *p_fds_sys_data) {
uint32_t err_code;
if (!p_fds_sys_data) {
NRF_LOG_ERROR("Null Pointer\r\n");
return NRF_ERROR_NULL;
}
err_code = fds_record_find(ouid_record.file_id,
ouid_record.key, &ouid_fds_desc_,
&(p_fds_sys_data->sys_data_ouid_token));
NRF_LOG_INFO("fds_record_find: 0x%X\r\n", err_code);
if (!err_code) {
NRF_LOG_INFO("System Data OUID found\r\n");
err_code = fds_record_open(&ouid_fds_desc_,
&(p_fds_sys_data->sys_data_ouid_record));
if (!err_code) {
NRF_LOG_WARNING("System Data OUID valid\r\n");
_is_ouid_data_valid = true;
} else {
NRF_LOG_WARNING("System Data OUID invalid\r\n");
_is_ouid_data_valid = false;
}
} else {
NRF_LOG_INFO("System Data OUID not found\r\n");
}
return err_code;
}
static uint32_t open_ouid_data_backup_record(FDSSysDataComponents_t *p_fds_sys_data) {
uint32_t err_code;
if (!p_fds_sys_data) {
NRF_LOG_ERROR("Null Pointer\r\n");
return NRF_ERROR_NULL;
}
err_code = fds_record_find(
ouid_backup_record.file_id, ouid_backup_record.key,
&ouid_backup_fds_desc_,
&(p_fds_sys_data->sys_data_ouid_backup_token));
if (!err_code) {
NRF_LOG_INFO("System Data OUID Backup found\r\n");
err_code =
fds_record_open(&ouid_backup_fds_desc_,
&(p_fds_sys_data->sys_data_ouid_backup_record));
if (!err_code) {
NRF_LOG_WARNING("System Data OUID Backup valid\r\n");
_is_ouid_data_backup_valid = true;
} else {
NRF_LOG_WARNING("System Data OUID Backup invalid\r\n");
_is_ouid_data_backup_valid = false;
}
} else {
NRF_LOG_INFO("System Data OUID Backup not found\r\n");
}
return err_code;
}
static uint32_t handle_possible_ouid_data_corruptions(FDSSysDataComponents_t *p_fds_sys_data) {
uint32_t err_code;
if (!p_fds_sys_data) {
NRF_LOG_ERROR("Null pointer\r\n");
return NRF_ERROR_NULL;
}
if (_is_ouid_data_valid && _is_ouid_data_backup_valid) {
// All OUID data is valid
NRF_LOG_INFO("All system data ouid records are intact.\r\n");
memcpy(&ouid_,
p_fds_sys_data->sys_data_ouid_record.p_data,
sizeof(ouid_));
memcpy(&ouid_backup_,
p_fds_sys_data->sys_data_ouid_backup_record.p_data,
sizeof(ouid_));
/* Close the records when done reading. */
err_code = fds_record_close(&ouid_fds_desc_);
APP_ERROR_CHECK(err_code);
err_code = fds_record_close(&ouid_backup_fds_desc_);
APP_ERROR_CHECK(err_code);
} else if (!_is_ouid_data_valid && _is_ouid_data_backup_valid) {
NRF_LOG_WARNING("Using system data OUID backup.\r\n");
memcpy(&ouid_,
p_fds_sys_data->sys_data_ouid_backup_record.p_data,
sizeof(ouid_));
memcpy(&ouid_backup_,
p_fds_sys_data->sys_data_ouid_backup_record.p_data,
sizeof(ouid_));
err_code = fds_record_close(&ouid_backup_fds_desc_);
APP_ERROR_CHECK(err_code);
} else if (_is_ouid_data_valid && !_is_ouid_data_backup_valid) {
NRF_LOG_WARNING("System data OUID backup invalid. Using system data OUID data.\r\n");
memcpy(&ouid_,
p_fds_sys_data->sys_data_ouid_record.p_data,
sizeof(ouid_));
memcpy(&ouid_backup_,
p_fds_sys_data->sys_data_ouid_record.p_data,
sizeof(ouid_));
err_code = fds_record_close(&ouid_fds_desc_);
APP_ERROR_CHECK(err_code);
} else {
NRF_LOG_WARNING("Neither system data OUID nor system data OUID backup were valid! \r\n");
err_code = NRF_ERROR_NOT_FOUND;
}
return err_code;
}
static uint32_t create_blank_ouid_records(void){
uint32_t err_code = NRF_SUCCESS;
// OUID data
NRF_LOG_INFO("Creating new ouid data file\r\n");
m_fds_is_writing = true;
err_code = fds_record_write(&ouid_fds_desc_, &ouid_record);
NRF_LOG_INFO("fds_record_write: 0x%X\r\n", err_code);
if (err_code) {
APP_ERROR_CHECK(err_code);
return err_code;
} else {
NRF_LOG_INFO("fds_record_write OK!\r\n");
}
wait_for_fds_write();
NRF_LOG_INFO("Creating new ouid backup file\r\n");
m_fds_is_writing = true;
err_code = fds_record_write(&ouid_backup_fds_desc_,
&ouid_backup_record);
if (err_code) {
APP_ERROR_CHECK(err_code);
return err_code;
}
wait_for_fds_write();
return err_code;
}
static uint32_t clear_ouid_data(void){
uint32_t virgin_ouid = 0;
return set_ouid(&virgin_ouid);
}
static void dump_all_system_data_to_console(void){
dump_ouid_data();
}
static void dump_ouid_data(void){
NRF_LOG_INFO("OUID: 0x%X\r\n", ouid_);
NRF_LOG_INFO("Backup OUID: 0x%X\r\n", ouid_backup_);
}
//------------------------------ PUBLIC FUNCTIONS -----------------------------
uint32_t init_nvs_storage(void) {
uint32_t err_code;
FDSSysDataComponents_t fds_sys_data;
memset(&fds_sys_data, 0, sizeof(fds_sys_data));
err_code = init_fds_and_wait_until_ready();
//NRF_LOG_INFO("init_fds_and_wait_until_ready: 0x%X\r\n", err_code);
if (err_code) {
APP_ERROR_CHECK(err_code);
return err_code;
}
err_code = check_for_fds_corruption(&(fds_sys_data.fds_stat));
//NRF_LOG_INFO("check_for_fds_corruption: 0x%X\r\n", err_code);
if (err_code) {
APP_ERROR_CHECK(err_code);
return err_code;
}
err_code = search_for_and_init_ouid_data(&fds_sys_data);
NRF_LOG_INFO("search_for_and_init_ouid_data: 0x%X\r\n", err_code);
if (err_code) {
NRF_LOG_WARNING("Unable to load ouid from internal flash. Will "
"create virgin records: 0x%X\r\n",
err_code);
err_code = create_blank_ouid_records();
if (err_code) {
NRF_LOG_ERROR("Unable to create new OUID records: 0x%X\r\n",
err_code);
} else {
NRF_LOG_INFO("OUID records newly created\r\n");
}
} else {
NRF_LOG_INFO("OUID records loaded from internal flash\r\n");
}
//TODO(bojankoce): We can use non-volatile flash to store some System Status/Settings data
dump_all_system_data_to_console();
NRF_LOG_INFO("Internal flash initialized\r\n");
return err_code;
}
uint32_t get_ouid(uint32_t *ouid) {
int ret = NRF_SUCCESS;
#if STORE_IN_NVS
*ouid = ouid_;
#else
*ouid = ouid_;
ret = 4;
#endif
return ret;
}
uint32_t set_ouid(uint32_t *new_ouid) {
uint32_t err_code = NRF_SUCCESS;
#if STORE_IN_NVS
fds_stat_t stat = {0};
if (!new_ouid) {
NRF_LOG_ERROR("Null pointer\r\n");
return NRF_ERROR_NULL;
}
ouid_ = *new_ouid;
ouid_backup_ = *new_ouid;
feed_wdt();
m_fds_is_updating = true;
err_code = fds_record_update(&ouid_fds_desc_, &ouid_record);
NRF_LOG_INFO("fds_record_update: 0x%X\r\n", err_code);
if (err_code) {
//APP_ERROR_CHECK(err_code);
return err_code;
}
wait_for_fds_update();
m_fds_is_updating = true;
err_code = fds_record_update(&ouid_backup_fds_desc_,
&ouid_backup_record);
NRF_LOG_INFO("fds_record_update: 0x%X\r\n", err_code);
if (err_code) {
APP_ERROR_CHECK(err_code);
return err_code;
}
wait_for_fds_update();
err_code = fds_stat(&stat);
if (err_code) {
APP_ERROR_CHECK(err_code);
return err_code;
}
NRF_LOG_INFO("Found %d valid records, %d dirty records.\r\n", stat.valid_records, stat.dirty_records);
if (stat.dirty_records >= MAX_DIRTY_RECORDS_BEFORE_GARBAGE_COLLECTION) {
run_garbage_collector();
}
#else
ouid_ = ouid;
if(!ouid_is_stored()){
ouid_is_stored_ = true;
}
#endif
return err_code;
}
bool ouid_is_stored(void) {
#if STORE_IN_NVS
return _is_ouid_data_valid;
#else
return ouid_is_stored_;
#endif
}