Persistent memory results in bus fault? Bug?

Hi, I’m trying to use persistent memory for some firmware I’m writing for the Versio board using the Daisy Seed. It wasn’t working, so I re-cloned the DaisyExamples repository and created a boilerplate using the exact same method described here:

Saving values to Flash memory using PersistentStorage class on Daisy Pod

Here’s my code:

#include "daisy_versio.h"
#include "daisysp.h"

using namespace daisy;
using namespace daisysp;

DaisyVersio hw;

//Setting Struct containing parameters we want to save to flash
struct Settings {
	float p1; // DryWet
	float p2; // Feedback

	//Overloading the != operator
	//This is necessary as this operator is used in the PersistentStorage source code
	bool operator!=(const Settings& a) const {
        return !(a.p1==p1 && a.p2==p2);
    }
};

//Persistent Storage Declaration. Using type Settings and passed the devices qspi handle
PersistentStorage<Settings> SavedSettings(hw.seed.qspi);

//Reverb and Parameters
//ReverbSc Verb;
float test1 = 0.f;
float test2 = 0.f;

bool use_preset = false;
bool trigger_save = false;


void Load() {

	//Reference to local copy of settings stored in flash
	Settings &LocalSettings = SavedSettings.GetSettings();
	
	test1 = LocalSettings.p1;
	test2 = LocalSettings.p2;

	use_preset = true;
}

void Save() {

	//Reference to local copy of settings stored in flash
	Settings &LocalSettings = SavedSettings.GetSettings();

	LocalSettings.p1 = test1;
	LocalSettings.p2 = test2;

	trigger_save = true;
}

void ProcessControls() {

	hw.ProcessAllControls();

	//Switches
	if(hw.tap.RisingEdge()){
		if(use_preset)
			use_preset = false;
		else 
			Load();
	}

	if(hw.tap.FallingEdge()){
		Save();
	}

	// //Knobs
	// if(!use_preset){
	// 	Verb.SetFeedback(Feedback.Process());
	// 	DryWet = hw.knob1.Process();
	// }

	// //LEDs
	// if(use_preset)
	// 	hw.led1.Set(0, 1, 0); // green
	// else 
	// 	hw.led1.Set(1, 0, 0); // red

	// if(trigger_save)
	// 	hw.led2.Set(0, 1, 0); // green
	// else 
	// 	hw.led2.Set(0, 0, 0); // red

	// hw.UpdateLeds();

}

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
	hw.ProcessAllControls();
	for (size_t i = 0; i < size; i++)
	{
		out[0][i] = in[0][i];
		out[1][i] = in[1][i];
	}
}

int main(void)
{
	hw.Init();
	hw.SetAudioBlockSize(4); // number of samples handled per callback
	hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);

		// Verb.Init(hw.AudioSampleRate());
	// Feedback.Init(hw.knob2, 0.0f, 1.0f, daisy::Parameter::LINEAR);
	float test = 1.f;
    test *= 2;
	//Initilize the PersistentStorage Object with default values.
	//Defaults will be the first values stored in flash when the device is first turned on. They can also be restored at a later date using the RestoreDefaults method
	Settings DefaultSettings = {test, 0.0f};
	SavedSettings.Init(DefaultSettings);

	hw.StartAdc();
	hw.StartAudio(AudioCallback);
	
	while(1) {
		if(trigger_save) {
			
			SavedSettings.Save(); // Writing locally stored settings to the external flash
			trigger_save = false;
		}
		System::Delay(100);
	}
}

When the code is executed, the line

SavedSettings.Init(DefaultSettings);

creates a bus fault here:

            if(SCB->CFSR & SCB_CFSR_BFARVALID_Msk)
                __asm("BKPT #0");

in system.cpp.

Then it creates a second bus fault here:

            if(SCB->CFSR & SCB_CFSR_PRECISERR_Msk)
                __asm("BKPT #0");

Then it goes down here:

        else if(SCB->HFSR & SCB_HFSR_VECTTBL_Msk)
        {
            // Vector table bus fault

            __asm("BKPT #0");
        }

