Home:ALL Converter>Reading strings from structures in PROGMEM using a loop

Reading strings from structures in PROGMEM using a loop

Ask Time:2017-05-16T22:56:46         Author:GleNnoS

Json Formatter

Found a solution, see my comment below!

I am trying to read data which is placed in structures and stored in the flash memory of an Arduino Mega (ATmega 2560) using PROGMEM. The structure objects manufacturer_1 and manufacturer_2 are accessed with pointers.

Due to size of the sketch; I decided to create a (relative) small example which illustrates the problem. The following code shows how I define the structures and data.

typedef struct
{
   char info[20];
} manufacturer_def;

typedef struct
{
   unsigned int totalManufacturers;
   const manufacturer_def* manufacturer[2];
} data_def;

const manufacturer_def manufacturer_1 PROGMEM =
{
   "Manufacturer 1"
};

const manufacturer_def manufacturer_2 PROGMEM =
{
   "Manufacturer 2"
};

const data_def data PROGMEM =
{
  2,
  {
    &manufacturer_1,
    &manufacturer_2
  }
};

void setup() 
{
  // Serial monitor setup
  Serial.begin(115200);   // Begin serial monitor
}

void loop() 
{
   mainMenu();
}

The problem!

I would like to fill an array with strings using a loop. The following code is not working properly:

void mainMenu()
{
   unsigned int i = 0;

   unsigned int totalMenuItems = pgm_read_word(&data.totalManufacturers);
   String menuItems[totalMenuItems];

   char str_buf[20];

   // Create array with items for menu
   for (i = 0; i < totalMenuItems; i++)
   {
     strcpy_P(str_buf, data.manufacturer[i]->info);
     menuItems[i] = str_buf;
     Serial.println(menuItems[i]);
   }
 }

Output (section):

p�









p�

Strangely, when I place the strcpy_P command outside the loop and specify the iteration variable by hand it works:

void mainMenu()
{
  unsigned int i = 0;

  unsigned int totalMenuItems = pgm_read_word(&data.totalManufacturers);
  String menuItems[totalMenuItems];

  char str_buf[20];

  strcpy_P(str_buf, data.manufacturer[0]->info);
  menuItems[0] = str_buf;
  strcpy_P(str_buf, data.manufacturer[1]->info);
  menuItems[1] = str_buf;

  // Create array with items for menu
  for (i = 0; i < totalMenuItems; i++)
  {
    Serial.println(menuItems[i]);
  }
}

Output:

Manufacturer 1
Manufacturer 2

Why is this happening?

Author:GleNnoS,eproduced under the CC 4.0 BY-SA copyright license with a link to the original source and this disclaimer.
Link to original article:https://stackoverflow.com/questions/44004939/reading-strings-from-structures-in-progmem-using-a-loop
drerD :

I think it has to do with PROGMEM is storing the variable in FLASH, instead of RAM. Read this documentation on PROGMEM, so when you don't use pgm_read_word_near() and dynamically accessing the FLASH-stored variable, there will be problem. But when you were using constants(literal):\n\nstrcpy_P(str_buf, data.manufacturer[0]->info);\nmenuItems[0] = str_buf;\n\n\nto access the variable it's fine.\n\nAnd the problem can manifest itself due to the implementation of strcpy_P().\n\nSo in that documentation they did this:\n\nconst char* const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};\n\nchar buffer[30]; // make sure this is large enough for the largest string it must hold\n\nvoid loop()\n{\n /* Using the string table in program memory requires the use of special functions to retrieve the data.\n The strcpy_P function copies a string from program space to a string in RAM (\"buffer\").\n Make sure your receiving string in RAM is large enough to hold whatever\n you are retrieving from program space. */\n\n\n for (int i = 0; i < 6; i++)\n {\n strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.\n Serial.println(buffer);\n delay( 500 );\n }\n}\n",
2017-05-16T22:55:26
Jeroen3 :

Your line right here:\nstrcpy_P(str_buf, data.manufacturer[i]->info);\nHas the problem that data is not in ram, after all you've specified PROGMEM, but you're using ram load instructions by default to read the data as argument for strcpy_P.\n\nDue to the harvard architecture of the chip it need to use specific instructions to read data from flash.\nFirst you instruct the compiler to put your string in PROGMEM, which is flash. If you do not, the boot code will copy the data from flash to DATA on boot for you to access with regular data pointers and instructions.\n\nThen when you want to read data from a PROGMEM address you have to tell the compiler again that your given address is in PROGMEM by using pgm_read.... \n\nYou cannot see by value that a pointer is program, data memory or peripheral registers, in contrast to ARM architecture, where there is only one 4GB address space where flash, ram and peripheral locations are distinguishable by their position in the address space.\n\nOn AVR:\n\n\nMOV - Copy Register to register \nLDS - Load Direct from data\nspace to register \nLPM - Load Program Memory to register \nIN - Load an I/O Location to Register\n\n\nAnd their opposites obviously.\n\nOn contrast to ARM where you just have (width variants of): LDR and STR, Load and Store with immediate offset\n\nThat's why its cumbersome to use PROGMEM. Welcome to embedded software development.",
2017-05-17T07:08:32
GleNnoS :

Thanks to user14042 and Jeroen3 my brother and I found a solution which partly works; up to 8 manufacturer_def structures. The solution is based on, firstly, creating a pointer to the correct manufacturer_def structure using pgm_read_ptr(&data.manufacturer[i]). And, secondly, the string is retrieved out of flash memory using strcpy_P(str_buf, manufacturer_ptr->info).\n\nThe code:\n\nvoid mainMenu()\n{\n unsigned int i = 0;\n\n unsigned int totalMenuItems = pgm_read_word(&data.totalManufacturers);\n String menuItems[totalMenuItems];\n\n char str_buf[20];\n\n // Create array with items for menu\n for (i = 0; i < totalMenuItems; i++)\n {\n manufacturer_def* manufacturer_ptr = pgm_read_ptr(&data.manufacturer[i]);\n strcpy_P(str_buf, manufacturer_ptr->info);\n menuItems[i] = str_buf;\n }\n }\n\n\nThe sketch uploads without any warnings or errors when I am using 8 manufacturer_def structures:\n\nconst data_def data PROGMEM =\n{\n 8,\n {\n &manufacturer_1,\n &manufacturer_2,\n &manufacturer_3,\n &manufacturer_4,\n &manufacturer_5,\n &manufacturer_6,\n &manufacturer_7,\n &manufacturer_8\n }\n};\n\n\nHowever, when I am using more than 8 manufacturer_def structures trouble starts. With 9 manufacturer_def structures the sketch uploads without warnings or errors but the Arduino does not boot correctly. With 10 manufacturer_def structures I get the following error:\n\n/tmp/ccovyDEX.s: Assembler messages:\n/tmp/ccovyDEX.s:5799: Error: value of 70776 too large for field of 2 bytes at 78808\n\n\nWith 11 manufacturering_def structures I get the following error:\n\n/tmp/ccCa42WT.s: Assembler messages:\n/tmp/ccCa42WT.s:6513: Error: value of 78640 too large for field of 2 bytes at 86672\n/tmp/ccCa42WT.s:6514: Error: value of 70776 too large for field of 2 bytes at 86674\n\n\nI understand that 2 bytes can hold a maximum value of 65535. But what value could be causing this?",
2017-05-18T07:24:56
yy