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 ?

1 Like

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

5 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.

1 Like

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.

2 Likes

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();
}

2 Likes

On the topic of saving settings to flash - what would the best way to execute the save function in the background? I’m finding that if I try to save to flash inside the AudioCallback the audio gets corrupted (kinda freezes), and seemingly once the saving has finished I get other strange behaviour such as my multiplexed ADC inputs stop working.

Is there a way to execute the save function concurrently in main() possibly? I guess I’ll need to control access to my settings struct, so it isn’t updated by another section of the program simultaneously? Any tips would be much appreciated, I feel I’m getting in to territory well beyond my knowledge of C++ here, but I’d like to learn. Thanks!

You shouldn’t write from the audio CB, because flash access can take more time than you have to process audio. Naturally there would be audio dropout.

Also, when you’re writing data to flash you can’t read from it until it’s in memory mapped mode again. You will have to store a copy in RAM if you want to be writing data to flash and using in your patch.

Another thing to consider is that audio callback could be interrupting the process in main a few times before it will finish writing. So you may have to add a few extra steps to prevent resetting write state before previous write is done.

Ok that all makes sense! Yes I thought as much regarding writing from the AudioCallback.

I think your second point shouldn’t be a problem in my case, I have a separate variable for the settings data which is used by my patch, it’s not reading directly from the flash memory.

What would those extra steps look like to prevent resetting the write state? Do you just mean in the logic of my code to avoid calling the flash saving function again until it’s finished? Or would something else inadvertently reset the write state?

Thanks!

I mean don’t just assume that it would be finished in the next audio callback run, it may be called while the write is still in progress. But writing multiple times probably won’t be a problem, I would expect other functions in libDaisy (button/event events) to be processed synchronously from the main loop.

Ok I see. Well currently I have an atomic bool ‘save_flag’ that can be switched true by a function from the Audiocallback (when I want to save), but can only be switched false after DSY_MEMORY_OK is returned from the save function. Here’s what I’ve got in main():

 for(;;)
 {
    if(save_flag)
    {
        if (SaveSettings(AltControls) == DSY_MEMORY_OK)
        {
            save_flag = false;
        }
    }
 }

So this seems to work correctly, and I’m reliably saving the settings without glitching the audio. However I’m still finding after every save all my muxed ADC’s stop working. Can you think of any reason why this might be? Have I used some common pins somehow with the QSPI? I didn’t think I needed to reserve any hardware pins for this?

I don’t see how that can happen, but @shensley would definitely enjoy troubleshooting this if you show him a way to reproduce the issue :wink:

Thanks for the shout @antivison :wink:

That is certainly fishy behavior, and I’d be happy to resolve it if it’s replicable on my end.

@adam_f are you running this on the Daisy Field, or some custom hardware with a mux? Also, having a look at your SaveSettings function may shed some light as well.

Hi Stephen, thanks for helping! Yes it’s a custom hardware of my own design, but the circuit mirrors the field for the ADC mux, albeit on different pins. I’ll try and put together a small program that replicates the problem and post it here later (just at work right now). Thanks!

1 Like

@shensley here’s my test program that recreates my flash / mux problem:

It’s the QSPI save and load setting functions from the module code I’m working on, included in a simple program that basically just reads all the ADC’s (including 8 on a mux) and sets the brightness of led1 based on the first ADC channel (as well as saves it to the last member of the setting struct).

In the AudioCallback I set a flag every 5 seconds to signal a setting save, which happens in main().

Just as a test I also load settings from flash in main() before the infinite loop, and set the brightness of led2 based on that setting.

What I’m finding is the ADC’s all work fine until the first save, at which point the muxed inputs become inactive. However the save seems to work, and my led2 gets the correct brightness once the Seed is restarted.

Apologies if this is caused by something silly I’ve done in my code, but any help debugging this would be much appreciated. I don’t know where to start to be honest!