1
votes

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?

4
C or C++? They're completely different languages.tambre
I am using this code on an Arduino Mega (ATmega 1280) which I believe is merely a set of C/C++.GleNnoS
Then it's either C or C++ (or a subset or superset of one of those). That said, your code very much looks like C (typedef structs for example, use of char buffers and C-strings).tambre

4 Answers

1
votes

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):

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

to access the variable it's fine.

And the problem can manifest itself due to the implementation of strcpy_P().

So in that documentation they did this:

const char* const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

char buffer[30];    // make sure this is large enough for the largest string it must hold

void loop()
{
  /* Using the string table in program memory requires the use of special functions to retrieve the data.
     The strcpy_P function copies a string from program space to a string in RAM ("buffer").
     Make sure your receiving string in RAM  is large enough to hold whatever
     you are retrieving from program space. */


  for (int i = 0; i < 6; i++)
  {
    strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
    Serial.println(buffer);
    delay( 500 );
  }
}
1
votes

Your line right here: strcpy_P(str_buf, data.manufacturer[i]->info);
Has 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.

Due to the harvard architecture of the chip it need to use specific instructions to read data from flash.
First 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.

Then 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....

You 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.

On AVR:

And their opposites obviously.

On contrast to ARM where you just have (width variants of): LDR and STR, Load and Store with immediate offset

That's why its cumbersome to use PROGMEM. Welcome to embedded software development.

0
votes

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).

The code:

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++)
   {
     manufacturer_def* manufacturer_ptr = pgm_read_ptr(&data.manufacturer[i]);
     strcpy_P(str_buf, manufacturer_ptr->info);
     menuItems[i] = str_buf;
   }
 }

The sketch uploads without any warnings or errors when I am using 8 manufacturer_def structures:

const data_def data PROGMEM =
{
  8,
  {
    &manufacturer_1,
    &manufacturer_2,
    &manufacturer_3,
    &manufacturer_4,
    &manufacturer_5,
    &manufacturer_6,
    &manufacturer_7,
    &manufacturer_8
  }
};

However, 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:

/tmp/ccovyDEX.s: Assembler messages:
/tmp/ccovyDEX.s:5799: Error: value of 70776 too large for field of 2 bytes at 78808

With 11 manufacturering_def structures I get the following error:

/tmp/ccCa42WT.s: Assembler messages:
/tmp/ccCa42WT.s:6513: Error: value of 78640 too large for field of 2 bytes at 86672
/tmp/ccCa42WT.s:6514: Error: value of 70776 too large for field of 2 bytes at 86674

I understand that 2 bytes can hold a maximum value of 65535. But what value could be causing this?

0
votes

Solution!

Due to the size of the data structure some addresses exceed the maximum value of standard 2-bytes pointers can hold. Therefore, it is necessary to use the _far commands to address that part of PROGMEM, and thus 4-byte pointers. This is easily solved for the user-defined PROGMEM data. However, by having this much user-defined data in PROGMEM, some internal Arduino functionality is also pushed past the 64K mark, and those do not use 4-byte pointers. This issue arises from the default linker map, which places those functions after the user-defined PROGMEM data. A solution here is to move that data into another section of PROGMEM. See the topics below for more information and the pull request for a fix:

https://github.com/arduino/Arduino/issues/2226

https://github.com/arduino/Arduino/pull/6317

When the mentioned fix is applied my code is working with data structures bigger than 64k bytes. A caveat of storing structs in PROGMEM at >64k bytes is that one has to manually calculate addresses using offsets (i.e. to get the ith element of an array in PROGMEM, one would do mystruct_ptr + offsetoff(mystruct, myarray) + i * sizeof(myarrayitem).

The code:

void mainMenu()
{
   unsigned int i = 0;

   unsigned int totalMenuItems = data.totalManufacturers;
   String menuItems[totalMenuItems];

   char str_buf[20];

  // Create array with items for menu
  for (i = 0; i < totalMenuItems; i++)
  {
    uint_farptr_t manufacturer_ptr = data.manufacturer[i];
    strcpy_PF(str_buf, manufacturer_ptr + offsetof(manufacturer_def, info));
    menuItems[i] = str_buf;
  }
 }