Storing a struct array in PROGMEM

Hello I'm trying to store a struct array in PROGMEM so during run time I can copy the stored information in a struct located on SRAM, so let's say I have the following structs:

struct myStruct_PROGMEM
{
  const char * const name;
  uint16_t Data; 
};

struct myStruct
{
  String name;

  uint8_t rootNote;
  uint8_t size;
  uint16_t mapping; 
};

So to make the struct array I tried to store the strings that I used in PROGMEM, then directly declared the Array on PROGMEM. (Exactly as in the code below).

static const char String1[]            PROGMEM = "String1";
static const char String2[]            PROGMEM = "String2";


const struct myStruct_PROGMEM myArray[2] PROGMEM = 
{
  {String1,0xCFFF}, {String2,0xFC12}
}

This way my code compiles, but with 33 structs declared it says I am using 50% RAM used, so I know for sure this is not the right way of doing it.

I remember the right way of declaring string arrays into PROGMEM was first to declare the individual strings directly in PROGMEM, then creating a char pointer array that points to each string. As in the following code:

static const char Str1[] PROGMEM = "1";
static const char Str2[] PROGMEM = "String2";
static const char Str3[] PROGMEM = "Str3";

const char * const StringArray_PROGMEM[12] PROGMEM = 
{
  	Str1, Str2, Str3
};

String getString(uint8_t x)
{
  x = x % 3;
  uint8_t size = strlen_P((char*)pgm_read_word(&(StringArray_PROGMEM[x])));
  uint8_t buffer[size];
  return strcpy_P(buffer, (char*)pgm_read_word(&(StringArray_PROGMEM[x])));
}

So maybe I should try to first declare each struct to PROGMEM first, and then declare a struct pointer array in PROGMEM??

Any advise would be greatly appreciated.

A struct definition should define what is in it, variables and pointers and such.
When you create a struct, then you can create it in PROGMEM.
When you copy the struct to sram, you need the same struct to copy to.
The function memcpy_P() can be used for that.

Can you give a working sketch, so we can try it ourselves.
For which Arduino board is it ?

Is the length of the text limited ? If the text is for example maximum 20 characters, then you can put an array in the struct instead of a pointer.

Are you sure that you want to have a String object in a struct ? Can you avoid that ?
If this is for an Arduino Uno, then try to avoid using the String object.

In the sketch below, you can make the ‘myArrayPROGMEM’ very large, the sram usage stays the same.

struct myStruct                // this is the typedef
{
  char label[20];
  uint16_t data;
};

const myStruct myArrayPROGMEM[] PROGMEM =
{
  { "String1", 0xCFFF}, 
  { "String2", 0xFC12},
};

myStruct myArraySRAM;

void setup()
{
  Serial.begin( 9600);
  Serial.println(F( "Started"));

  for( int i=0; i<(sizeof(myArrayPROGMEM)/sizeof(myStruct)); i++)
  {
    memcpy_P( &myArraySRAM, &myArrayPROGMEM[i], sizeof( myStruct));
    Serial.println( myArraySRAM.label);
    Serial.println( myArraySRAM.data, HEX);
  }
}

void loop()
{
}

To have the each text seperate in memory is possible:

struct myStruct                // typedef
{
  const char *label;
  uint16_t data;
};

const char text1[] PROGMEM = "short text";
const char text2[] PROGMEM = "long text in progmem without wasting extra memory";

const myStruct myArrayPROGMEM[] PROGMEM =
{
  { text1, 0xCFFF }, 
  { text2, 0xFC12 },
};

myStruct myArraySRAM;

void setup()
{
  Serial.begin( 9600);
  Serial.println(F( "Started"));

  for( int i=0; i<(sizeof(myArrayPROGMEM)/sizeof(myStruct)); i++)
  {
    // copy the struct to ram
    memcpy_P( &myArraySRAM, &myArrayPROGMEM[i], sizeof( myStruct));

    // Let the "println" solve the PROGMEM pointer
    Serial.println( (const __FlashStringHelper *) myArraySRAM.label);
    Serial.println( myArraySRAM.data, HEX);

    // Use sram to store the text in
    int size = strlen_P( myArraySRAM.label);
    char buffer[size+1];   // one more for zero terminator
    strcpy_P( buffer, myArraySRAM.label);
    Serial.println( buffer);
  }
}

void loop()
{
}

This sketch does not make sense. I have copied the struct from PROGMEM to sram, but I don’t know why. What do you want to achieve ?

