struct containing array to be stored to SD card....

snowsh

Well-known member
right, I have been fighting this one for a few days now....

I have several different structs that make up the core of my project...

here is an example of one of my structs:

Code:
struct mypattern {
  uint8_t note[MAX_NUMBER_OF_STEPS];
  int8_t octave[MAX_NUMBER_OF_STEPS];    // -1 = down 0 = normal 1 = up
  int8_t transpose;
  uint8_t accent[MAX_NUMBER_OF_STEPS];
  bool slide[MAX_NUMBER_OF_STEPS];
  uint8_t noteLength[MAX_NUMBER_OF_STEPS];
  uint8_t patternLength;
  char patternName[PATTERN_NAME_LENGTH];
} currentPattern;

EXTMEM struct mypattern mypatterns[NUMBER_OF_PATTERNS];

Its "library" members are stored in EXTMEM. the current pattern is stored in SRAM so its fast. any time a pattern is changed, I siply copy from the libray the requested pattern into currentPattern. All good. I can store them also to SD card for backup, and when the device is started it loads all the data from the SD card back into the EXTMEM members... perfect. happy me. ;)

However......

this struct does not perform.....

Code:
struct drummerLibraryStruct {
  char title[DRUMMER_PATTERN_NAME_LENGTH] = {'n','a','m','e',' ','h','e','r','e',' '};
  uint8_t pattern[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];    // this is the offender!
};

EXTMEM struct drummerLibraryStruct drummerLibrary[NUMBER_OF_DRUMMER_PATTERNS];

the obvious difference here is that the struct contains a multidimensional array.....

I get no errors in complile, and otherwise it works perfectly well. The program is quite happy with the multidimensional array.

I have a complicated "boot" sequence that checks for the sd card, if its there, it checks for an init file which says if the SD card contains the data, ie "formatted" for my project....
if the init file is not present, it runs through all the struct members and stores them to the SD card during startup. As the program is run, and the user makes any edits, the data for the given pattern for example is updated in EXTMEM or wherever the particular location is the given data. At the same time it is also saved back to the SD card so any edits are stored for the next time the device is booted up...

In this particular instance, with the multi dimensional array, when the device starts up and tries to reload the data back from SD to the EXTMEM location, it fails. the only error I can get is "Unable to read the example block in dru_000.txt" which is attached to the loop checking against the data size:

Code:
sdfile.available()

When I check in notepadd++ and veiw the data on the SD card in hex, I see the data has not been written correctly to the SD card....

I ran an experiment by placing a multidimensional array to the other struct example, and yes, it fails in the same way......

