Is there nonvolatile memory (EEPROM) on the Nano2?


#1

I was wondering if there is any way to read and store (sensor calibration from MPU) data on the BLE Nano2. Would I have to use an external EEPROM component for this, as there doesn’t seem to be any EEPROM on the chip itself?


#2

Hi eddie,
there is no eeprom on the chip and you can use an external one to store.


#3

But you can use the integrated Flash storage on the chip, which is equivalent and even superior than EEPROM. EEPROM is very slow compared to Flash storage.


#4

Thanks for the replies!

Lucian, that’s what I was afraid of!

Would there be a way to connect up external EEPROM and the MPU at the same time though, as both need to communicate over i2c, and the nano2 board only has one pair of SDA/SCL pins?

Hi Gjsea, that sounds really intriguing - do you know of any Arduino examples or documentation for doing this with the nano2?

Thanks,
Eddie


#5

FDS and the way it has been implemented is fairly unconventional compared to the typical PC file metaphor. It exists deep in the BLE soft device, so ble.init() needs to be called before everything else, then my NANO2FDS_Initialize().

FDS is record based so instead of filenames records are accessed by 16 bit integer IDs, this is perfect though if you’re storing data along the lines of database rows. There’s a whole bunch of callback goop, and some functions require waiting for before continuing with other code. I attached some working code that I created for the Nano2 using Arduino, hopefully it helps and you can adapt.

#include <Arduino.h>
#include <nRF5xn.h>
#include <btle.h>
#include <ble/blecommon.h>
#include <nrf_soc.h>
#include <nrf_nvic.h>

extern "C"
{
#include <fstorage.h>
#include <fds.h>
#include <fds_config.h>
}



/*
* Local reference to last record id accessed
*/
#define NVM_INVALID_EVENT      0xFF
#define NVM_INVALID_RECORD     0xFFFFFFFF

static uint32_t     m_nvm_mgt_last_record_id = NVM_INVALID_RECORD;
static fds_evt_id_t m_nvm_mgt_last_event = (fds_evt_id_t)NVM_INVALID_EVENT;

void NANO2FDSEventHandler(fds_evt_t const * const p_fds_evt);
void NANO2FDSWaitFor(uint32_t record_id, fds_evt_id_t event);
void NANO2FDSGarbageCollect();

extern BLE                     ble;



bool NANO2FDS_Initialize()
{
	uint32_t ret = fds_register(NANO2FDSEventHandler);
	if (ret == FDS_SUCCESS)
	{
		ret = fds_init();
		NANO2FDSWaitFor(NVM_INVALID_RECORD, FDS_EVT_INIT);
		if (ret != FDS_SUCCESS)
			Serial.println(" fds_init FAILURE " + String(ret, DEC));
	}
	else
		Serial.println(" fds_register FAILURE RET=" + String(ret, DEC) + String(" ") + String(FDS_ERR_USER_LIMIT_REACHED, DEC));

	return ret == FDS_SUCCESS;
}