Storing the string data in PROGMEM makes sense. There's a lot of data.

Storing the pointers to the strings in PROGMEM rarely makes sense. Pointers are small. There's NOT a lot of data.

Fisrt of all thanks for your replies, let me explain a little bit more about this header. This is only a header of a library, the data I am storing are music scales, so I need to store the scale name and the data, I have managed to get all the data I need right now on 16 bits, thats the main reason for the strucutre.

I have two separate structs mainly because the PROGMEM Struct will be invisible on code. I use a set of functions that manage getting the data out of program memory and copy it to a scale struct used on ram.

I know that storing thing in EEPROM doesn’t take up SRAM, I’ve had this library working before, but now I am merging some libraries and suddenly this header started taking 40% more sram, and I don’t really know why.

This data should be stored in EEPROM because it is not reasonable to store on ram 30+ Structs with strings.

Koepel:
Can you give a working sketch, so we can try it ourselves.

Sure, Besides some definitions and my function declaration this is the header I am using:

static const char ChromaticStr[]            PROGMEM = "Chromatic";
static const char MajorStr[]                PROGMEM = "Major";
static const char NaturalMinorStr[]         PROGMEM = "Natural Minor";
static const char HarmonicMinorStr[]        PROGMEM = "Harmonic Minor";
static const char MajorPentatonicStr[]      PROGMEM = "Major Pentatonic";

static const char MinorPentatonicStr[]      PROGMEM = "Minor Pentatonic";
static const char BluesStr[]                PROGMEM = "Blues";
static const char DorianStr[]               PROGMEM = "Dorian";
static const char PhrygianStr[]             PROGMEM = "Phrygian";
static const char LydianStr[]               PROGMEM = "Lydian";

static const char MixolydianStr[]           PROGMEM = "Mixolydian";
static const char LocrianStr[]              PROGMEM = "Locrian";
static const char AlgerianStr[]             PROGMEM = "Algerian";
static const char ArabicStr[]               PROGMEM = "Arabic";
static const char _AugmentedStr[]           PROGMEM = "Augmented";

static const char BalineseStr[]             PROGMEM = "Balinese";
static const char ByzantineStr[]            PROGMEM = "Byzantine";
static const char ChineseStr[]              PROGMEM = "Chinese";
static const char _DiminishedStr[]          PROGMEM = "Diminished";
static const char DominantDiminishedStr[]   PROGMEM = "Dominant Diminished";

static const char EgyptianStr[]             PROGMEM = "Egyptian";
static const char EightToneSpanishStr[]     PROGMEM = "Eight Tone Spanish";
static const char EnigmaticStr[]            PROGMEM = "Enigmatic";
static const char EthiopianStr[]            PROGMEM = "Ethiopian";
static const char HinduStr[]                PROGMEM = "Hindu";

static const char HirajoshiStr[]            PROGMEM = "Hirajoshi";
static const char HungarianStr[]            PROGMEM = "Hungarian";
static const char JapaneseStr[]             PROGMEM = "Japanese";
static const char OrientalStr[]             PROGMEM = "Oriental";
static const char WholeToneStr[]            PROGMEM = "Whole Tone";

static const char RomanianMinorStr[]        PROGMEM = "Romanian Minor";
static const char SpanishGypsyStr[]         PROGMEM = "Spanish Gypsy";
static const char SuperLocrianStr[]         PROGMEM = "Super Locrian";

struct ScaleStruct_PROGMEM
{
  const char * const name;
  uint16_t ScaleData; 
};

struct ScaleStruct
{
  String name;

  uint8_t varX;                 // This var has nothing to do with eeprom
  uint8_t var1;                 // This var is 4 bits wide it is obtained from eeprom
  uint16_t var2;                // This var if 12 bits wide it is obtained from eeprom
};
  