Then here:

        __asm("BKPT #0");

Then loops forever.

The debug console says the following:


Program
-> ~" received signal SIGTRAP, Trace/breakpoint trap.\n"
 received signal SIGTRAP, Trace/breakpoint trap.
-> ~"HardFault_Handler () at src/sys/system.cpp:197\n"
HardFault_Handler () at src/sys/system.cpp:197
-> ~"197\t        __asm(\"BKPT #0\");\n"
197	        __asm("BKPT #0");
-> *stopped,reason="signal-received",signal-name="SIGTRAP",signal-meaning="Trace/breakpoint trap",frame={addr="0x08002882",func="HardFault_Handler",args=[],file="src/sys/system.cpp",fullname="C:\\Users\\digit\\OneDrive\\Desktop\\DaisyExamples2~\\libDaisy\\src\\sys\\system.cpp",line="197",arch="armv7e-m"},thread-id="1",stopped-threads="all"

Am I do something wrong in my code? Perhaps the implementation is supposed to be slightly different. I really am just a beginner when it comes to programming for microcontrollers. Any help would be much appreciated!

Thanks

It is better to call Save and Load outside of the audio callback. You can set a flag when the button is pressed and then call Save in the main loop with some follow-up delay.

I tried your suggestion:

#include "daisy_versio.h"
#include "daisysp.h"

using namespace daisy;
using namespace daisysp;

DaisyVersio hw;

//Setting Struct containing parameters we want to save to flash
struct Settings {
	float p1; // DryWet
	float p2; // Feedback

	//Overloading the != operator
	//This is necessary as this operator is used in the PersistentStorage source code
	bool operator!=(const Settings& a) const {
        return !(a.p1==p1 && a.p2==p2);
    }
};

//Persistent Storage Declaration. Using type Settings and passed the devices qspi handle
PersistentStorage<Settings> SavedSettings(hw.seed.qspi);

//Reverb and Parameters
//ReverbSc Verb;
float test1 = 0.f;
float test2 = 0.f;

bool use_preset = false;
bool trigger_save = false;

bool save_flag = false;
bool load_flag = false;


void Load() {

	//Reference to local copy of settings stored in flash
	Settings &LocalSettings = SavedSettings.GetSettings();
	
	test1 = LocalSettings.p1;
	test2 = LocalSettings.p2;

	use_preset = true;
}

void Save() {

	//Reference to local copy of settings stored in flash
	Settings &LocalSettings = SavedSettings.GetSettings();

	LocalSettings.p1 = test1;
	LocalSettings.p2 = test2;

	trigger_save = true;
}

void ProcessControls() {

	hw.ProcessAllControls();

	hw.tap.Debounce();

	//Switches
	if(hw.tap.RisingEdge()){
		if(use_preset) {
			use_preset = false;
		} else {
			load_flag = true;
		}
	}

	if(hw.tap.FallingEdge()){
		save_flag = true;
	}

	// //Knobs
	// if(!use_preset){
	// 	Verb.SetFeedback(Feedback.Process());
	// 	DryWet = hw.knob1.Process();
	// }

	// //LEDs
	// if(use_preset)
	// 	hw.led1.Set(0, 1, 0); // green
	// else 
	// 	hw.led1.Set(1, 0, 0); // red

	// if(trigger_save)
	// 	hw.led2.Set(0, 1, 0); // green
	// else 
	// 	hw.led2.Set(0, 0, 0); // red

	// hw.UpdateLeds();

}

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
	hw.ProcessAllControls();
	for (size_t i = 0; i < size; i++)
	{
		out[0][i] = in[0][i];
		out[1][i] = in[1][i];
	}
}

