Here is an example of using the PersistentStorage class to save parameter values to flash using the daisy Pod. Storing the values in non volatile memory allows them to be accessed between power cycles. This allows for storing presets or settings of knobs used to control multiple parameters
I enjoy making posts like this because not only do I think examples like these are helpful to the less experienced in the community (and also few and far between), but it also helps me to better learn these concepts myself.
PersistentStorage is a class in LibDaisy that stores a user defined struct in the seeds external flash memory. It also keeps a local copy stored in the RAM. In this example, two parameters of a reverb effect are stored, DryWet mix level and the feedback. These parameters are set using the pods two potentiometers. Button 2 is used to save the current settings. Button 1 is used to switch between the saved preset or knob control of the parameters. LED1 is set red when the reverb parameters are read from the knobs and green when the saved settings are being used. Here is the code:
#include "daisy_pod.h"
#include "daisysp.h"
using namespace daisy;
using namespace daisysp;
DaisyPod 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 DryWet;
Parameter Feedback;
bool use_preset = false;
bool trigger_save = false;
void Load() {
//Reference to local copy of settings stored in flash
Settings &LocalSettings = SavedSettings.GetSettings();
DryWet = LocalSettings.p1;
Verb.SetFeedback(LocalSettings.p2);
use_preset = true;
}
void Save() {
//Reference to local copy of settings stored in flash
Settings &LocalSettings = SavedSettings.GetSettings();
LocalSettings.p1 = hw.knob1.Process();
LocalSettings.p2 = Feedback.Value();
trigger_save = true;
}
void ProcessControls() {
hw.ProcessAllControls();
//Switches
if(hw.button1.RisingEdge()){
if(use_preset)
use_preset = false;
else
Load();
}
if(hw.button2.RisingEdge()){
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)
{
float inl, inr, outl, outr;
ProcessControls();
for (size_t i = 0; i < size; i++)
{
inl = in[0][i];
inr = in[1][i];
Verb.Process(inl, inr, &outl, &outr);
out[0][i] = DryWet * outl + (1 - DryWet) * inl;
out[1][i] = DryWet * outr + (1 - DryWet) * inr;
}
}
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);
//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(trigger_save) {
SavedSettings.Save(); // Writing locally stored settings to the external flash
trigger_save = false;
}
System::Delay(100);
}
}
First, the settings struct type is defined. This type is passed to the class template PesistentStorage. The settings struct should contain all the values we want to save in flash. It is necessary to overload the != operator for the struct as this is used in the PersistentStorage source code. Its used to check that the locally stored settings struct is not equal to the struct stored in flash before the latter is overwritten. This can be defined as you see fit in order to determine which parameter changes are necessary before a saved preset can be overwritten.
//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);
}
};
The PersistentStorage object is then defined using the settings struct type and also the devices QSPI handle. QSPI is the mechanism the processor uses to communicate with the external flash memory. In the main function, our PersistentStorage object, SavedSettings, is initialized. The initialization method is passed the default values of the parameters. These will be the first values stored in flash when the device is first booted after programming. They can also always be restored later using the RestoreDefaults() method.
The locally stored settings can then be accessed using the GetSettings() method which returns a reference to the PersistentClasses internal settings struct. These can then be read and written to as seen in the Load() and Save() functions.
Finally, any edits made to the locally stored settings can be saved to flash using the Save)() method. This method can not be called from the audio callback as it takes quite a while and uses Delay
which cant be used in the audio thread. Therefore, a bool is used as a trigger to initiate a save in the while loop of the main function as shown
while(1) {
if(trigger_save) {
SavedSettings.Save(); // Writing locally stored settings to the external flash
trigger_save = false;
}
System::Delay(100);
}
I found it was necessary to add the Delay after the if block. Iām not fully sure why this was the case. I can only assume that evaluating the condition too often causes problems. I havenāt experimented with the bounds of this condition but I found the 100ms delay to work fine.
I hope some one finds this helpful! Let me know if I got anything particularly egregious wrong