const struct ScaleStruct_PROGMEM Scale[33] PROGMEM = 
{
  {ChromaticStr         , 0xCFFF}, {MajorStr             , 0x7AD5},
  {NaturalMinorStr      , 0x7B5A}, {HarmonicMinorStr     , 0x7B59},
  {MajorPentatonicStr   , 0x5A94}, {MinorPentatonicStr   , 0x5952},
  {BluesStr             , 0x6972}, {DorianStr            , 0x7B56},
  {PhrygianStr          , 0x7D5A}, {LydianStr            , 0x7AB5},

  {MixolydianStr        , 0x7AD6}, {LocrianStr           , 0x7D6A}, 
  {AlgerianStr          , 0x8B79}, {ArabicStr            , 0x7AEA},
  {_AugmentedStr        , 0x6999}, {BalineseStr          , 0x5D18},
  {ByzantineStr         , 0x7CD9}, {ChineseStr           , 0x58B1}, 
  {_DiminishedStr       , 0x8B6D}, {DominantDiminishedStr, 0x8DB6}, 

  {EgyptianStr          , 0x5A52}, {EightToneSpanishStr  , 0x8DEA}, 
  {EnigmaticStr         , 0x7CAB}, {EthiopianStr         , 0x7B5A},
  {HinduStr             , 0x7ADA}, {HirajoshiStr         , 0x5C62},
  {HungarianStr         , 0x7B39}, {JapaneseStr          , 0x5C52}, 
  {OrientalStr          , 0x7CE6}, {WholeToneStr         , 0x6AAA}, 

  {RomanianMinorStr     , 0x7B36}, {SpanishGypsyStr      , 0x7CDA}, 
  {SuperLocrianStr      , 0x7DAA}
};

So as you can see it is a significant amount of data, it wouldn't make sense to have all of this in SRAM.

Koepel:
When you copy the struct to sram, you need the same struct to copy to.
The function memcpy_P() can be used for that.

I'm using different structs, the struct in SRAM uses a few more variables.

Koepel:
For which Arduino board is it ?

It is for any arduino, I mean is only memory, so this should be able to compile for any arduino (if it fits.., it should fit because it is not a lot of data).

If you want to know I am compiling and running it on an Arduino Mega and on an Arduino Uno.

Koepel:
Is the length of the text limited ? If the text is for example maximum 20 characters, then you can put an array in the struct instead of a pointer.

The text size may differ a lot between them, from "Dominant Diminished" to "Blues".

Koepel:
Are you sure that you want to have a String object in a struct ? Can you avoid that ?
If this is for an Arduino Uno, then try to avoid using the String object.

Well the data is actually stored in char Arrays, so I think If I want to change this I only need to change the SRAM struct.

PaulS:
Storing the string data in PROGMEM makes sense. There's a lot of data.

Storing the pointers to the strings in PROGMEM rarely makes sense. Pointers are small. There's NOT a lot of data.

Thanks, I know that, but I am storing pointers to the char Arrays because these Arrays may differ in length.

So, what do you think should be the right way to go about this?

You want to store it in PROGMEM and not in EEPROM ?
When you upload a sketch, everything in PROGMEM is available to use.
When you store something in EEPROM, you have to find a way to store that data in EEPROM somehow.

Did you know that the definition is already the typedef in c++.
You can remove the 'struct' keyword in this line:

// const struct ScaleStruct_PROGMEM Scale[33] PROGMEM = 
const ScaleStruct_PROGMEM Scale[33] PROGMEM =

The last piece of code with text in PROGMEM, the struct in PROGMEM with pointers and so is okay. You can add to it and the sram usage stays the same.

When you start using that data, it becomes confusing.
Do you want a function that returns the text and the number ? You may not declare a buffer on the stack and return a pointer to it. You need to have a location for that text. Or you could return a pointer to PROGMEM. That depends on the rest of the sketch. For example the Serial functions accept a pointer to sram and also a pointer to PROGMEM.

The way the pointers to PROGMEM (to flash memory) are used is not very consistant. Sometimes a const near pointer is used, and you have to remember yourself that it is a pointer to PROGMEM. The Arduino AVR family does not have a single pointer that can point to sram and PROGMEM. Those microcontrollers assume that a data pointer points to sram and code pointer points to flash.

cheche_romo:
This data should be stored in EEPROM because it is not reasonable to store on ram 30+ Structs with strings.

I’m guessing you really mean FLASH not EEPROM here ?

So, what do you think should be the right way to go about this?

You have the strings and pointer/data array in PROGMEM, so you have to copy out a selected struct, then copy out the string. like this:

static const char ChromaticStr[]            PROGMEM = "Chromatic";
static const char MajorStr[]                PROGMEM = "Major";
static const char NaturalMinorStr[]         PROGMEM = "Natural Minor";
static const char HarmonicMinorStr[]        PROGMEM = "Harmonic Minor";
static const char MajorPentatonicStr[]      PROGMEM = "Major Pentatonic";

static const char MinorPentatonicStr[]      PROGMEM = "Minor Pentatonic";
static const char BluesStr[]                PROGMEM = "Blues";
static const char DorianStr[]               PROGMEM = "Dorian";
static const char PhrygianStr[]             PROGMEM = "Phrygian";
static const char LydianStr[]               PROGMEM = "Lydian";