int main(void)
{
	hw.Init();
	hw.SetAudioBlockSize(4); // number of samples handled per callback
	hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);

		// Verb.Init(hw.AudioSampleRate());
	// Feedback.Init(hw.knob2, 0.0f, 1.0f, daisy::Parameter::LINEAR);
	float test = 1.f;
    test *= 2;
	//Initilize the PersistentStorage Object with default values.
	//Defaults will be the first values stored in flash when the device is first turned on. They can also be restored at a later date using the RestoreDefaults method
	Settings DefaultSettings = {0.0f, 0.0f};
	SavedSettings.Init(DefaultSettings);

	hw.StartAdc();
	hw.StartAudio(AudioCallback);
	
	while(1) {
		if(save_flag) {
			Save();
			save_flag = false;
		}
		if(trigger_save) {
			
			SavedSettings.Save(); // Writing locally stored settings to the external flash
			trigger_save = false;
		}
		if(load_flag) {
			Load();
			load_flag = false;
		}
		System::Delay(100);
	}
}

It didn’t work. The problem is with the initialization of the qspi object. I do not know exactly what causes it though or why. I suspect it has something to do with the hardware of the versio? I see other people getting it working:

Very important though! I got it working yesterday for a little bit, persistence was working, but the audio became very distorted and it never worked again since. The power cable had become loose. There were no shorts though… Is it possible I fried the qspi? I don’t see why that would happen but maybe?

Here’s the assembly. The last line to get executed is 0x080006a8.

This is what was run after the assembly line just beforehand to get the value to be stored in register 0:

Register 0 is 603980728 after 6a2, then -1879048192 after 2460.

The value in register 0 at 0x080006a8 is -1879048192. Register 1 always stays 0, so this is probably the address offset? Register 3 does not get updated stepping into 0x080006a8 and I am immediately brought to the hard fault handler.

Invalid state or needs to be cleared?

In storage_data the following is defined:

daisy::PersistentStorage<Settings>::State::UNKNOWN
user_data:
          p1: 0
          p1: 0

Why is the state unknown?

Looking in qspi_:

daisy::QSPIHandle::E_HAL_ERROR

Looking in qspi_.halqspi_:

HAL_QSPI_STATE_ERROR

These flags are present immediately after 68e, line 114 in the code.

I stepped through all the lines of code in the debugger and found that it gets stuck here:

  /* Wait until flag is in expected state */
  while((__HAL_QSPI_GET_FLAG(hqspi, Flag)) != State)
  {
    /* Check for the Timeout */
    if (Timeout != HAL_MAX_DELAY)
    {
      if(((HAL_GetTick() - Tickstart) > Timeout) || (Timeout == 0U))
      {
        hqspi->State     = HAL_QSPI_STATE_ERROR;
        hqspi->ErrorCode |= HAL_QSPI_ERROR_TIMEOUT;

        return HAL_ERROR;
      }
    }
  }
  return HAL_OK;
}

I wasn’t patient enough to see if it ever exits here, but it hands for like ten seconds when I hit step into and then the bus fault occurs.

At this point I’m pretty confused… I cloned the repository for Repetita Versio, the patch designed by the poster linked in the previous reply, and built it in vs code, then I debugged it and got the same hard fault. This makes zero sense to me why it would do that even with code that is proven working.

Then I flashed the official .bin for the firmware and nothing works, just white leds, meaning it never got past the boot sequence and hard faulted…

Did I fry the qspi?

I’m not understanding the use of the pushbutton.

Maybe the flash got corrupted after one of the saves. You can try resetting the QSPI flash on load (the reset case below):

void Storage::Init(PersistentStorage<Settings> &settings, Knob *knobs, bool reset)
{
  PendingSave = false;
  m_settings = &settings;
  m_knobs = knobs;

  // Reference to local copy of settings stored in flash
  Settings defaultSettings;
  AssignSettings(defaultSettings, knobs);

  // Initilize the PersistentStorage Object with default values.
  // Defaults will be the first values stored in flash when the device is first turned on.
  // They can also be restored at a later date using the RestoreDefaults method
  settings.Init(defaultSettings);

  if (reset) {
    settings.RestoreDefaults();
  } else {
    LoadFromPersistentStorage();
  }
}

In the original code:

Saving values to Flash memory using PersistentStorage class on Daisy Pod

