Persisting data to (from) Flash

Hi
Has anyone read/write data from/to the flash from their C/C++ app ?
Thinking of persisting pot / switch parameters to flash , above the apps actual executing code, top of flash.
Power off then power on and read the data back from flash into my app

I’ve noticed the dsy_qspi api, anyone used it ?

There is an example of this and some code in the wild

3 Likes

Thank you. I’ll have a look

If your’re using Arduino, you can check out the BufferedEEPROM example on stm32duino:
https://github.com/stm32duino/STM32Examples/blob/master/examples/NonReg/BufferedEEPROM/BufferedEEPROM.ino
I’ve tried it with daisy seed and it works well.

I’m not using Arduino, but I’ll take a look
I do have an Uno, but not using it for this project…
Just had a look at the Arduino code, gosh, so simple

Hi, sorry to hijack this thread but I’m trying to implement the dsy_qspi in my own daisy seed program, but having some difficulty reading / writing anything sensible from the flash. I’ve got this so far, which is meant to save and load 4 float values in a struct. Any obvious mistakes or stuff I’ve left out? Thanks in advance!

#include "QSPI_Settings.h"
using namespace daisy;

#define BUFF_SIZE 4

static uint32_t DSY_QSPI_BSS buff[BUFF_SIZE];
uint32_t inbuff[BUFF_SIZE];

//save settings to flash memory
uint32_t SaveSettings(const Settings &setting)
{
    hw.qspi_handle.mode = DSY_QSPI_MODE_INDIRECT_POLLING;
	dsy_qspi_init(&hw.qspi_handle);

    //inbuff[0] = (uint32_t) 1;   //flag to say there's data loaded??
    inbuff[0] = static_cast<uint32_t> (setting.RevLength);
    inbuff[1] = static_cast<uint32_t> (setting.tapRatio);
    inbuff[2] = static_cast<uint32_t> (setting.ModDepth);
    inbuff[3] = static_cast<uint32_t> (setting.ModFreq);

	uint32_t base = 0x90000000;
	uint32_t writesize = BUFF_SIZE * sizeof(buff[0]);
	dsy_qspi_erase(base, base + writesize);
	
    uint32_t res{};
    res = dsy_qspi_write(base, writesize, (uint8_t*)inbuff);
	dsy_qspi_deinit();

    return res;
}

//load memory from flash memory
Settings LoadSettings()
{
    Settings SettingsInMem{};

    hw.qspi_handle.mode = DSY_QSPI_MODE_DSY_MEMORY_MAPPED;
    dsy_qspi_init(&hw.qspi_handle);

    SettingsInMem.RevLength = static_cast<float> (buff[0]);
    SettingsInMem.tapRatio = static_cast<float> (buff[1]);
    SettingsInMem.ModDepth = static_cast<float> (buff[2]);
    SettingsInMem.ModFreq = static_cast<float> (buff[3]);

    dsy_qspi_deinit();

    return SettingsInMem;
}

@adam_f, looks like you’re allocating SettingsInMem in stack, so it likely gets overwritten on function return. You should probably be using a global object in this case.

Also, your code is hard to follow and seems to complicate things. You could probably just do something like this to store data (not tested, but should give you the general idea):

dsy_qspy_write((uint32_t)buff, sizeof(setting), (uint8_t*)setting);

and read it like this

memcpy((void*)&SettingsInMem, (void*)buff, sizeof(SettingsInMem));

And you can drop useless (in this case) static casts, hardcoded address and other potential sources of errors. Besides that, it would be much safer to use a magic value as settings object header, which would allow you to confirm that what you’re reading is the actual object and not some garbage data (stored by previous patch). It would also let introduce settings versioning later if necessary.

Also, I would say that there’s probably no benefit in calling dsy_qspi_deinit();, IIRC it’s called automatically if necessary when switching modes.

Hello,
I also have problem using qspi.
I’d like to save/ load a structure (with all my synth parameters).

struct CONFIGURATION
{ … }

with curent_config an instance of this structure:

CONFIGURATION curent_config;

so far, I’ve made 2 functions to save / load this structure. This is working :

static CONFIGURATION DSY_QSPI_BSS saved_config;