static const char MixolydianStr[]           PROGMEM = "Mixolydian";
static const char LocrianStr[]              PROGMEM = "Locrian";
static const char AlgerianStr[]             PROGMEM = "Algerian";
static const char ArabicStr[]               PROGMEM = "Arabic";
static const char _AugmentedStr[]           PROGMEM = "Augmented";

static const char BalineseStr[]             PROGMEM = "Balinese";
static const char ByzantineStr[]            PROGMEM = "Byzantine";
static const char ChineseStr[]              PROGMEM = "Chinese";
static const char _DiminishedStr[]          PROGMEM = "Diminished";
static const char DominantDiminishedStr[]   PROGMEM = "Dominant Diminished";

static const char EgyptianStr[]             PROGMEM = "Egyptian";
static const char EightToneSpanishStr[]     PROGMEM = "Eight Tone Spanish";
static const char EnigmaticStr[]            PROGMEM = "Enigmatic";
static const char EthiopianStr[]            PROGMEM = "Ethiopian";
static const char HinduStr[]                PROGMEM = "Hindu";

static const char HirajoshiStr[]            PROGMEM = "Hirajoshi";
static const char HungarianStr[]            PROGMEM = "Hungarian";
static const char JapaneseStr[]             PROGMEM = "Japanese";
static const char OrientalStr[]             PROGMEM = "Oriental";
static const char WholeToneStr[]            PROGMEM = "Whole Tone";

static const char RomanianMinorStr[]        PROGMEM = "Romanian Minor";
static const char SpanishGypsyStr[]         PROGMEM = "Spanish Gypsy";
static const char SuperLocrianStr[]         PROGMEM = "Super Locrian";

struct ScaleStruct_PROGMEM
{
  const char * const name;
  uint16_t ScaleData; 
};

struct ScaleStruct_RAM      // Need matching struct to copy out PROGMEM struct
{
  const char * name;
  uint16_t ScaleData; 
};

#define MAX_NAME_LEN 32
struct ScaleStruct
{
//  String name;    ** avoid String, it dynamically eats memory **          

  char name[MAX_NAME_LEN];    // string from PROGMEM
  uint16_t ScaleData;         // int from PROGMEM

  uint8_t varX;                 // This var has nothing to do with eeprom
  uint8_t var1;                 // This var is 4 bits wide it is obtained from eeprom
  uint16_t var2;                // This var if 12 bits wide it is obtained from eeprom
};
  
const struct ScaleStruct_PROGMEM Scale[33] PROGMEM = 
{
  {ChromaticStr         , 0xCFFF}, {MajorStr             , 0x7AD5},
  {NaturalMinorStr      , 0x7B5A}, {HarmonicMinorStr     , 0x7B59},
  {MajorPentatonicStr   , 0x5A94}, {MinorPentatonicStr   , 0x5952},
  {BluesStr             , 0x6972}, {DorianStr            , 0x7B56},
  {PhrygianStr          , 0x7D5A}, {LydianStr            , 0x7AB5},

  {MixolydianStr        , 0x7AD6}, {LocrianStr           , 0x7D6A}, 
  {AlgerianStr          , 0x8B79}, {ArabicStr            , 0x7AEA},
  {_AugmentedStr        , 0x6999}, {BalineseStr          , 0x5D18},
  {ByzantineStr         , 0x7CD9}, {ChineseStr           , 0x58B1}, 
  {_DiminishedStr       , 0x8B6D}, {DominantDiminishedStr, 0x8DB6}, 

  {EgyptianStr          , 0x5A52}, {EightToneSpanishStr  , 0x8DEA}, 
  {EnigmaticStr         , 0x7CAB}, {EthiopianStr         , 0x7B5A},
  {HinduStr             , 0x7ADA}, {HirajoshiStr         , 0x5C62},
  {HungarianStr         , 0x7B39}, {JapaneseStr          , 0x5C52}, 
  {OrientalStr          , 0x7CE6}, {WholeToneStr         , 0x6AAA}, 

  {RomanianMinorStr     , 0x7B36}, {SpanishGypsyStr      , 0x7CDA}, 
  {SuperLocrianStr      , 0x7DAA}
};
  
