sid8580
Well-known member
Hello all, been at this problem for 3 days now and think it's finally time to ask for help -
1. What is the simplest way to save a big struct (several variable types and enums, many variables in total) to SD? How about if it's a 2-dimensional array as well?
2. And the simplest way to then read it back from SD, and repopulate the struct it came from with the data?
I have no trouble at all saving/loading this data using the onboard EEPROM, but my project has grown and now the data for a single save now requires that entire space, need an SD card to function similarly. I know the SD (Teensy enhanced) library has the capabilities but I am lost. I'm actually a little surprised there isn't some tool or library function that just takes whatever you throw at it and saves it, like EEPROM.get and .put!
I can save data to a .txt file per the example ReadWrite easily enough. Should I be trying to store it all in human-readable ASCII .txt format? Or should I be casting it all to bytes, saving it to the file, and then dumping it back into the original array after loading? I figured the latter makes more sense, this project isn't a simple datalogger writing to text files as I've seen explained in most of the examples across the internet, this is device configuration data only meant for the device itself (and occasionally for me to debug).
The complete code is over 15k lines long (and the rest is working fine). I've tried to post this in a typical format, just the pieces that matter. If this isn't enough I will set up a standalone test program with these pieces alone later on and post that.
The first goal was just to save/load the variable "macro_global[0].MacroPolyphony". Thanks.
---
#include "SD.h"
#include <SPI.h>
File SDfile;
const int chipSelect = BUILTIN_SDCARD;
//============================//
// MACRO OBJECTS (MACRO GLOBALS) //
//============================//
// enumerate available polyphony options:
enum output_polyphony
{
POLYPHONY_MONO, // 0 - single note at a time, selected by a mapped sensor range
POLYPHONY_DUO, // 1 - two notes at a time, selected by mapped sensor ranges
POLYPHONY_POLY, // 2 - 3 notes at a time, selected by mapped sensor ranges
POLYPHONY_CHORD, // 3 - one chord at a time, selected by mapped sensor range
POLYPHONY_CONTROL // 4 - Control, CC only
};
// enumerate chromatic/CC quantizations:
enum output_quantization
{
QUANTIZE_NONE, // 0 - all notes/values available to output
QUANTIZE_SCALE, // 1 - scale, to be selectable
QUANTIZE_NOTELIST, // 2 - manually defined notes
QUANTIZE_CHORDLIST, // 3 - manually defined chords
QUANTIZE_NUMERIC // 4 - manually defined raw numeric values
};
// enumerate types for chord mode output:
enum chord_types
{
CHORD_DISABLED, // 0
MAJOR, // 1
MINOR, // 2
AUGMENTED_TRIAD, // 3
SUSPENDED_4TH, // 4
DIMINISHED_TRIAD, // 5
MAJOR_6TH, // 6
MINOR_6TH, // 7
MAJOR_7TH, // 8
MINOR_7TH, // 9
DOMINANT_7TH, // 10
DOMINANT_7TH_SUS4, // 11
DOMINANT_7TH_AUGMENTED_5TH, // 12
DOMINANT_7TH_FLATTENED_5TH, // 13
DIMINISHED_7TH, // 14
MINOR_7TH_FLATTENED_5TH, // 15
MINOR_MAJOR_7TH, // 16
MAJOR_9TH, // 17
MINOR_9TH, // 18
DOMINANT_9TH, // 19
NINTH_AUGMENTED_5TH, // 20
NINTH_FLATTENED_5TH, // 21
NINTH_ADD_6TH, // 22
MAJOR_11TH, // 23
MINOR_11TH, // 24
DOMINANT_11TH, // 25
ELEVENTH_FLATTENED_9TH, // 26
MAJOR_13TH, // 27
MINOR_13TH, // 28
DOMINANT_13TH, // 29
THIRTEENTH_FLATTENED_9TH // 30
};
// create data structure for globals like sensor calibration, per macro -
struct macro_globals
{
// User-definable polyphony options -
output_polyphony MacroPolyphony;
// User-definable output quantizations -
output_quantization MacroQuantizer;
// enumerated chord selection -
chord_types MacroChordType;
// MIDI channel acting as master for macro, needed for composite modeled output
unsigned char MacroMIDIchannel;
// calibation values per sensor, intended for use within the calibration UI
short CAPACITIVE_A_SensorInMax;
short CAPACITIVE_A_SensorInMin;
short CAPACITIVE_B_SensorInMax;
short CAPACITIVE_B_SensorInMin;
short GYRO_YAW_SensorInMax;
short GYRO_YAW_SensorInMin;
short GYRO_PITCH_SensorInMax;
short GYRO_PITCH_SensorInMin;
short GYRO_ROLL_SensorInMax;
short GYRO_ROLL_SensorInMin;
short GYRO_ACCEL_SensorInMax;
short GYRO_ACCEL_SensorInMin;
unsigned char CAPACITIVE_A_MIDIOutMax;
unsigned char CAPACITIVE_A_MIDIOutMin;
unsigned char CAPACITIVE_B_MIDIOutMax;
unsigned char CAPACITIVE_B_MIDIOutMin;
unsigned char GYRO_YAW_MIDIOutMax;
unsigned char GYRO_YAW_MIDIOutMin;
unsigned char GYRO_PITCH_MIDIOutMax;
unsigned char GYRO_PITCH_MIDIOutMin;
unsigned char GYRO_ROLL_MIDIOutMax;
unsigned char GYRO_ROLL_MIDIOutMin;
unsigned char GYRO_ACCEL_MIDIOutMax;
unsigned char GYRO_ACCEL_MIDIOutMin;
bool proxA_inverted;
bool proxB_inverted;
bool yaw_inverted;
bool pitch_inverted;
bool roll_inverted;
bool accel_inverted;
// default values:
macro_globals():
// these define how the macro can be used:
MacroPolyphony(POLYPHONY_MONO),
MacroQuantizer(QUANTIZE_NONE),
MacroChordType(CHORD_DISABLED),
// default mode is MONO, no output quantization
MacroMIDIchannel(9),
// sensor calibration data:
CAPACITIVE_A_SensorInMax(896), // == SensorIn values fall within each sensor's native range ==
CAPACITIVE_A_SensorInMin(127), // Capacitive sensor hardware range: 0-1023 (via MPR121.getFilteredData)
CAPACITIVE_B_SensorInMax(896),
CAPACITIVE_B_SensorInMin(127),
GYRO_YAW_SensorInMax(-179), // == SensorIn values fall within each sensor's native range ==
GYRO_YAW_SensorInMin(179), // Yaw, pitch and roll range from -180 to 180 degrees (360 for a complete rotation)
GYRO_PITCH_SensorInMax(-179),
GYRO_PITCH_SensorInMin(179),
GYRO_ROLL_SensorInMax(-179),
GYRO_ROLL_SensorInMin(179),
GYRO_ACCEL_SensorInMax(0),
GYRO_ACCEL_SensorInMin(0),
CAPACITIVE_A_MIDIOutMax(0), // MIDIOut values fall within 0-127
CAPACITIVE_A_MIDIOutMin(127), // The MIDI values are inverted to avoid having to rewrite the calibration UI
CAPACITIVE_B_MIDIOutMax(0),
CAPACITIVE_B_MIDIOutMin(127),
GYRO_YAW_MIDIOutMax(0),
GYRO_YAW_MIDIOutMin(127),
GYRO_PITCH_MIDIOutMax(0),
GYRO_PITCH_MIDIOutMin(127),
GYRO_ROLL_MIDIOutMax(0),
GYRO_ROLL_MIDIOutMin(127),
GYRO_ACCEL_MIDIOutMax(0),
GYRO_ACCEL_MIDIOutMin(127),
proxA_inverted(false),
proxB_inverted(false),
yaw_inverted(false),
pitch_inverted(false),
roll_inverted(false),
accel_inverted(false)
{}
};
// initialize an array of macro globals -
macro_globals macro_global[4];
void setup() {
if (!SD.begin(chipSelect)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
}
void loop() {
// load -
SDfile = SD.open("macro_global_0.sav");
char b[sizeof(macro_global[0].MacroPolyphony)];
SDfile.read(b, sizeof(macro_global[0].MacroPolyphony));
memcpy(¯o_global[0].MacroPolyphony, &b, sizeof(macro_global[0].MacroPolyphony));
Serial.print("Loading macro_global[0].MacroPolyphony : ");
Serial.println(macro_global[0].MacroPolyphony); //Display a value to see if it's correct
Serial.print("b : ");
Serial.println(b); //Display a value to see if it's correct
SDfile.close();
// save -
SDfile = SD.open("macro_global_0.sav", FILE_WRITE);
char b[sizeof(macro_global[0].MacroPolyphony)];
memcpy(&b, ¯o_global[0].MacroPolyphony, sizeof(macro_global[0].MacroPolyphony));
SDfile.write(b, sizeof(macro_global[0].MacroPolyphony));
Serial.print("Saving macro_global[0].MacroPolyphony : ");
Serial.println(macro_global[0].MacroPolyphony); //Display a value to see if it's correct
Serial.print("b : ");
Serial.println(b); //Display a value to see if it's correct
SDfile.close();
}
1. What is the simplest way to save a big struct (several variable types and enums, many variables in total) to SD? How about if it's a 2-dimensional array as well?
2. And the simplest way to then read it back from SD, and repopulate the struct it came from with the data?
I have no trouble at all saving/loading this data using the onboard EEPROM, but my project has grown and now the data for a single save now requires that entire space, need an SD card to function similarly. I know the SD (Teensy enhanced) library has the capabilities but I am lost. I'm actually a little surprised there isn't some tool or library function that just takes whatever you throw at it and saves it, like EEPROM.get and .put!
I can save data to a .txt file per the example ReadWrite easily enough. Should I be trying to store it all in human-readable ASCII .txt format? Or should I be casting it all to bytes, saving it to the file, and then dumping it back into the original array after loading? I figured the latter makes more sense, this project isn't a simple datalogger writing to text files as I've seen explained in most of the examples across the internet, this is device configuration data only meant for the device itself (and occasionally for me to debug).
The complete code is over 15k lines long (and the rest is working fine). I've tried to post this in a typical format, just the pieces that matter. If this isn't enough I will set up a standalone test program with these pieces alone later on and post that.
The first goal was just to save/load the variable "macro_global[0].MacroPolyphony". Thanks.
---
#include "SD.h"
#include <SPI.h>
File SDfile;
const int chipSelect = BUILTIN_SDCARD;
//============================//
// MACRO OBJECTS (MACRO GLOBALS) //
//============================//
// enumerate available polyphony options:
enum output_polyphony
{
POLYPHONY_MONO, // 0 - single note at a time, selected by a mapped sensor range
POLYPHONY_DUO, // 1 - two notes at a time, selected by mapped sensor ranges
POLYPHONY_POLY, // 2 - 3 notes at a time, selected by mapped sensor ranges
POLYPHONY_CHORD, // 3 - one chord at a time, selected by mapped sensor range
POLYPHONY_CONTROL // 4 - Control, CC only
};
// enumerate chromatic/CC quantizations:
enum output_quantization
{
QUANTIZE_NONE, // 0 - all notes/values available to output
QUANTIZE_SCALE, // 1 - scale, to be selectable
QUANTIZE_NOTELIST, // 2 - manually defined notes
QUANTIZE_CHORDLIST, // 3 - manually defined chords
QUANTIZE_NUMERIC // 4 - manually defined raw numeric values
};
// enumerate types for chord mode output:
enum chord_types
{
CHORD_DISABLED, // 0
MAJOR, // 1
MINOR, // 2
AUGMENTED_TRIAD, // 3
SUSPENDED_4TH, // 4
DIMINISHED_TRIAD, // 5
MAJOR_6TH, // 6
MINOR_6TH, // 7
MAJOR_7TH, // 8
MINOR_7TH, // 9
DOMINANT_7TH, // 10
DOMINANT_7TH_SUS4, // 11
DOMINANT_7TH_AUGMENTED_5TH, // 12
DOMINANT_7TH_FLATTENED_5TH, // 13
DIMINISHED_7TH, // 14
MINOR_7TH_FLATTENED_5TH, // 15
MINOR_MAJOR_7TH, // 16
MAJOR_9TH, // 17
MINOR_9TH, // 18
DOMINANT_9TH, // 19
NINTH_AUGMENTED_5TH, // 20
NINTH_FLATTENED_5TH, // 21
NINTH_ADD_6TH, // 22
MAJOR_11TH, // 23
MINOR_11TH, // 24
DOMINANT_11TH, // 25
ELEVENTH_FLATTENED_9TH, // 26
MAJOR_13TH, // 27
MINOR_13TH, // 28
DOMINANT_13TH, // 29
THIRTEENTH_FLATTENED_9TH // 30
};
// create data structure for globals like sensor calibration, per macro -
struct macro_globals
{
// User-definable polyphony options -
output_polyphony MacroPolyphony;
// User-definable output quantizations -
output_quantization MacroQuantizer;
// enumerated chord selection -
chord_types MacroChordType;
// MIDI channel acting as master for macro, needed for composite modeled output
unsigned char MacroMIDIchannel;
// calibation values per sensor, intended for use within the calibration UI
short CAPACITIVE_A_SensorInMax;
short CAPACITIVE_A_SensorInMin;
short CAPACITIVE_B_SensorInMax;
short CAPACITIVE_B_SensorInMin;
short GYRO_YAW_SensorInMax;
short GYRO_YAW_SensorInMin;
short GYRO_PITCH_SensorInMax;
short GYRO_PITCH_SensorInMin;
short GYRO_ROLL_SensorInMax;
short GYRO_ROLL_SensorInMin;
short GYRO_ACCEL_SensorInMax;
short GYRO_ACCEL_SensorInMin;
unsigned char CAPACITIVE_A_MIDIOutMax;
unsigned char CAPACITIVE_A_MIDIOutMin;
unsigned char CAPACITIVE_B_MIDIOutMax;
unsigned char CAPACITIVE_B_MIDIOutMin;
unsigned char GYRO_YAW_MIDIOutMax;
unsigned char GYRO_YAW_MIDIOutMin;
unsigned char GYRO_PITCH_MIDIOutMax;
unsigned char GYRO_PITCH_MIDIOutMin;
unsigned char GYRO_ROLL_MIDIOutMax;
unsigned char GYRO_ROLL_MIDIOutMin;
unsigned char GYRO_ACCEL_MIDIOutMax;
unsigned char GYRO_ACCEL_MIDIOutMin;
bool proxA_inverted;
bool proxB_inverted;
bool yaw_inverted;
bool pitch_inverted;
bool roll_inverted;
bool accel_inverted;
// default values:
macro_globals():
// these define how the macro can be used:
MacroPolyphony(POLYPHONY_MONO),
MacroQuantizer(QUANTIZE_NONE),
MacroChordType(CHORD_DISABLED),
// default mode is MONO, no output quantization
MacroMIDIchannel(9),
// sensor calibration data:
CAPACITIVE_A_SensorInMax(896), // == SensorIn values fall within each sensor's native range ==
CAPACITIVE_A_SensorInMin(127), // Capacitive sensor hardware range: 0-1023 (via MPR121.getFilteredData)
CAPACITIVE_B_SensorInMax(896),
CAPACITIVE_B_SensorInMin(127),
GYRO_YAW_SensorInMax(-179), // == SensorIn values fall within each sensor's native range ==
GYRO_YAW_SensorInMin(179), // Yaw, pitch and roll range from -180 to 180 degrees (360 for a complete rotation)
GYRO_PITCH_SensorInMax(-179),
GYRO_PITCH_SensorInMin(179),
GYRO_ROLL_SensorInMax(-179),
GYRO_ROLL_SensorInMin(179),
GYRO_ACCEL_SensorInMax(0),
GYRO_ACCEL_SensorInMin(0),
CAPACITIVE_A_MIDIOutMax(0), // MIDIOut values fall within 0-127
CAPACITIVE_A_MIDIOutMin(127), // The MIDI values are inverted to avoid having to rewrite the calibration UI
CAPACITIVE_B_MIDIOutMax(0),
CAPACITIVE_B_MIDIOutMin(127),
GYRO_YAW_MIDIOutMax(0),
GYRO_YAW_MIDIOutMin(127),
GYRO_PITCH_MIDIOutMax(0),
GYRO_PITCH_MIDIOutMin(127),
GYRO_ROLL_MIDIOutMax(0),
GYRO_ROLL_MIDIOutMin(127),
GYRO_ACCEL_MIDIOutMax(0),
GYRO_ACCEL_MIDIOutMin(127),
proxA_inverted(false),
proxB_inverted(false),
yaw_inverted(false),
pitch_inverted(false),
roll_inverted(false),
accel_inverted(false)
{}
};
// initialize an array of macro globals -
macro_globals macro_global[4];
void setup() {
if (!SD.begin(chipSelect)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
}
void loop() {
// load -
SDfile = SD.open("macro_global_0.sav");
char b[sizeof(macro_global[0].MacroPolyphony)];
SDfile.read(b, sizeof(macro_global[0].MacroPolyphony));
memcpy(¯o_global[0].MacroPolyphony, &b, sizeof(macro_global[0].MacroPolyphony));
Serial.print("Loading macro_global[0].MacroPolyphony : ");
Serial.println(macro_global[0].MacroPolyphony); //Display a value to see if it's correct
Serial.print("b : ");
Serial.println(b); //Display a value to see if it's correct
SDfile.close();
// save -
SDfile = SD.open("macro_global_0.sav", FILE_WRITE);
char b[sizeof(macro_global[0].MacroPolyphony)];
memcpy(&b, ¯o_global[0].MacroPolyphony, sizeof(macro_global[0].MacroPolyphony));
SDfile.write(b, sizeof(macro_global[0].MacroPolyphony));
Serial.print("Saving macro_global[0].MacroPolyphony : ");
Serial.println(macro_global[0].MacroPolyphony); //Display a value to see if it's correct
Serial.print("b : ");
Serial.println(b); //Display a value to see if it's correct
SDfile.close();
}