There were two push buttons, on the Daisy Pod. One was for loading from the QSPI and the other was for saving to the local Settings struct. I’m using only one push button so I do those functions on the falling and rising edges. I think the delay in the main loop may stop this though, hence why the original code did not use flags. The main loop writes the actual data in the local settings struct into the QSPI. I think this is how it’s working. I could be wrong. Once I get the actual QSPI initialized successfully I can troubleshoot. I won’t be using the push button in my actual firmware. I will instead use flags to save and write. I will only load on initialization.

I’m going to try this as soon as I get home tonight, will report back. I really hope that this is the case… Seeing as the actual Init() function isn’t working, hopefully assigning the settings and then initializing allows it to actually reach the reset conditional.

When you press the button, you’ll get a falling edge, when you release you’ll get rising. Or vice-versa, but, my point is, any press/release will trigger both actions.

Is that really what you want?

If it was me, and only one button available, I’d use button only for save, and have restore happen on powerup.
Or no button at all, and just save after settings are unchanged for some time.

For testing purposes, this will be fine. I’ll probably do what you’ve just suggested though and do just restore on powerup in this testing code. I think that saving and loading actually takes a few milliseconds or longer, so if I pressed the button too fast by accident then it may corrupt the memory again because it tries to write while reading. Granted I get it working again.

I was reading here:

Storing of QSPI buffer causing data corruption and hard faults

that it’s possible that the MCU is trying to prefetch the code and do work prior to it actually being executed which might result in writing to the QSPI while saving and vice versa. Also the poster erases an aligned section of QSPI prior to writing to avoid bus faults. I may try that too if all else fails.

Also, to KnightHill, the other person that was having problems with Versio said that using separate scopes and using pointers worked for him, just as what I see in your code. I’ve taken the time to structure my code in object oriented land like you have and now all that’s left is to test it when I;m home. Fingers crossed!

I changed the code to be like this as per suggestions in this thread:

#include "daisy_versio.h"
#include "daisysp.h"

using namespace daisy;
using namespace daisysp;

DaisyVersio hw;

float test1 = 0.f;
float test2 = 0.f;

bool save_flag = false;
bool load_flag = false;

//Setting Struct containing parameters we want to save to flash
struct Settings {
    float p1; // DryWet
	float p2; // Feedback

	//Overloading the != operator
	//This is necessary as this operator is used in the PersistentStorage source code
	bool operator!=(const Settings& a) const {
        return !(a.p1==p1 && a.p2==p2);
    }
};

struct Storage{
	bool PendingSave = true;
	PersistentStorage<Settings> *m_settings;
	float *m_vals; 

	void AssignSettings(Settings &settings, float *vals);
	void SaveToPersistentStorage();
	void GetDataInLocalStorage(float *vals);
	void PopulateLocalStorage(float *vals);
	void LoadFromPersistentStorage();
	void Init(PersistentStorage<Settings> &settings, float *vals, bool reset);
};

void Storage::AssignSettings(Settings &settings, float *vals) {
	settings.p1 = vals[0];
	settings.p2 = vals[1];
}

void Storage::SaveToPersistentStorage() {
	Settings &LocalSettings = m_settings->GetSettings();
	
	LocalSettings.p1 = m_vals[0];
	LocalSettings.p2 = m_vals[1];
}

void Storage::GetDataInLocalStorage(float *vals) {
	vals[0] = m_vals[0];
	vals[1] = m_vals[1];
}

void Storage::PopulateLocalStorage(float *vals) {
	m_vals[0] = vals[0];
	m_vals[1] = vals[1];
}

void Storage::LoadFromPersistentStorage() {
		//Reference to local copy of settings stored in flash
	Settings &LocalSettings = m_settings->GetSettings();
	
	m_vals[0] = LocalSettings.p1;
	m_vals[1] = LocalSettings.p2;
}

void Storage::Init(PersistentStorage<Settings> &settings, float *vals, bool reset)
{
	PendingSave = false;
	m_settings = &settings;
	m_vals = vals;

	// Reference to local copy of settings stored in flash
	Settings defaultSettings;
	AssignSettings(defaultSettings, vals);

	// Initilize the PersistentStorage Object with default values.
	// Defaults will be the first values stored in flash when the device is first turned on.
	// They can also be restored at a later date using the RestoreDefaults method
	settings.Init(defaultSettings);

	if (reset) {
		settings.RestoreDefaults();
	} else {
		LoadFromPersistentStorage();
	}
}