here are two snippets from my code, the first saves the data (it could be a struct, or an array - I have had a headache with this so I gave up and simply wrote different functions to deal with each struct.... I hope to tidy that later as I improve my c++ - this was all so easy with PHP :p - not that I am going back. I am really enjoying the challenge of C++

anyway....

function to store to SD card:

Code:
void savePatternSD(int i)
{
  Serial.print( F("saving  to SD card : "));
  Serial.println(i);
  getFilename(i, SEQUENCER);
  addNames(i, SEQUENCER);

  byte    *buff = (byte *) &mypatterns[i];                               // to access example as bytes
  File  sdfile;                                                             // File object
  char  fname[ 14 ];                                                        // even # of bytes big enough to hold 8.3 filename and terminator
  strcpy( fname, filename);

  sdfile = SD.open( fname, FILE_WRITE );
  if (!sdfile) Serial.println( F("error cannot open sd card"));
  if (!sdfile.seek( 0 )) Serial.println( F("error cannot seek sd card"));
  if (!sdfile.write( buff, sizeof( mypatterns[i] ))) Serial.println( F("error cannot write sd card"));
  sdfile.close();
}



function example to read from SD card and store back to EXTMEM

Code:
void sdOpenPatternFile(int i, int type)
{
  getFilename(i, type);
  byte  count;
  File  sdfile;                                                                   // File object
  char  fname[ 14 ];                                                              // even # of bytes big enough to hold 8.3 filename and terminator
  strcpy( fname, filename );

  sdfile = SD.open( fname, FILE_READ );                                           // try to open text.txt  // ========== SD open file to read ====================================

  if ( !sdfile )                                                                  // if test.txt not found, notify and change state
  {
    Serial.print( F("Unable to open for read: ") );
    Serial.println( fname );
    while ( 1 );
  }

  byte    *buff = (byte *) &myPatterns[i];                                     // to access example as bytes  // ========== SD read file to struct ==================================

  for ( count = 0; count < sizeof( mypatterns[i] ); count++ )
  {
    if ( sdfile.available())
    {
      *( buff + count ) = sdfile.read();
    }
    else
    {
      Serial.print( F( "Unable to read the example block in " ));
      Serial.println( fname );
      Serial.print( count, DEC );
      Serial.println( F( " bytes read." ));
      while ( 1 );
    }
  }
  sdfile.close();
}

I suspect the issue is that I have yet to properly get my head around pointers etc.... I see & and * and am still confused..... I hope that someone here can spot the issue and help me on my way.

if you are so inclined, there is a video of my project here: https://www.picuki.com/media/2603517577921609539 sorry for the sound, it seems my phone needs replacing :eek::(

I just realised I didnt show the functions that deal with this particular struct. here they are:

Save:

Code:
void saveDrummerPatternSD(int i)
{
  Serial.print( F("saving drummer pattern to SD card : "));
  Serial.println(i);
  getFilename(i, DRUMMER);
  addNames(i, DRUMMER);



  byte    *buff = (byte *) &drummerLibrary[i].pattern;                                // to access example as bytes

  
  File  sdfile;                                                                   // File object
  char  fname[ 14 ];                                                              // even # of bytes big enough to hold 8.3 filename and terminator
  strcpy( fname, filename);

  Serial.print("size: ");
  Serial.println(sizeof(buff));
  
  sdfile = SD.open( fname, FILE_WRITE );
  if (!sdfile) Serial.println( F("drummer pattern error cannot open sd card"));
  if (!sdfile.seek( 0 )) Serial.println( F("drummer pattern error cannot seek sd card"));
  if (!sdfile.write( buff, sizeof( drummerLibrary[i].pattern ))) Serial.println( F("error cannot write sd card"));

  sdfile.close();
}

Load:

Code:
void sdOpenDrummerFile(int i, int type)
{
  getFilename(i, type);
  byte  count;
  File  sdfile;                                                                   // File object
  char  fname[ 14 ];                                                              // even # of bytes big enough to hold 8.3 filename and terminator
  strcpy( fname, filename );

  sdfile = SD.open( fname, FILE_READ );                                           // try to open text.txt  // ========== SD open file to read ====================================

  if ( !sdfile )                                                                  // if test.txt not found, notify and change state
  {
    Serial.print( "Unable to open for read: " );
    Serial.println( fname );
    while ( 1 );
  }

  byte    *buff = (byte *) &drummerLibrary[i].pattern;                            // to access example as bytes  // ========== SD read file to struct ==================================

  for ( count = 0; count < sizeof( drummerLibrary[i].pattern ); count++ )
  {
    if ( sdfile.available())
    {
      *( buff + count ) = sdfile.read();
    }
    else
    {
      Serial.print( F( "Unable to read the example block in " ));
      Serial.println( fname );
      Serial.print( count, DEC );
      Serial.println( F( " bytes read." ));
      while ( 1 );
    }
  }
  sdfile.close();
}
 
Last edited:
update - the code is actually being written correctly it seems. The bytes look to tally up to defined patterns. So i think the issue is with the load function, which at present is only ever used during the boot process, but that will change as the project develops.


drummerhex.JPG
 
When you save the file, you write it as one block with write( buff, sizeof( drummerLibrary.pattern )) but you read it back in one byte at a time.
Can you try reading it all in one go and see if that changes anything?
Code:
sdfile.read( buff, sizeof( drummerLibrary[i].pattern ))

Pete
 
When you save the file, you write it as one block with write( buff, sizeof( drummerLibrary.pattern )) but you read it back in one byte at a time.
Can you try reading it all in one go and see if that changes anything?
Code:
sdfile.read( buff, sizeof( drummerLibrary[i].pattern ))

Pete


Thanks pete! it now passes and runs without the error. But, the data in the SD is now garbage. I will look tomorrow as its now way too late here in Scotland!
 
i would memmove the struct as a byte array then write that to file, then when reading back the bytes to an array, memmove the array back into the struct. I did this in FlexCAN_T4 for CAN frames (well not the SD part, but it'll help you get there i think)
 
When I want to send a "struct" over radio I create a Union as exampled in the code below. Then I just address it as buf and send that.
Code:
typedef struct drummerLibraryStruct{
  char     title[DRUMMER_PATTERN_NAME_LENGTH] = {'n','a','m','e',' ','h','e','r','e',' '};
  uint8_t  pattern[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sixeof[ drummerLibraryStruct ];

typedef union drummerLibraryDataUnion {
    SlaveToMasterTransmitDataType  message;
    uint8_t                        buf[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;
I must admit I don't like memmove, it just seems an area where errors could occur and I would be unknowingly trampling over memory locations.
 
Yea, could be, but why move memory in any case when the data can simply be addressed in a different manner and a problem solved.
In the example shown above buf is exactly the same as message. For data storage buf is used, for data access message is used as in below:-
message.title = whatever or
message.pattern[x][y] = whatever
 
Last edited:
When I am using structures and union for data it can become seemingly difficult to name all the bits and pieces such that the code seems to make sense and is readable. Taking the example above I would probably change it to that shown below.
Code:
typedef struct drummerLibraryStruct{
  char     title[DRUMMER_PATTERN_NAME_LENGTH] = {'n','a','m','e',' ','h','e','r','e',' '};
  uint8_t  pattern[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sixeof[ drummerLibraryStruct ];

typedef union drummerLibraryDataUnion {
    SlaveToMasterTransmitDataStruct  structure;
    uint8_t                          structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;

drummerLibraryDataUnion data;

// then addressing the data becomes
data.structure.title = whatever
// or
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );

PS please note that I got mixed up in my naming in msg#6 above.
I hope that this starts to make some sense. When you realize that "structureSaveAndLoad" and "structure" in the union are the same entity, just called different names it starts to make more sense.
 
When I am using structures and union for data it can become seemingly difficult to name all the bits and pieces such that the code seems to make sense and is readable. Taking the example above I would probably change it to that shown below.
Code:
typedef struct drummerLibraryStruct{
  char     title[DRUMMER_PATTERN_NAME_LENGTH] = {'n','a','m','e',' ','h','e','r','e',' '};
  uint8_t  pattern[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sixeof[ drummerLibraryStruct ];

typedef union drummerLibraryDataUnion {
    SlaveToMasterTransmitDataStruct  structure;
    uint8_t                          structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;

drummerLibraryDataUnion data;

// then addressing the data becomes
data.structure.title = whatever
// or
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );

PS please note that I got mixed up in my naming in msg#6 above.
I hope that this starts to make some sense. When you realize that "structureSaveAndLoad" and "structure" in the union are the same entity, just called different names it starts to make more sense.

Sorry, I dont see it.... How does the member structure relate to drummerLibraryStruct?

Code:
typedef union drummerLibraryDataUnion {
    SlaveToMasterTransmitDataStruct  structure;
    uint8_t                          structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;
 
Oops sorry it's still got bits of my data structure in there.
I've just corrected it.
Code:
typedef struct drummerLibraryStruct{
  char     title[DRUMMER_PATTERN_NAME_LENGTH] = {'n','a','m','e',' ','h','e','r','e',' '};
  uint8_t  pattern[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sizeof[ drummerLibraryStruct ];

typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  structure;
    uint8_t               structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;

drummerLibraryDataUnion data;

// then addressing the data becomes
data.structure.title = whatever
// or
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );
 
Code:
const int sizeOfDrummerLibraryStruct = sizeof[ drummerLibraryStruct ];

is that correct? how come [] not () after sizeof?

I still dont see how the union references the drummer pattern struct...
 
Yes you're correct. The number of times I looked at this small piece of code to make sure it was correct!!! Blame it upon my old eyes.
Corrected bit below:-
Code:
typedef struct drummerLibraryStruct{
  char     title[DRUMMER_PATTERN_NAME_LENGTH] = {'n','a','m','e',' ','h','e','r','e',' '};
  uint8_t  pattern[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sizeof( drummerLibraryStruct );

typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  structure;
    uint8_t               structureSaveAndLoad[ sizeOfDrummerLibraryStruct ];
} drummerLibraryDataUnion;

drummerLibraryDataUnion data;

// then addressing the data becomes
data.structure.title = whatever
// or
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );
 
im guessing this:

Code:
typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  structure;
    uint8_t               structureSaveAndLoad[ sizeOfDrummerLibraryStruct ];
} drummerLibraryDataUnion;

should be:

Code:
typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  drummerLibraryStruct;            // not this: structure;
    uint8_t               structureSaveAndLoad[ sizeOfDrummerLibraryStruct ];
} drummerLibraryDataUnion;

maybe (Probably) I am wrong though..... but that line is totally confusing me :D
 
I am afraid you are wrong. Perhaps "structure" is not a very well chosen name. Replace the word structure with any other name that you wish to call your structured data. Perhaps called just that or similar as below:-
Code:
typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  myStructuredData;
    uint8_t               structureSaveAndLoad[ sizeOfDrummerLibraryStruct ];
} drummerLibraryDataUnion;

drummerLibraryDataUnion data;

// then addressing the data becomes
data.myStructuredData.title = whatever
// or
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );

Once you get the idea it becomes so easy to save structured data, send it over serial link or even radio.
All without having to move it to a buffer array to achieve the same thing, and of course reducing the risk of moving data to the wrong place and corrupting things. When this sort of corruption occurs it is very difficult to diagnose as it is impossible (nearly) to see.
 
I am afraid you are wrong. Perhaps "structure" is not a very well chosen name. Replace the word structure with any other name that you wish to call your structured data. Perhaps called just that or similar as below:-
Code:
typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  myStructuredData;
    uint8_t               structureSaveAndLoad[ sizeOfDrummerLibraryStruct ];
} drummerLibraryDataUnion;

drummerLibraryDataUnion data;

// then addressing the data becomes
data.myStructuredData.title = whatever
// or
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );

Once you get the idea it becomes so easy to save structured data, send it over serial link or even radio.
All without having to move it to a buffer array to achieve the same thing, and of course reducing the risk of moving data to the wrong place and corrupting things. When this sort of corruption occurs it is very difficult to diagnose as it is impossible (nearly) to see.

Aha! OK, I think I see it now..... will give this a go right now!
 
_drummer:62: error: use of deleted function 'drummerLibraryDataUnion::drummerLibraryDataUnion()'
drummerLibraryDataUnion data;
^
_drummer.ino:57:15: note: 'drummerLibraryDataUnion::drummerLibraryDataUnion()' is implicitly deleted because the default definition would be ill-formed:
typedef union drummerLibraryDataUnion {
^
_drummer:58: error: union member 'drummerLibraryDataUnion::myStructuredData' with non-trivial 'drummerLibraryStruct::drummerLibraryStruct()'
drummerLibraryStruct myStructuredData;
^
fileSystem: In function 'void saveDrummerPatternSD(int)':
fileSystem:218: error: invalid array assignment
data.myStructuredData.title = drummerLibrary.title;
^
fileSystem:219: error: invalid array assignment
data.myStructuredData.pattern = drummerLibrary.pattern;
^
fileSystem:221: error: 'class SDClass' has no member named 'write'
SD.write( data.structureSaveAndLoad, sizeOfDrummerLibraryStruct );
^
use of deleted function 'drummerLibraryDataUnion::drummerLibraryDataUnion()'
 
@snowsh
it now passes and runs without the error.
That's good.
But, the data in the SD is now garbage.
That's bad.

Until you can reliably read and write the structure to/from the SD, the actual "structure" of the structure isn't as important.

Pete
 
This is what I have used in my code, and it works fine.
Code:
/***************************************************************************
 *                     SLAVE Originated Data Types                         *
 ***************************************************************************/
typedef struct SlaveToMasterReplyType {
    byte      replyFrom;
    byte      replyWhat;
    uint16_t  tempEtAl;
} SlaveToMasterReplyType;
const int sizeOfSlaveToMasterReplyType = sizeof(SlaveToMasterReplyType);

typedef union SlaveToMasterReplyUnion {
    SlaveToMasterReplyType data;
    uint8_t                buf[sizeOfSlaveToMasterReplyType];
} SlaveToMasterReplyUnion;

typedef struct SlaveToMasterTransmitDataType {
    addrType               addr;
    SlaveToMasterReplyType data;
} SlaveToMasterTransmitDataType;
const int sizeOfSlaveToMasterTransmitDataType = sizeof(SlaveToMasterTransmitDataType);

typedef union SlaveToMasterTransmitReplyUnion {
    SlaveToMasterTransmitDataType message;
    uint8_t                       buf[sizeOfSlaveToMasterTransmitDataType];
} SlaveToMasterTransmitReplyUnion; // slaveToMasterData;
I am having a look to see if I can find a solution, but it may not be until tomorrow.
 
I managed to get this to work, seems definition complexity must be outside a Union.
Code:
// Visual Micro is in vMicro>General>Tutorial Mode
// 
/*
    Name:       drummerlibrary.ino
    Created:	26/06/2021 18:53:23
*/
#include "Arduino.h"

const int8_t DRUMMER_PATTERN_NAME_LENGTH    = 10;
const int8_t NUMBER_OF_DRUMMER_INSTRUMENTS  = 3;
const int8_t NUMBER_OF_DRUMMER_STEPS        = 7;

// Define User Types below here or use a .h file
//
typedef int drummerArrayType[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];
typedef byte    titleArray[6];

typedef struct drummerLibraryStruct {
    titleArray          title;  // [DRUMMER_PATTERN_NAME_LENGTH] = { 'n','a','m','e',' ','h','e','r','e',' ' };
    drummerArrayType    pattern; // [NUMBER_OF_DRUMMER_STEPS] ;    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sizeof(drummerLibraryStruct);

typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  myDataStructure;
    uint8_t               structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;
drummerLibraryDataUnion data;

void setup()
{


}

void loop()
{

 //   data.myDataStructure.title = "myTitle";
    data.myDataStructure.pattern[2][2] = 44;
}
The bit where it falls over is when I get to assigning a constant name to a variable name, but that is nothing to do with using a Union.
I will leave that bit up to you to sort.
Let me know how you get on.
 
Here is something I knocked up reading from SD card, modifying and writing the data back to SD card.
Code:
// Visual Micro is in vMicro>General>Tutorial Mode
// 
/*
    Name:       drummerlibrary.ino
    Created:	26/06/2021 18:53:23
*/
#include "Arduino.h"
#include <SD.h>
#include <SPI.h>

const int chipSelect = BUILTIN_SDCARD;

const int8_t DRUMMER_PATTERN_NAME_LENGTH    = 10;
const int8_t NUMBER_OF_DRUMMER_INSTRUMENTS  = 3;
const int8_t NUMBER_OF_DRUMMER_STEPS        = 7;

// Define User Types below here or use a .h file
//
typedef int   drummerArrayType[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];
typedef byte  titleArray[8];

typedef struct drummerLibraryStruct {
    titleArray          title;  // [DRUMMER_PATTERN_NAME_LENGTH] = { 'n','a','m','e',' ','h','e','r','e',' ' };
    drummerArrayType    pattern; // [NUMBER_OF_DRUMMER_STEPS] ;    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sizeof(drummerLibraryStruct);

typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  myDataStructure;
    uint8_t               structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;
drummerLibraryDataUnion data;

void setup(){

    Serial.begin(9600);
    while (!Serial) {
        ; // wait for serial port to connect.
    }

//Read from SD Card

    Serial.print("Initializing SD card...");

    // see if the card is present and can be initialized:
    if (!SD.begin(chipSelect)) {
        Serial.println("Card failed, or not present");
        // don't do anything more:
       return;
    }
    Serial.println("card initialized.");

    // open the file.
    File dataFile = SD.open("Drummer");
    dataFile.read(&data.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
    dataFile.close();

// Modify Data

    data.myDataStructure.pattern[2][2] = 44;

// Write to SD Card

  // open the file. 
    dataFile = SD.open("Drummer", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to Drummer...");
        dataFile.write(&data.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }

}

void loop(){

    //data.myDataStructure.title = "myTitle";
    data.myDataStructure.pattern[2][2] = 44;

}
It compiles ok, but I have not tested it.
 
Here is something I knocked up reading from SD card, modifying and writing the data back to SD card.
Code:
// Visual Micro is in vMicro>General>Tutorial Mode
// 
/*
    Name:       drummerlibrary.ino
    Created:	26/06/2021 18:53:23
*/
#include "Arduino.h"
#include <SD.h>
#include <SPI.h>

const int chipSelect = BUILTIN_SDCARD;

const int8_t DRUMMER_PATTERN_NAME_LENGTH    = 10;
const int8_t NUMBER_OF_DRUMMER_INSTRUMENTS  = 3;
const int8_t NUMBER_OF_DRUMMER_STEPS        = 7;

// Define User Types below here or use a .h file
//
typedef int   drummerArrayType[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];
typedef byte  titleArray[8];

typedef struct drummerLibraryStruct {
    titleArray          title;  // [DRUMMER_PATTERN_NAME_LENGTH] = { 'n','a','m','e',' ','h','e','r','e',' ' };
    drummerArrayType    pattern; // [NUMBER_OF_DRUMMER_STEPS] ;    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sizeof(drummerLibraryStruct);

typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  myDataStructure;
    uint8_t               structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;
drummerLibraryDataUnion data;

void setup(){

    Serial.begin(9600);
    while (!Serial) {
        ; // wait for serial port to connect.
    }

//Read from SD Card

    Serial.print("Initializing SD card...");

    // see if the card is present and can be initialized:
    if (!SD.begin(chipSelect)) {
        Serial.println("Card failed, or not present");
        // don't do anything more:
       return;
    }
    Serial.println("card initialized.");

    // open the file.
    File dataFile = SD.open("Drummer");
    dataFile.read(&data.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
    dataFile.close();

// Modify Data

    data.myDataStructure.pattern[2][2] = 44;

// Write to SD Card

  // open the file. 
    dataFile = SD.open("Drummer", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to Drummer...");
        dataFile.write(&data.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }

}

void loop(){

    //data.myDataStructure.title = "myTitle";
    data.myDataStructure.pattern[2][2] = 44;

}
It compiles ok, but I have not tested it.

I just sent you a PM
 
Here is a sketch which creates data, saves it to SD card and reads it back for comparison. It does a little more than that but I am being lazy in not wanting to type out a description of all it does.
It's quite well documented so should be relatively easy to understand.
When I was writing this sketch I was surprised to find that you can assign a struct ( struct/union a = struct/union b) but you cannot do a comparison in C. Just seems nuts. Anyway I put in a Function to do that.
Here is the code, enjoy (or otherwise!!)
Code:
// Visual Micro is in vMicro>General>Tutorial Mode
// 
/*
    Name:       drummerlibrary.ino
    Created:	26/06/2021 18:53:23
*/
#include "Arduino.h"
#include <SD.h>
#include <SPI.h>

const int chipSelect = BUILTIN_SDCARD;

const int8_t DRUMMER_PATTERN_NAME_LENGTH    = 10;
const int8_t NUMBER_OF_DRUMMER_INSTRUMENTS  = 3;
const int8_t NUMBER_OF_DRUMMER_STEPS        = 7;

// Define User Types below here or use a .h file
//
typedef uint8_t drummerArrayType[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];
typedef byte    titleArray[8];

typedef struct drummerLibraryStruct {
    titleArray          title;  // [DRUMMER_PATTERN_NAME_LENGTH] = { 'n','a','m','e',' ','h','e','r','e',' ' };
    drummerArrayType    pattern; // [NUMBER_OF_DRUMMER_STEPS] ;    // this is the offender!
} drummerLibraryStruct;
const int sizeOfDrummerLibraryStruct = sizeof(drummerLibraryStruct);

typedef union drummerLibraryDataUnion {
    drummerLibraryStruct  myDataStructure;
    uint8_t               structureSaveAndLoad[sizeOfDrummerLibraryStruct];
} drummerLibraryDataUnion;
drummerLibraryDataUnion saveOnSdCardData;
drummerLibraryDataUnion emptyData;


void ClearOutEmptyData() {
    int a, b;

    for (a = 0; a < NUMBER_OF_DRUMMER_INSTRUMENTS; a++) {
        for (b = 0; b < NUMBER_OF_DRUMMER_STEPS; b++) {
            emptyData.myDataStructure.pattern[a][b] = 0;
        }
    }
}

void FillDataForSdCardWithData() {
    int a, b;

    for (a = 0; a < NUMBER_OF_DRUMMER_INSTRUMENTS; a++) {       //3
        for (b = 0; b < NUMBER_OF_DRUMMER_STEPS; b++) {                 //7
            saveOnSdCardData.myDataStructure.pattern[a][b] = (a+1) * (b+1);
        }
    }
}

void SaveDataForSdCardWithData() {

    File dataFile = SD.open("Drummer", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to Drummer...");
        dataFile.write(saveOnSdCardData.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }
}

void SaveEmptyDataOnSdCard() {

    File dataFile = SD.open("MtDrmr", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to MtDrmr...");
        dataFile.write(emptyData.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }
}
void WriteDataFromEmptyDataToWrData() {
    File dataFile = SD.open("WrData", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to WrData...");
        dataFile.write(emptyData.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }
}

void DeleteFile(char fn[]) {
    Serial.println(fn);
    if (SD.exists(fn)) {
        SD.remove(fn);
    }
}
void DeleteTheFiles() {
    Serial.println("Deleting files");
    DeleteFile("Drummer");
    DeleteFile("MtDrmr");
    DeleteFile("WrData");
}

void ReadDataFromSdCardIntoEmptyData(){
    // open the file.
    File dataFile = SD.open("Drummer");
    dataFile.read(emptyData.structureSaveAndLoad, sizeOfDrummerLibraryStruct);
    dataFile.close();
}

bool DataMatches(drummerLibraryDataUnion a, drummerLibraryDataUnion b) {
    bool match = true;
    int  i = 0;
    while (match & ( i<sizeOfDrummerLibraryStruct)) {
        match = ( a.structureSaveAndLoad[i] == b.structureSaveAndLoad[i] );
        i++;
    }
    return match;
}
void setup() {

    Serial.begin(9600);
    while (!Serial) {
        ; // wait for serial port to connect.
    }

    //Read from SD Card

    Serial.print("Initializing SD card...");

    // see if the card is present and can be initialized:
    if (!SD.begin(chipSelect)) {
        Serial.println("Card failed, or not present");
        // don't do anything more:
        return;
    }
    Serial.println("card initialized.");
    DeleteTheFiles();
    Serial.println("Deleted Files");

    Serial.println("Clearing emptyData and copying to saveOnSdCardData");
    ClearOutEmptyData();                    //Make sure emptyData IS Empty
    saveOnSdCardData = emptyData;           //Save data from emptyData into saveOnSdCardData to make sure they are the same

    // Now check that saveOnSdCardData has been filled with the original EmptyData
    if (DataMatches(saveOnSdCardData, emptyData)) Serial.println("Data matched ok");
    else Serial.println("Data mismatch");

    Serial.println("Putting some info/dara into saveOnSdCardData and Saving to SD card");
    Serial.println("...also saving emptyData to SD card for comparison");
    FillDataForSdCardWithData();            //Put some data into this structure;
    SaveEmptyDataOnSdCard();                //..and save the emptyData and saveOnSdCardData to SD card.
    SaveDataForSdCardWithData();


    Serial.println("Read data from saveOnSdCardData Saved onto SD card into emptyData");
    Serial.println("...and check that they now match again.");
    ReadDataFromSdCardIntoEmptyData();      // Read the saaveOnSdCardData cack into the emptyData

    // Now see if the original EmptyDataFile has been loaded with the Data Saved on SD Card
    if (DataMatches(saveOnSdCardData, emptyData)) Serial.println("Data Matched, saved ok");
    else Serial.println("Data NOT saved ok: Data mismatch");

    Serial.println("...also re save emptyData to SD card as file WrData for comparison");
    WriteDataFromEmptyDataToWrData();        // Write data from emptyData which should be filled with data from saveOnSdCardData
    Serial.println("Data savedto SD card");

// The SD card can be placed in a PC and checked to prove that the program has worked correctly.
// A useful editor for examining disk files is https://mh-nexus.de/en/hxd/
}

void loop(){

    //data.myDataStructure.title = "myTitle";
    emptyData.myDataStructure.pattern[2][2] = 44;

}
 
Last edited:
Here is a version of the above sketch which saves an array of the data structure.
Code:
// Visual Micro is in vMicro>General>Tutorial Mode
// 
/*
    Name:       drummerlibrary.ino
    Created:	26/06/2021 18:53:23
*/
#include "Arduino.h"
#include <SD.h>
#include <SPI.h>

const int chipSelect = BUILTIN_SDCARD;

const int8_t DRUMMER_PATTERN_NAME_LENGTH    = 10;
const int8_t NUMBER_OF_DRUMMER_INSTRUMENTS  = 3;
const int8_t NUMBER_OF_DRUMMER_STEPS        = 7;
const int8_t numberOfRecords                = 22;

// Define User Types below here or use a .h file
//
typedef uint8_t drummerArrayType[NUMBER_OF_DRUMMER_INSTRUMENTS][NUMBER_OF_DRUMMER_STEPS];
typedef byte    titleArray[8];

typedef struct drummerLibraryStruct {
    titleArray          title;  // [DRUMMER_PATTERN_NAME_LENGTH] = { 'n','a','m','e',' ','h','e','r','e',' ' };
    drummerArrayType    pattern; // [NUMBER_OF_DRUMMER_STEPS] ;    // this is the offender!
} drummerLibraryStruct;

typedef struct drummerLibraryArray {
    drummerLibraryStruct dataArray[numberOfRecords];
} drummerLibraryArray;
const int sizeOfDrummerLibraryArray = sizeof(drummerLibraryArray);

typedef union drummerLibraryDataUnion {
    drummerLibraryArray   myDataStructure;
    uint8_t               structureSaveAndLoad[sizeOfDrummerLibraryArray];
} drummerLibraryDataUnion;
drummerLibraryDataUnion saveOnSdCardData, emptyData;


void ClearOutEmptyData() {
    int a, b, i;

    for (i = 0; i < numberOfRecords; i++) {       //22
        for (a = 0; a < NUMBER_OF_DRUMMER_INSTRUMENTS; a++) {
            for (b = 0; b < NUMBER_OF_DRUMMER_STEPS; b++) {
                emptyData.myDataStructure.dataArray[i].pattern[a][b] = 0;
            }
        }
    }
}

void FillDataForSdCardWithData() {
    int a, b, i;


    for (i = 0; i < numberOfRecords; i++) {       //22
        for (a = 0; a < NUMBER_OF_DRUMMER_INSTRUMENTS; a++) {       //3
            for (b = 0; b < NUMBER_OF_DRUMMER_STEPS; b++) {                 //7
                saveOnSdCardData.myDataStructure.dataArray[i].pattern[a][b] = (a + 1) * (b + 1);
            }
        }
    }
}

void SaveDataForSdCardWithData() {

    File dataFile = SD.open("Drummer", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to Drummer...");
        dataFile.write(saveOnSdCardData.structureSaveAndLoad, sizeOfDrummerLibraryArray);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }
}

void SaveEmptyDataOnSdCard() {

    File dataFile = SD.open("MtDrmr", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to MtDrmr...");
        dataFile.write(emptyData.structureSaveAndLoad, sizeOfDrummerLibraryArray);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }
}
void WriteDataFromEmptyDataToWrData() {
    File dataFile = SD.open("WrData", FILE_WRITE);

    // if the file opened okay, write to it:
    if (dataFile) {
        Serial.print("Writing to WrData...");
        dataFile.write(emptyData.structureSaveAndLoad, sizeOfDrummerLibraryArray);
        // close the file:
        dataFile.close();
        Serial.println("done.");
    }
    else {
        // if the file didn't open, print an error:
        Serial.println("error opening test.txt");
    }
}

void DeleteFile(char fn[]) {
    Serial.println(fn);
    if (SD.exists(fn)) {
        SD.remove(fn);
    }
}
void DeleteTheFiles() {
    Serial.println("Deleting files");
    DeleteFile("Drummer");
    DeleteFile("MtDrmr");
    DeleteFile("WrData");
}

void ReadDataFromSdCardIntoEmptyData(){
    // open the file.
    File dataFile = SD.open("Drummer");
    dataFile.read(emptyData.structureSaveAndLoad, sizeOfDrummerLibraryArray);
    dataFile.close();
}

bool DataMatches(drummerLibraryDataUnion a, drummerLibraryDataUnion b) {
    bool match = true;
    int  i = 0;
    while (match & ( i< sizeOfDrummerLibraryArray)) {
        match = ( a.structureSaveAndLoad[i] == b.structureSaveAndLoad[i] );
        i++;
    }
    return match;
}
void setup() {

    Serial.begin(9600);
    while (!Serial) {
        ; // wait for serial port to connect.
    }

    //Read from SD Card

    Serial.print("Initializing SD card...");

    // see if the card is present and can be initialized:
    if (!SD.begin(chipSelect)) {
        Serial.println("Card failed, or not present");
        // don't do anything more:
        return;
    }
    Serial.println("card initialized.");
    DeleteTheFiles();
    Serial.println("Deleted Files");

    Serial.println("Clearing emptyData and copying to saveOnSdCardData");
    ClearOutEmptyData();                    //Make sure emptyData IS Empty
    saveOnSdCardData = emptyData;           //Save data from emptyData into saveOnSdCardData to make sure they are the same

    // Now check that saveOnSdCardData has been filled with the original EmptyData
    if (DataMatches(saveOnSdCardData, emptyData)) Serial.println("Data matched ok");
    else Serial.println("Data mismatch");

    Serial.println("Putting some info/dara into saveOnSdCardData and Saving to SD card");
    Serial.println("...also saving emptyData to SD card for comparison");
    FillDataForSdCardWithData();            //Put some data into this structure;
    SaveEmptyDataOnSdCard();                //..and save the emptyData and saveOnSdCardData to SD card.
    SaveDataForSdCardWithData();


    Serial.println("Read data from saveOnSdCardData Saved onto SD card into emptyData");
    Serial.println("...and check that they now match again.");
    ReadDataFromSdCardIntoEmptyData();      // Read the saaveOnSdCardData cack into the emptyData

    // Now see if the original EmptyDataFile has been loaded with the Data Saved on SD Card
    if (DataMatches(saveOnSdCardData, emptyData)) Serial.println("Data Matched, saved ok");
    else Serial.println("Data NOT saved ok: Data mismatch");

    Serial.println("...also re save emptyData to SD card as file WrData for comparison");
    WriteDataFromEmptyDataToWrData();        // Write data from emptyData which should be filled with data from saveOnSdCardData
    Serial.println("Data savedto SD card");

// The SD card can be placed in a PC and checked to prove that the program has worked correctly.
// A useful editor for examining disk files is https://mh-nexus.de/en/hxd/
}

void loop(){

    //data.myDataStructure.title = "myTitle";
    emptyData.myDataStructure.dataArray[1].pattern[2][2] = 44;

}

PS I am using VisualMicro with Visual studio. It really makes writing and debugging code so much easier.
Each time there is a compile error I am able to jump directly to the line at fault.
 
this came up on a google search yesterday and I thought I would pop up the solution i finally went with. Hope it helps someone. Two functions, one to save to SD, the other to read. there is a call to a function that preps the filename and path, you can replace that with hardcoded filename or roll your own method...

Code:
function openFromSd()
{
  size_t size = 0;
  byte * buff;

  for (uint8_t i = 0; i < SIZE_OF_DRUMMER_LIBRARY; i++)
  {
    size = sizeof(drummerLibrary[i]);                                                   // the struct
    buff = (byte *) &drummerLibrary[i];

    getFilePathAndName(i, config.currentProject, type);// replace this as needed.

    size_t count;                                                                       // !!!!! IMPORTANT !!!  make sure count is declared as size_t
    File  sdfile;                                                                       // File object
    char  fname[ FULL_PATHNAME_LENGTH ];                                                // even # of bytes big enough to hold 8.3 filename and terminator



    strcpy( fname, pathName );

    // could define fname here hardcoded

    sdfile = SD.open( fname, FILE_READ );                                           

    size_t available = sdfile.available();
    size_t sdSize = sdfile.size();

    if (sdfile.size() != size)
    {
      Serial.println(F("!! ERROR !! SD size mismatch!!: "));
      Serial.println(fname);
      Serial.print(F("type: "));
      Serial.println(type);
      Serial.print(F("i: "));
      Serial.println(i);

      Serial.print(F("SD size: "));
      Serial.println(sdSize);

      Serial.print( F( "available: " ));
      Serial.println(available);

      Serial.print(F("struct size: "));
      Serial.println(size);

      Serial.println(F("END ERROR REPORT"));

      if (sdSize == 0)                                                                  // this is for development. it will make the new folder on the fly as we need it.
      {
        Serial.print(F("CREATE NEW FOLDER TYPE: "));
        Serial.println(type);

        makeFolder(type);
        initialiseSdCardFolderFiles(type);

        sdfile = SD.open( fname, FILE_READ );                                           // try to open text.asq  // ========== SD open file to read ====================================
        available = sdfile.available();
        sdSize = sdfile.size();
      }
      else
      {
        while (1);
      }
    }
    if ( !sdfile )                                                                      // if test.asq not found, notify and change state
    {
      Serial.print( F("Unable to open for read: ") );
      Serial.println( fname );
      while ( 1 );
    }

    for (count = 0; count < size; count++ )                                             // !!!!! IMPORTANT !!!  make sure count is declared as size_t here
    {
      if (sdfile.available())
      {
        available = sdfile.available();

        *( buff + count ) = sdfile.read();
      }
      else
      {
        Serial.print( F( "Unable to read the example block in " ));
        Serial.println( fname );
        Serial.print( count);
        Serial.println( F( " bytes read." ));
        while ( 1 );
      }
    }
    sdfile.close();
  }
}



void saveHandlerToSD(int type, uint8_t i)
{
  size_t size;
  
  byte *buff;

  size = sizeof(drummerLibrary[i]);
  
  buff = (byte *) &drummerLibrary[i];

  getFilePathAndName(i, config.currentProject, type);                                     // prep the full path inc filename id, projectId, type

  File  sdfile;                                                                           // File object

  char  fname[ FULL_PATHNAME_LENGTH ];                                                    // even # of bytes big enough to hold 8.3 filename and terminator

  strcpy( fname, pathName);                                                               // the vars were set by getFileAndPathNAme()

    // could define fname here hardcoded

  sdfile = SD.open( fname, FILE_WRITE );

  if (!sdfile) Serial.println( F("error cannot open sd card"));

  if (!sdfile.seek( 0 )) Serial.println( F("error cannot seek sd card"));

  if (!sdfile.write( buff, size)) Serial.println( F("error cannot write sd card"));

  sdfile.close();

}
 
Back
Top