// Function to lookup an index in Scale[]
// - copies data and string to passed pointer to ScaleStruct
//
void getScale( struct ScaleStruct *pScale, int scaleIndex )
{
  struct ScaleStruct_RAM temp;
  
  // copy to local struct from PROGMEM
  memcpy_P( &temp, &Scale[scaleIndex], sizeof(struct ScaleStruct_RAM) );

  // get string from PROGMEM
  strncpy_P( pScale->name, temp.name, MAX_NAME_LEN );
  pScale->ScaleData= temp.ScaleData;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("Test...");
  struct ScaleStruct myScale;

  for( int i=0; i<10; i++ ){
    getScale( &myScale, i );
    Serial.print( "name: "); Serial.print(myScale.name);
    Serial.print( ", "); Serial.println(myScale.ScaleData, HEX);
  }
}

void loop() {
  delay(100);
}

Yours,
TonyWilk

I have these set of functions to retrieve data:

String getScaleName(uint8_t x)
{
  uint8_t size = strlen_P((char*)pgm_read_word(&(Scale[x].name)));
  uint8_t buffer[size];
  return strcpy_P(buffer, (char*)pgm_read_word(&(Scale[x].name)));
}
uint8_t getScaleSize(uint8_t mapping)
{
    uint16_t a = pgm_read_word(&(Scale[mapping].ScaleData));
    return a >> 12;
}
uint16_t getScaleMapping(uint8_t mapping)
{
	//pgm_read_word returns 16bits
  return pgm_read_word(&(Scale[mapping].ScaleData)) & 0x0FFF;  
}

void setScale(ScaleStruct *object, uint8_t scale, uint8_t root_note)
{
  object -> name = getScaleName(scale);
  object -> size = getScaleSize(scale);
  object -> mapping = getScaleMapping(scale);
  object -> rootNote = root_note % 12;
} 

void printScaleData(uint8_t x)
{
    Serial.print(getScaleName(x));
    Serial.print(F(", "));
    Serial.print(getScaleSize(x));
    Serial.print(F(" : "));
    Serial.println(getScaleMapping(x),BIN);
}

void printScaleStructData(ScaleStruct *object)
{
  Serial.println( object -> name);
  Serial.println( NoteToString(object -> rootNote , 0 ));
  Serial.println( object -> size);
  Serial.println( object -> mapping, BIN);
}

which I use as follows:

#include "Scales.h"

ScaleStruct myScale;
ScaleStruct *myScalePtr;


void setup() {

  myScalePtr = &myScale;
  Serial.begin(115200);
  setScale(myScalePtr, NATURAL_MINOR, 0); //Pointer to scale struct, Scale , Root Note 
  printScaleStructData(myScalePtr);

}

I think I was wrong assuming that I have made these declarations wrong, I will try to further isolate my memory problem, as it might be coming from other header, by the way my memory is now at 50%

cheche_romo:
It is for any arduino, I mean is only memory, so this should be able to compile for any arduino (if it fits.., it should fit because it is not a lot of data).

If you want to know I am compiling and running it on an Arduino Mega and on an Arduino Uno.

Keep in mind that all the PROGMEM "stuff" and hoops you have to jump through to use it, is not a standard thing. It is proprietary to the AVR and more specifically to the avr-gcc toolset.
It consists of proprietary macros, typedefs, and functions provided by AVR libC (not Arduino) that are used to work around h/w limitations inside the AVR chips that does not allow the processor to directly access data stored in flash.
So all the pain and wonking up of the sketch code needed to support directly accessing data stored in flash is an AVR thing.
On other processors you can simply declare the data as const and get on with the developing the rest of your application.

That said because the AVR processor was the first processor used by Arduino, and there is so much existing code that has already been wonked up to use the AVR PROGMEM stuff, that nearly all the other cores have now provided AVR PROGMEM compatibility even though it is AVR proprietary and totally unnecessary on the other cores.

--- bill

I got it to compile and run on an Arduino UNO:

Sketch uses 2378 bytes (7%) of program storage space. Maximum is 32256 bytes.
Global variables use 206 bytes (10%) of dynamic memory, leaving 1842 bytes for local variables. Maximum is 2048 bytes.
Test...
name: Chromatic, CFFF
name: Major, 7AD5
name: Natural Minor, 7B5A
name: Harmonic Minor, 7B59
name: Major Pentatonic, 5A94
name: Minor Pentatonic, 5952
name: Blues, 6972
name: Dorian, 7B56
name: Phrygian, 7D5A
name: Lydian, 7AB5
const int MAX_NAME_LEN = 25;