void NANO2FDSEventHandler(fds_evt_t const * const p_fds_evt)
{
	// Store the current event as last event that happened
	m_nvm_mgt_last_event = p_fds_evt->id;
	switch (p_fds_evt->id)
	{
	case FDS_EVT_INIT:
		if (p_fds_evt->result != FDS_SUCCESS)
			// Initialization failed.
			Serial.println("\nFDS init failed !");
		else
			// Managing an init flag so that nothing is done before FDS is ready...
			Serial.println("\nFDS init SUCCESS FDS_VIRTUAL_PAGE_SIZE = " + String(FDS_VIRTUAL_PAGE_SIZE *4) + String(" bytes"));
		m_nvm_mgt_last_record_id = NVM_INVALID_RECORD;
		break;
	case FDS_EVT_WRITE:
		if (p_fds_evt->result == FDS_SUCCESS)
			Serial.println("\nFDS Write event SUCCESS ID:" + String(p_fds_evt->write.record_id));
		else
			Serial.println("\nFDS Write event **ERROR** " + String(p_fds_evt->result));
		m_nvm_mgt_last_record_id = p_fds_evt->write.record_id;
		break;
	case FDS_EVT_UPDATE:
		if (p_fds_evt->result == FDS_SUCCESS)
			Serial.println("\nFDS Update record event SUCCESS ID: " + String(p_fds_evt->write.record_id));
		else
			Serial.println("\nFDS Update record event **ERROR**: " + String(p_fds_evt->result));
		m_nvm_mgt_last_record_id = p_fds_evt->write.record_id;
		break;
	case FDS_EVT_DEL_RECORD:
		if (p_fds_evt->result == FDS_SUCCESS)
			Serial.println("\nFDS delete record event SUCCESS ID: " + String(p_fds_evt->del.record_id));
		else
			Serial.println("\nFDS delete record event **ERROR**: " + String(p_fds_evt->result));
		m_nvm_mgt_last_record_id = p_fds_evt->del.record_id;
		break;
	case FDS_EVT_DEL_FILE:
		// TODO: Check whther we need to wait at some point for delete events
		// in any modules
		if (p_fds_evt->result == FDS_SUCCESS)
			Serial.println("\nFDS delete file event SUCCESS");
		else
			Serial.println("\nFDS delete file event **ERROR **: " + String(p_fds_evt->result));
		break;
	case FDS_EVT_GC:
		break;
	default:
		break;
	}

}



void NANO2FDSEnumerateFiles()
{
	uint16_t  file_id = 0;
	fds_flash_record_t  flash_record;
	fds_record_desc_t   record_desc;
	uint32_t totalFSBytes = 0;
	uint16_t cbLen;

	Serial.println("FDSEnumerateFiles START SEARCHING...");
	while (file_id < 0xFFFF)
	{
		fds_find_token_t    ftok = { 0 };//Important, make sure you zero init the ftok token
		uint32_t totalFileBytes = 0;

		while (fds_record_find_in_file(file_id, &record_desc, &ftok) == FDS_SUCCESS)
		{
			if (fds_record_open(&record_desc, &flash_record) == FDS_SUCCESS)
			{
				uint32_t cbRecord = (flash_record.p_header->tl.length_words * 4);
				totalFileBytes += cbRecord;
				totalFSBytes += cbRecord;
			}
			fds_record_close(&record_desc);
		}

		if (totalFileBytes > 0)
			Serial.println("File found ID #0x" + String(file_id, HEX) + String(" ") + String(totalFileBytes) + String(" bytes"));
		file_id++;
	}
	Serial.println("FDSEnumerateFiles FINISHED " + String(totalFSBytes) + String(" total bytes"));
}




bool NANO2FDSFileExists(uint16_t file_id, uint16_t record_key)
{
	uint32_t err_code;
	fds_flash_record_t  flash_record;
	fds_record_desc_t   record_desc;
	fds_find_token_t    ftok = { 0 };//Important, make sure you zero init the ftok token

	// Loop until all records with the given key and file ID have been found.
	while (fds_record_find(file_id, record_key, &record_desc, &ftok) == FDS_SUCCESS)
	{
		err_code = fds_record_open(&record_desc, &flash_record);
		if (err_code != FDS_SUCCESS)
			return false;

		// Close the record when done.
		err_code = fds_record_close(&record_desc);
		if (err_code != FDS_SUCCESS)
			return false;

		return true;
	}
	return false;
}