void save_config() {
uint32_t base = 0x90000000;
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_INDIRECT_POLLING;
dsy_qspi_init(&hw.seed.qspi_handle);
dsy_qspi_erase(base, base+sizeof(CONFIGURATION) );
dsy_qspi_write(base, sizeof(CONFIGURATION), (uint8_t*)&curent_config);
dsy_qspi_deinit();
}

void load_config() {
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_DSY_MEMORY_MAPPED;
dsy_qspi_init(&hw.seed.qspi_handle);
memcpy(&curent_config, &saved_config, sizeof(CONFIGURATION));
dsy_qspi_deinit();
}

I tried without dsy_qspi_deinit(); as sugested by adam_f, but it was working only few times (after 2 or 3 write / read sequence, the datas where not read or write anymore).

Now, I’d like to have multiples slot to load / save. I change my functions to this :

void save_config(uint32_t address) {
uint32_t base = 0x90000000;
base += address * sizeof(CONFIGURATION);
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_INDIRECT_POLLING;
dsy_qspi_init(&hw.seed.qspi_handle);
dsy_qspi_erase(base, base+sizeof(CONFIGURATION) );
dsy_qspi_write(base, sizeof(CONFIGURATION), (uint8_t*)&curent_config);
dsy_qspi_deinit();
}

void load_config(uint32_t address) {
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_DSY_MEMORY_MAPPED;
dsy_qspi_init(&hw.seed.qspi_handle);
memcpy(&curent_config, &saved_config + (address * sizeof(CONFIGURATION)), sizeof(CONFIGURATION));
dsy_qspi_deinit();
}
but it’s not working : as soon as I write a configuration to an other slot than 0, the data read are not valid.
What I am doing wrong? how should I compute the correct address of the data to read and to write?

You can only erase data in 4kb, 32kb or 64kb blocks. LibDaisy only exposes access to 4kb erasure in its code. So your alternatives are:

  1. store all configurations in one or more 4kb sector, read them all in memory and write them all after erasing all sectors used

  2. align each configuration to 4kb address and erase them one by one - this is what your code intends to do, but it probably doesn’t enforce alignment

  3. make a primitive filesystem that doesn’t require erasing data, just marks old configurations as deleted. actual erasure would be performed when you’re out of free storage space and defragrmentation process runs. this might require a more detailed explanation, so I’ll just mention that this is how the OWL firmware manages its flash storage.

1 Like

Thanks for your answer,
In order to force 4K alignment (My structure is less than 4K), I change my function to :

void save_config(uint32_t address) {
uint32_t base = 0x90000000;
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_INDIRECT_POLLING;
dsy_qspi_init(&hw.seed.qspi_handle);
base += address * 4096;
dsy_qspi_erase(base, base + sizeof(CONFIGURATION));
dsy_qspi_write(base, sizeof(CONFIGURATION), (uint8_t*)&curent_config);
dsy_qspi_deinit();
}
void load_config(uint32_t address)
{
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_DSY_MEMORY_MAPPED;
dsy_qspi_init(&hw.seed.qspi_handle);
memcpy(&curent_config, &saved_config + (address * 4096), sizeof(CONFIGURATION));
dsy_qspi_deinit();
}

When saving in slot 1, it no longer erragse slot 0, but loading data from slot 1 is still not working.

I change the previous code to this, and it look like it’s working!

void save_config(uint32_t slot) {
uint32_t base = 0x90000000;
base += slot4096;
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_INDIRECT_POLLING;
dsy_qspi_init(&hw.seed.qspi_handle);
dsy_qspi_erase(base, base + sizeof(CONFIGURATION));
dsy_qspi_write(base, sizeof(CONFIGURATION), (uint8_t
)&curent_config);
dsy_qspi_deinit();
}

void load_config(uint32_t slot) {
hw.seed.qspi_handle.mode = DSY_QSPI_MODE_DSY_MEMORY_MAPPED;
dsy_qspi_init(&hw.seed.qspi_handle);
memcpy(&curent_config, reinterpret_cast<void*>(0x90000000 + (slot * 4096)), sizeof(CONFIGURATION));
dsy_qspi_deinit();
}