static const char ChromaticStr[]            PROGMEM = "Chromatic";
static const char MajorStr[]                PROGMEM = "Major";
static const char NaturalMinorStr[]         PROGMEM = "Natural Minor";
static const char HarmonicMinorStr[]        PROGMEM = "Harmonic Minor";
static const char MajorPentatonicStr[]      PROGMEM = "Major Pentatonic";

static const char MinorPentatonicStr[]      PROGMEM = "Minor Pentatonic";
static const char BluesStr[]                PROGMEM = "Blues";
static const char DorianStr[]               PROGMEM = "Dorian";
static const char PhrygianStr[]             PROGMEM = "Phrygian";
static const char LydianStr[]               PROGMEM = "Lydian";

static const char MixolydianStr[]           PROGMEM = "Mixolydian";
static const char LocrianStr[]              PROGMEM = "Locrian";
static const char AlgerianStr[]             PROGMEM = "Algerian";
static const char ArabicStr[]               PROGMEM = "Arabic";
static const char _AugmentedStr[]           PROGMEM = "Augmented";

static const char BalineseStr[]             PROGMEM = "Balinese";
static const char ByzantineStr[]            PROGMEM = "Byzantine";
static const char ChineseStr[]              PROGMEM = "Chinese";
static const char _DiminishedStr[]          PROGMEM = "Diminished";
static const char DominantDiminishedStr[]   PROGMEM = "Dominant Diminished";

static const char EgyptianStr[]             PROGMEM = "Egyptian";
static const char EightToneSpanishStr[]     PROGMEM = "Eight Tone Spanish";
static const char EnigmaticStr[]            PROGMEM = "Enigmatic";
static const char EthiopianStr[]            PROGMEM = "Ethiopian";
static const char HinduStr[]                PROGMEM = "Hindu";

static const char HirajoshiStr[]            PROGMEM = "Hirajoshi";
static const char HungarianStr[]            PROGMEM = "Hungarian";
static const char JapaneseStr[]             PROGMEM = "Japanese";
static const char OrientalStr[]             PROGMEM = "Oriental";
static const char WholeToneStr[]            PROGMEM = "Whole Tone";

static const char RomanianMinorStr[]        PROGMEM = "Romanian Minor";
static const char SpanishGypsyStr[]         PROGMEM = "Spanish Gypsy";
static const char SuperLocrianStr[]         PROGMEM = "Super Locrian";

struct ScaleStruct_PROGMEM
{
  const char * name;
  uint16_t ScaleData;
};

struct ScaleStruct_RAM
{
  char name[MAX_NAME_LEN];
  uint16_t ScaleData;

  uint8_t varX;                 // This var has nothing to do with eeprom
  uint8_t var1;                 // This var is 4 bits wide it is obtained from eeprom
  uint16_t var2;                // This var if 12 bits wide it is obtained from eeprom
};

// Create an array of ScaleStruct_PROGMEM in PROGMEM
const ScaleStruct_PROGMEM Scale[33] PROGMEM =
{
  {ChromaticStr         , 0xCFFF}, {MajorStr             , 0x7AD5},
  {NaturalMinorStr      , 0x7B5A}, {HarmonicMinorStr     , 0x7B59},
  {MajorPentatonicStr   , 0x5A94}, {MinorPentatonicStr   , 0x5952},
  {BluesStr             , 0x6972}, {DorianStr            , 0x7B56},
  {PhrygianStr          , 0x7D5A}, {LydianStr            , 0x7AB5},

  {MixolydianStr        , 0x7AD6}, {LocrianStr           , 0x7D6A},
  {AlgerianStr          , 0x8B79}, {ArabicStr            , 0x7AEA},
  {_AugmentedStr        , 0x6999}, {BalineseStr          , 0x5D18},
  {ByzantineStr         , 0x7CD9}, {ChineseStr           , 0x58B1},
  {_DiminishedStr       , 0x8B6D}, {DominantDiminishedStr, 0x8DB6},

  {EgyptianStr          , 0x5A52}, {EightToneSpanishStr  , 0x8DEA},
  {EnigmaticStr         , 0x7CAB}, {EthiopianStr         , 0x7B5A},
  {HinduStr             , 0x7ADA}, {HirajoshiStr         , 0x5C62},
  {HungarianStr         , 0x7B39}, {JapaneseStr          , 0x5C52},
  {OrientalStr          , 0x7CE6}, {WholeToneStr         , 0x6AAA},

  {RomanianMinorStr     , 0x7B36}, {SpanishGypsyStr      , 0x7CDA},
  {SuperLocrianStr      , 0x7DAA}
};