uint32_t NANO2FDSWriteFileRecord(uint16_t file_id, uint16_t record_key, uint8_t *pBuffer, uint16_t cb)
{
	fds_record_desc_t   findRecord_desc;
	fds_find_token_t    findFtok = { 0 };//Important, make sure you zero init the ftok token
	uint16_t cbRecordPayload = cb + sizeof(uint16_t);
	uint16_t cbAligned = cbRecordPayload + ((-cbRecordPayload) & 3);
	uint8_t *pAlignedBuffer = NULL;

	pAlignedBuffer = (uint8_t *)malloc(cbAligned);
	if (pAlignedBuffer == NULL)
		return FDS_ERR_INTERNAL;
	memcpy(pAlignedBuffer, &cb, sizeof(uint16_t));
	memcpy(pAlignedBuffer + sizeof(uint16_t), pBuffer, cb);

	fds_record_t        record;
	fds_record_desc_t   record_desc;
	fds_record_chunk_t  record_chunk;

	// Set up data.
	record_chunk.p_data = pAlignedBuffer;
	record_chunk.length_words = cbAligned / 4;  // When calculating the length in words, using integer division, the result will always be "rounded" down to the whole integer. A data array of 21 bytes, is strictly speaking 5 words + 1 byte. However, the length should be in words, and thus 6 words are needed to store all the data. 21/4 = 5, (21+3)/4 = 6. For 20 bytes, 5 words are needed, in integer division 20/4 = (20+3)/4 =5.

	// Set up record.
	record.file_id = file_id;
	record.key = record_key;
	record.data.p_chunks = &record_chunk;
	record.data.num_chunks = 1;

	// Perform action
	if (fds_record_find(file_id, record_key, &findRecord_desc, &findFtok) == FDS_SUCCESS)
	{
		Serial.println("UPDATING EXISTING");
		uint32_t ret = fds_record_update(&findRecord_desc, &record);
		if (ret != FDS_SUCCESS)
		{
			Serial.println("FDSWriteFileRecord fds_record_update ERROR " + String(ret, DEC));
			return ret;
		}
		//SerialLogLn(LOGLEVEL_INFO, "Updating Record ID = " + String(findRecord_desc.record_id, DEC));
		NANO2FDSWaitFor(findRecord_desc.record_id, FDS_EVT_UPDATE);
	}
	else
	{
		Serial.println("WRITING NEW");
		uint32_t ret = fds_record_write(&record_desc, &record);
		if (ret != FDS_SUCCESS)
		{
			Serial.println("FDSWriteFileRecord fds_record_write ERROR " + String(ret, DEC));
			return ret;
		}
		NANO2FDSWaitFor(record_desc.record_id, FDS_EVT_WRITE);
	}
	if (pAlignedBuffer != NULL)
		free(pAlignedBuffer);
	return NRF_SUCCESS;
}



uint16_t NANO2FDSReadFileRecord(uint16_t file_id, uint16_t record_key, uint8_t *pBuffer, uint16_t cb)
{
	uint16_t bytesRead = 0;
	fds_flash_record_t  flash_record;
	fds_record_desc_t   record_desc;
	fds_find_token_t    ftok = { 0 };//Important, make sure you zero init the ftok token

	uint32_t err_code;

	// Loop until all records with the given key and file ID have been found.
	while (fds_record_find(file_id, record_key, &record_desc, &ftok) == FDS_SUCCESS)
	{
		err_code = fds_record_open(&record_desc, &flash_record);
		if (err_code != FDS_SUCCESS)
		{
			Serial.println("FDSReadFileRecord::fds_record_open FAILED " + String(file_id) + String(", ERROR: ") + String(err_code, DEC));
			return 0;
		}

		if (flash_record.p_header->tl.length_words >= 1)
		{
			uint16_t cbData = *((uint16_t *)flash_record.p_data);

			if (cbData > cb)
				return 0;
			memcpy(pBuffer, ((uint8_t *)flash_record.p_data) + sizeof(uint16_t), cbData);
			bytesRead = cbData;
		}

		// Close the record when done.
		err_code = fds_record_close(&record_desc);
		if (err_code != FDS_SUCCESS)
		{
			return 0;
		}
	}
	if(cb == 0)
		Serial.println("FDSReadFileRecord NO RECORD FOUND");
	return bytesRead;
}