//Persistent Storage Declaration. Using type Settings and passed the devices qspi handle
PersistentStorage<Settings> SavedSettings(hw.seed.qspi);
Storage storage;

float test[2] = {4.5, 5.4};

void ProcessControls() {

	hw.ProcessAllControls();

	hw.tap.Debounce();

	//Switches
	if(hw.tap.RisingEdge()){
		save_flag = true;
	}
}

void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
    hw.ProcessAllControls();
	for (size_t i = 0; i < size; i++)
	{
		out[0][i] = in[0][i];
		out[1][i] = in[1][i];
	}
}

float settingsValues[2] = {1.2f, 0.6f};

int main(void)
{
	hw.Init();
	hw.SetAudioBlockSize(4); // number of samples handled per callback
	hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
	
	storage.Init(SavedSettings, settingsValues, true);

	hw.leds[0].Set(0, 0, 0);
    hw.leds[0].Set(0, 0, 0);
    hw.leds[0].Set(0, 0, 0);
    hw.leds[0].Set(0, 0, 0);
    hw.UpdateLeds();

	hw.StartAdc();
	hw.StartAudio(AudioCallback);

while(1) {
	if(save_flag) {
		storage.PopulateLocalStorage(test);
		storage.SaveToPersistentStorage();
		save_flag = false;
	}

    System::Delay(100);
}

}

Still didn’t work. :slightly_frowning_face:

Although I just noticed something. Right after hw.Init() the qspi status at hw.seed.qspi is E_HAL_ERROR. The QSPI is errored immediately during the hardware initialization. Not sure when that happens though.

It’s apparently still failing at

State cur_state = storage_data->storage_state;

in PersistentStorage.h. The storage state is daisy::PersistentStorage::State::UNKNOWN

I used the official libDaisy QSPI example and it fails here:

    /* Enable write operations */
    if(WriteEnable() != QSPIHandle::Result::OK)
    {
        ERR_SIMPLE(Status::E_HAL_ERROR);
    }

Then it fails here:

QSPIHandle::Result QSPIHandle::Impl::SetMode(QSPIHandle::Config::Mode mode)
{
    if(config_.mode != mode)
    {
        config_.mode = mode;
        if(Init(config_) != Result::OK)
        {
            config_.mode = Config::Mode::MEMORY_MAPPED;
            status_      = Status::E_SWITCHING_MODES;
            Init(config_);
            return Result::ERR;
        }
    }
    return Result::OK;
}

Then hw.qspi.pimpl_halqspi_.State resets it’s status and goes here:

    if(HAL_QSPI_DeInit(&halqspi_) != HAL_OK)
    {
        ERR_SIMPLE(Status::E_HAL_ERROR);
    }

Then it goes here, line 864 in hal_qspi.c:

  __HAL_UNLOCK(hqspi);

At this point both hqspi.State and hw.qspi.pimpl.halqspi.State are HAL_QSPI_STATE_READY

Then they’re busy, line 829:

    status = QSPI_WaitFlagStateUntilTimeout(hqspi, QSPI_FLAG_BUSY, RESET, tickstart, Timeout);

It does this a couple times.

Then it becomes busy TX and then auto polls, line 1526:

    status = QSPI_WaitFlagStateUntilTimeout(hqspi, QSPI_FLAG_BUSY, RESET, tickstart, Timeout);

It does this a couple times, then goes to line 1549:

      status = QSPI_WaitFlagStateUntilTimeout(hqspi, QSPI_FLAG_SM, SET, tickstart, Timeout);

It hangs for a very long time and then the states are ERROR again.

The last things in the call stack from qspi.cpp:
WritePage
SetMode
SetMode
Init
QuadEnable

Then it fails.

It goes back here and starts over again:

    if(WriteEnable() != QSPIHandle::Result::OK)
    {
        ERR_SIMPLE(Status::E_HAL_ERROR);
    }