// Function to lookup an index in Scale[]
// - copies data and string to passed pointer to ScaleStruct
//
void getScale(ScaleStruct_RAM *pScale, int scaleIndex )
{
  struct ScaleStruct_PROGMEM temp;

  // copy to local struct from PROGMEM
  memcpy_P( &temp, &Scale[scaleIndex], sizeof temp);

  // copy from the PROGMEM format to the RAM format
  strncpy_P(pScale->name, temp.name, MAX_NAME_LEN);
  pScale->ScaleData = temp.ScaleData;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(19200);
  Serial.println("Test...");
  ScaleStruct_RAM myScale;

  for ( int i = 0; i < 10; i++ ) {
    getScale( &myScale, i );
    Serial.print( "name: "); Serial.print(myScale.name);
    Serial.print( ", "); Serial.println(myScale.ScaleData, HEX);
  }
}

void loop() {
  delay(100);
}

johnwasser:
I got it to compile and run on an Arduino UNO:

struct ScaleStruct_PROGMEM

{
 const char * name;
 uint16_t ScaleData;
};

Ah, I notice you just changed the "const char * const name;" in that struct and from the numbers it looks like everything is really in PROGMEM.

I made a separate struct with "const char * name;" (after getting the oddly-worded compiler error when creating a struct variable) thinking that "const char * const name;" was required for PROGMEM.

Yours,
TonyWilk

TonyWilk:
Ah, I notice you just changed the "const char * const name;" in that struct and from the numbers it looks like everything is really in PROGMEM.

I had to remove the second 'const' (the one that said the pointer itself was 'const' as well as pointing to characters that are 'const') so that 'temp' could be declared. The Scale array is declared 'const' which is enough to allow it to go into PROGMEM.

Thanks. I fiddled with it some towards storing a Colossal Cave Adventure and Hunt the Wumpus map. It fits easily on an Uno:

Sketch uses 8914 bytes (27%) of program storage space. Maximum is 32256 bytes.
Global variables use 466 bytes (22%) of dynamic memory, leaving 1582 bytes for local variables. Maximum is 2048 bytes.


// Try storing an adventure-like structure in PROGMEM 
// Based on https://forum.arduino.cc/t/storing-a-struct-array-in-progmem/512308


struct nodeStruct         // typedef to store the nodes
{
  uint16_t id;
  char label[200];
  uint16_t data;
  uint16_t east; 
  uint16_t west; 
  uint16_t north;
  uint16_t south; 
};