uint16_t NANO2FDSReadFileRecordBuffer(uint16_t file_id, uint16_t record_key, uint8_t **ppBuffer, uint16_t *pcb, uint16_t cbAddl)
{
	uint16_t bytesRead = 0;
	fds_flash_record_t  flash_record;
	fds_record_desc_t   record_desc;
	fds_find_token_t    ftok = { 0 };//Important, make sure you zero init the ftok token
	uint8_t *pBuffer;

	uint32_t err_code;

	// Loop until all records with the given key and file ID have been found.
	while (fds_record_find(file_id, record_key, &record_desc, &ftok) == FDS_SUCCESS)
	{
		err_code = fds_record_open(&record_desc, &flash_record);
		if (err_code != FDS_SUCCESS)
		{
			Serial.println("FDSReadFileRecordBuffer::fds_record_open FAILED " + String(file_id) + String(", ERROR: ") + String(err_code, DEC));
			return 0;
		}

		if (flash_record.p_header->tl.length_words >= 1)
		{
			uint16_t cbData = *((uint16_t *)flash_record.p_data);

			pBuffer = (uint8_t *)malloc(cbData + cbAddl);
			if (pBuffer != NULL)
			{
				memcpy(pBuffer, ((uint8_t *)flash_record.p_data) + sizeof(uint16_t), cbData);
				*ppBuffer = pBuffer;
				*pcb = bytesRead = cbData;
			}
		}

		// Close the record when done.
		err_code = fds_record_close(&record_desc);
		if (err_code != FDS_SUCCESS)
		{
			return 0;
		}
	}
	if (bytesRead == 0)
		Serial.println("FDSReadFileRecordBuffer NO RECORD FOUND");
	return bytesRead;
}




bool NANO2FDSDeleteFile(uint16_t file_id)
{
	ret_code_t ret = fds_file_delete(file_id);
	NANO2FDSWaitFor(NVM_INVALID_RECORD, FDS_EVT_DEL_FILE);
	if (ret == FDS_SUCCESS)
		Serial.println("Deleted File ID: 0x" + String(file_id, HEX));
	else
		Serial.println("ERROR Deleting File ID: " + String(file_id) + String(" ERROR: ") + String(ret));
	NANO2FDSGarbageCollect();
	return ret == FDS_SUCCESS;
}



void NANO2FDSDeleteFileRecord(uint16_t file_id, uint16_t record_key)
{
	fds_record_desc_t   record_desc;
	fds_find_token_t    ftok;
	ftok.page = 0;
	ftok.p_addr = NULL;

	// Loop and find records with same ID and rec key and mark them as deleted. 
	while (fds_record_find(file_id, record_key, &record_desc, &ftok) == FDS_SUCCESS)
	{
		delay(50);
		Serial.println("Deleting record ID: " + String(record_desc.record_id));
		ret_code_t ret = fds_record_delete(&record_desc);		
		NANO2FDSWaitFor(record_desc.record_id, FDS_EVT_DEL_RECORD);
		if (ret == FDS_SUCCESS)
			Serial.println("Deleted record ID: " + String(record_desc.record_id));
		else
			Serial.println("ERROR Deleting record ID: " + String(record_desc.record_id) + String(" ERROR: ") + String(ret));
	}
	NANO2FDSGarbageCollect();
}



void NANO2FDSGarbageCollect()
{
	Serial.println("\nGarbage Collecting...");
	ret_code_t ret = fds_gc();
	NANO2FDSWaitFor(NVM_INVALID_RECORD, FDS_EVT_GC);
	if (ret == FDS_SUCCESS)
		Serial.println("Garbage Collect SUCCESS");
	else
		Serial.println("Garbage Collect FAILURE: " + String(ret));
}



void NANO2FDSWaitFor(uint32_t record_id, fds_evt_id_t event)
{
	Serial.println("Waiting for event (" + String(event, DEC) + String(")"));
	while ((m_nvm_mgt_last_record_id != record_id) || (event != m_nvm_mgt_last_event))
	{
		ble.processEvents();
		delay(50);
	}

	// Reset values for next event to wait
	m_nvm_mgt_last_record_id = NVM_INVALID_RECORD;
	m_nvm_mgt_last_event = (fds_evt_id_t)NVM_INVALID_EVENT;
}

#6

Thank you gjsea for that snippet - I’ve currently switched to a different project but when I’m back onto this I will try and let you know!