// Sample data from an old "Hunt the Wumpus" game and "Adventure"
// https://github.com/osgcc/colossal-cave-adventure/blob/master/advdat.77-03-31
// ... but simplify and map movement words 43:E, 44:W, 45:N, 46:S 
//
// FLASH storage of array of nodes
const nodeStruct nodes[] PROGMEM =  
{//node, desc,data,      forward, left, right, back (zero based) 
  { 0,  "Zero Placeholder",0xfeed, 0,0,0,0},
  { 1, "YOU ARE STANDING AT THE END OF A ROAD BEFORE A SMALL BRICK BUILDING.",
    0xface, 3,2,5,4}, 
  { 2, "YOU HAVE WALKED UP A HILL, STILL IN THE FOREST THE ROAD NOW SLOPES BACK DOWN",
    0xdead, 1,5,1,5},
  { 3, "YOU ARE INSIDE A BUILDING, A WELL HOUSE FOR A LARGE SPRING.", 0xbeef, 0,1,0,0},
  { 4, "YOU ARE IN A VALLEY IN THE FOREST BESIDE A STREAM TUMBLING ALONG A ROCKY BED.", 0xFC12, 5,5,1,7},
  { 5, "YOU ARE IN OPEN FOREST, WITH A DEEP VALLEY TO ONE SIDE.", 0xFC12, 4,4,0,4},
  { 6, "YOU ARE IN OPEN FOREST NEAR BOTH A VALLEY AND A ROAD.", 0xFC12, 4,4,1,5},
  { 7, "AT YOUR FEET ALL THE WATER OF THE STREAM SPLASHES INTO A\n"\
       "2 INCH SLIT IN THE ROCK. DOWNSTREAM THE STREAMBED IS BARE ROCK.", 0xFC12, 5,5,4,8},
  { 8, "YOU ARE IN A 20 FOOT DEPRESSION FLOORED WITH BARE DIRT.", 0xFC12, 9,5,7,5},
  { 9, "YOU ARE IN A SMALL CHAMBER BENEATH A 3X3 STEEL GRATE TO THE\n"\
       "SURFACE. A LOW CRAWL OVER COBBLES LEADS INWARD TO THE WEST.", 0xFC12, 8,10,0,0},
  { 10, "YOU ARE CRAWLING OVER COBBLES IN A LOW PASSAGE. THERE IS A\n"\
        "DIM LIGHT AT THE EAST END OF THE PASSAGE.", 0xFC12, 9,11,0,0},
  { 11, "YOU ARE IN A DEBRIS ROOM, FILLED WITH STUFF WASHED IN FROM\n"\
        "THE SURFACE.  YOU HEAR A WUMPUS.", 0xFC12, 10,10,10,12},
  { 12, "wumpus dodecahedron 0, ", 0x0000, 13,16,19,11}, // 1,4,7 (+12)
  { 13, "wumpus dodecahedron 1", 0x0000, 14,12,21,13}, // 2,0,9
  { 14, "wumpus dodecahedron 2", 0x0000, 15,13,23,14}, // 3,1,11
  { 15, "wumpus dodecahedron 3", 0x0000, 16,14,25,15}, //4,2,13
  { 16, "wumpus dodecahedron 4", 0x0000, 12,15,17,16}, //0,3,5
  { 17, "wumpus dodecahedron 5", 0x0000, 12,26,18,17}, // 0,14,6
  { 18, "wumpus dodecahedron 6", 0x0000, 19,17,28,18}, // 7,5,16
  { 19, "wumpus dodecahedron 7", 0x0000, 12,18,19,20}, // 0,6,8
  { 20, "wumpus dodecahedron 8", 0x0000, 21,19,29,20}, // 9,7,17
  { 21, "wumpus dodecahedron 9", 0x0000, 13,20,22,21}, // 1,8,10
  { 22, "wumpus dodecahedron 10", 0x0000, 23,21,30,22}, // 11,9,18
  { 23, "wumpus dodecahedron 11", 0x0000, 14,22,24,23}, // 2,10,12
  { 24, "wumpus dodecahedron 12", 0x0000, 25,23,31,24}, //13,11,19
  { 25, "wumpus dodecahedron 13", 0x0000, 15,24,26,25}, // 3,12,14,
  { 26, "wumpus dodecahedron 14", 0x0000, 17,25,27,26}, // 5,13,15
  { 27, "wumpus dodecahedron 15", 0x0000, 26,31,28,27}, // 14,19,16
  { 28, "wumpus dodecahedron 16", 0x0000, 18,27,29,28}, //6,15,17
  { 29, "wumpus dodecahedron 17", 0x0000, 20,28,30,29}, // 8,16,18
  { 30, "wumpus dodecahedron 18", 0x0000, 22,29,31,30}, // 10,17,19
  { 31, "wumpus dodecahedron 19", 0x0000, 24,30,27,31}, // 12,18,15
  };


nodeStruct node;  // RAM spage for the working node
int new_location,look ;
int location ;
int old_location ; 

int doGetNode(int i){  // copy a node from FLASH to working memory 
  if( (i > 0) && ((unsigned int) i < sizeof(nodes)/sizeof(node))){
     memcpy_P( &node, &nodes[i], sizeof( nodeStruct));
     return(true);
  }
  Serial.print("Problems copying node ");
  Serial.println(i);
  return(0);
}

int check_move(int plan, int location){
  if (plan <1 ){
    Serial.println("You can't go that direction");
    return(location);
  }
  return(plan);
  
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  while(!Serial); // wait....
  location = 0;
  new_location = 1;
  look = 1;
  
}
void loop() {
  char ch;
  if(look or new_location != location){
    doGetNode(new_location);
    old_location = location;
    location = new_location ; 
    Serial.println( node.label);
    Serial.println("e,w,n,s? ");
    look = 0;
  }
  if (Serial.available()){
    ch = Serial.read();
    if(ch == '\n' or ch == '\r') { 
      Serial.println(ch);
      look = 1;
    }
    switch(ch) {
      case 'e':
        new_location = check_move(node.east,location);
        break;
      case 'w':
        new_location = check_move(node.west,location);
        break;
      case 'n':
        new_location = check_move(node.north,location);;
        break;
      case 's':
        new_location = check_move(node.south,location);
        break;
    }
    
  }
 // delay(500);

}

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.