PROGMEM headache !!! Why life is soooo complicated?

Dear Arduino experts,

I have a large table of text constants that is eating up all my RAM (BTW I don't understand why the compiler is stupid enough to place constants in RAM ??!?), and I want to move it to Flash. :grin:

Obviously, I tried to use PROGMEM and prog_char, ** but ** I get a nice "section type conflict" when trying to compile.
My problem is that part of the table contains language-dependent string constants (stored outside of the table), and apparently the compiler is too dumb to copy them in my table.

Here is the code : example of table that is OK, and another that is failing!!!
BTW, this code is an example, the actual table is WAY larger, and only contains text, not numeric data...

#define CONFIG_NB					5		// Max number of predefined configurations
#define CONFIG_SENSNAMES_NB			5		// Max number of names in each configuration

const prog_char * A PROGMEM = "Test 1";		// Definition of a language-dependent string
const prog_char * B PROGMEM = "Test 2";		// Definition of a language-dependent string

// If not using my language-dependent strings, it is OK
//     the following line compiles without error
const prog_char * OTHER_NAMES_ARR[CONFIG_NB][CONFIG_SENSNAMES_NB] PROGMEM =
	{	{ "Test 1", "1.30", "2.30", "3.30", "4.60"} ,			// Config 1
		{ "Test 2", "1.30", "2.30", "3.30", "4.30"} ,			// Config 2
		{ "Test 1", "1.25", "2.25", "3.25", "4.50"} ,			// Config 3
		{ "Test 1", "1.20", "2.20", "3.20", "4.40"} ,			// Config 4
		{ "Test 2", "1.15", "2.15", "3.15", "4.30"} ,			// Config 5
	 };

// If I try to use my language-dependent strings, it is FAILING => the following line compiles with the following error:
//    error: CONFIG_NAMES_ARR causes a section type conflict
const prog_char * CONFIG_NAMES_ARR[CONFIG_NB][CONFIG_SENSNAMES_NB] PROGMEM =
	{	{ A, "1.30", "2.30", "3.30", "4.60"} ,			// Config 1
		{ B, "1.30", "2.30", "3.30", "4.30"} ,			// Config 2
		{ A, "1.25", "2.25", "3.25", "4.50"} ,			// Config 3
		{ A, "1.20", "2.20", "3.20", "4.40"} ,			// Config 4
		{ B, "1.15", "2.15", "3.15", "4.30"} ,			// Config 5
	 };

How can I implement this language-dependent table cleanly ?
Thanks for your help!!

Maybe I should add that:

  • I would prefer not to have an almost-identical table for each language (this is error-prone). XD
  • Selection of language is a compilation directive (#define)

Its not too dumb at all, you've asked for an array of pointers to chars, which is what you've got.

The trick is the pointers need to be PROGMEM pointers too, like:

char * A PROGMEM = "Test 1";		// Definition of a language-dependent string
char * B PROGMEM = "Test 2";		// Definition of a language-dependent string

char *MyPRG[]  PROGMEM = {
  A,
  B
};

Then to access the pointer in the array you have to get the PGM data for the pointer:

  //Read pointer from array.
  const __FlashStringHelper *data = ( __FlashStringHelper * ) pgm_read_word( &MyPRG[ 0 ] );

  //Read data from pointer
  Serial.print(  data  );

I used serial as a shortcut to print the PROGMEM data, however you can use memcpy_P or pgm_read_byte.

Thanks pYro_65, but I tried to compile your example and get:
error: MyPRG causes a section type conflict

:frowning:

Oh hadn't actually tried it. Change A, B from:

char * A PROGMEM = "Test 1";		// Definition of a language-dependent string
char * B PROGMEM = "Test 2";		// Definition of a language-dependent string

To:

char  A[] PROGMEM = "Test 1";		// Definition of a language-dependent string
char  B[] PROGMEM = "Test 2";		// Definition of a language-dependent string

Just tried it, works with the change from pointer to array type, got 'Test 1' in serial.

Wow !!!! It works!! Thank you very much!!

I'm still puzzled about the difference between

char *a

and char a[] ... But maybe it's because I'm an electronics engineer, not a software guy! :grin:

I don't understand why the compiler is stupid enough to place constants in RAM ??!?

Consider this:Serial.println ("The quick brown fox");
and this

char* text = "The quick brown fox";
Serial.println (text);

and

char* text PROGMEM = "The quick brown fox";
Serial.println (text);

Bearing in mind that this is a Harvard architecture processor, do you see the problem?

Well... You just lost me!! :cold_sweat:

Let me guess:
case 1 - string in RAM
case 2 - pointer in RAM, string in Flash??
case 3 - string in Flash??

=( =( =(

It's much simpler than that - in all cases, (the last one doesn't work, BTW) the pointer is just a pointer - an address in memory.
The function called has no way of knowing which memory space the pointer refers to.

Let's take a step back a moment and take a look at what is actually happening here, and get a few basic concepts straight. It's all very well bandying around magic words like "Harvard Architecture", but what does that actually mean?

Basically there are two (main) kinds of processor architecture in the world - Harvard (and "modified" Harvard) and Princeton (aka Von Neumann) architectures. The main difference between them is in how the core CPU looks at the outside world.

Let's go back in time a moment to the days of the ZX Spectrum. This is very much a simplified view of the "traditional" computer model. You have an address bus, a data bus, and some control signals. Onto those buses are attached different kinds of memory. For the ZX Spectrum 48K that's a 64KB (65536 address) memory space, as there are 16 address lines (216 = 65536). Addresses 0 - 16383 are attached to the system ROM. 16384 - 65535 are connected to RAM, with some of that RAM being used by the display system as a frame buffer. So, reading from address 362 will be reading from ROM, and writing to address 52876 will be writing to RAM. Simple enough, yes?

That's the "Princeton" architecture. All the memory of all different types lumped together into one big address space. Makes for a nice simple system.

Small microcontrollers, however, tend to use the "Harvard" (or more commonly the "Modified Harvard") architecture. In this way of thinking, instead of one address and data bus for all the memory, it has two! One of the bus sets goes to the Flash (or ROM) memory, and the other goes to the RAM. Consequently things get a little more complex. Each address bus (let's, for the sake of argument take it to be like the above example, and have 16-bit addresses) has its own addresses, so the Flash address bus has addresses 0-65535, and the RAM address bus has addresses 0-65535. This gives a massive increase in speed over the Princeton architecture, as it can be (for example) reading from Flash and writing to RAM at the same time! But that comes at a cost - the cost of complexity.

Say you want to read, as above, from the Flash address 362 and write to the RAM address 52876. Just saying "Read from 362" is now ambiguous, as there are two address 362's - the Flash 362 and the RAM 362, so which is it that gets read? Well, this is when it comes down to the specific chip to sort it out. Most will have different instructions to read/write RAM to reading/writing Flash. You might have, for example, (hypothetical) instructions like this:

MOV $r1, (362)
MOV (52876), $r1
MOVF $r1, (362)
MOVR (52876), $r1

Of course, some don't have instructions for accessing the flash at all, and instead use an internal peripheral to access the flash:

LDI $r1, 0x6A
MOV (FLADDL), $r1
LDI $r1, 0x01
MOV (FLADDH), $r1
LDI $r1, 0x40 // hypothetical "read from flash" instruction to peripheral
MOV (FLCON), $r1
MOV $r1, (FLDAT)

As you can see that gets even more complex. Load the two parts of the address into HIGH and LOW registers (we're talking 8-bit here), set a function, and read the data.

So how do you simplify the job?

Well, unless told otherwise, the compiler tries to make things easy for us. Data in the flash is first copied into RAM as part of the startup routine of the program (aka crt0 - C Run-Time phase 0). This makes accessing that data from then on as simple as reading any other data from RAM. But when you have a limited amount of RAM and lots of data that can cause big problems. That's where such things as the PROGMEM flag comes in to play. This basically tells the compiler "This data must NOT be copied into RAM". Which is good, but then how do you get at the data? Your program then has to specifically use the facilities the chip provides to read that data, which as you have seen is not always as straight forward as just reading. In fact, it can get downright complex.

So Arduino have provided a set of functions to do the work for you. You can have direct access to the flash memory by using the pgm_read_byte() and similar functions. These will run the special commands needed to read the data from the flash memory. You pass them the address of a variable in flash and it gets the data from that address. If you pass the address of a variable in RAM to the function it won't know the difference, and will get the data at that address from flash anyway, so you have to be careful to only ever use variables that have been flagged as PROGMEM.

Also there are a number of extra versions of existing functions, specially string manipulation functions, which deal specifically with C strings in flash. These are suffixed with _P to indicate they work on PROGMEM variables. Examples are strcmp_P(), memcpy_P() etc.

So to summarise, let's take the analogy of apartment blocks.

You have two apartment blocks next door to each other (A and B). Each block has 16 apartments in it, numbered 1 to 16. Someone sends a letter to apartment 3, but where does it go? There's 2 apartment 3s. And to top it all off apartment block B doesn't have any mailboxes.

So, you have to address your letter to either apartment A3 or apartment B3, but to get it to apartment B3 you have to first send your letter to Geoff in apartment A6 who is friends with Gill in apartment B9. He will hand the letter to Gill, who can then pass it on to Arthur in apartment B3.

So there you have it - Harvard architecture in a nutshell.

Thank you. This is an excellent explanation of processor architectures! XD

BTW, real life is certainly more complex than this, since you need cache to access Flash... but it should be more or less transparent to the programmer... :grin:

real life is certainly more complex than this, since you need cache to access Flash.

That's not a problem - the AVR has no cache.

FTBug:
Thank you. This is an excellent explanation of processor architectures! XD

BTW, real life is certainly more complex than this, since you need cache to access Flash... but it should be more or less transparent to the programmer... :grin:

No, you don't "need" cache to access flash. Cache is often employed when accessing flash as flash access is often slower than RAM access, but it is not a requirement for accessing flash.

but to get it to apartment B3 you have to first send your letter to Geoff in apartment A6 who is friends with Gill in apartment B9. He will hand the letter to Gill, who can then pass it on to Arthur in apartment B3.

GILL!, How embarrassing, I've been calling him Jim.


Rob

Nice summary of Harvard vs. Princeton architectures majenko.

As to the difference between a pointer and an array, as somebody has said a pointer is the address within a given address space, while an array is a series of bytes that holds the data. Most places in C derived languages, when you look at an array, the compiler silently converts the array to a pointer to the first element. So for example:

    static char foo[] = "abcdef";
    char x;
    int i;
    // ...
    x = foo[i];

is logically converted to:

    static char foo[] = "abcdef";
    char *tmpptr;
    char x;
    int i;
    // ...
    tmpptr = &foo[0];
    x = *(tmpptr + i);

This means you can't pass an entire array by value to a function, so if you have a function of the form:

void foo (char foo[])
{
    // ...
}

The compiler rewrites this into:

void foo (char *foo)
{
    // ...
}

However, there are two places where the implicit conversion from array to pointer does not happen (sizeof and addressing). In these cases, you are still dealing with an array. So for instance:

In this example, size_array would be 8 (7 characters + trailing null). The size_ptr array would be 2/4/8 depending on the machine involved (on most Arduinos it would be 2, on Arm processors like the Due, it would be 4).

static char array[] = "abcdefg";
static char *ptr = "abcdefg";
size_t size_array = sizeof (array);
size_t size_ptr = sizeof (ptr);

Now again, if you have a function declaration with an argument that is an array, it gets converted to be a pointer, and so sizeof of an array argument would return the size of a pointer, and not the array it points to. This trips up a lot of newbies who want to use sizeof to get the number of elements or the entire array size, and instead just get the size of a pointer.

I tend to feel that due to these rules, arrays are second class citizens in C/C++/etc. But that is the way Dennis Ritchie designed the C language.

In Princeton type architectures (like the arm chips used in the Due/Teensy 3.0) the compiler can (and does) put const arrays into flash memory and not SRAM. You would still need to declare the item as a **char []** and not __char *__ to get the array constants put into flash and not just the pointer. However, the compiler will tend to put string literals into flash as well.

Graynomad:

but to get it to apartment B3 you have to first send your letter to Geoff in apartment A6 who is friends with Gill in apartment B9. He will hand the letter to Gill, who can then pass it on to Arthur in apartment B3.

GILL!, How embarrassing, I've been calling him Jim.


Rob

He was Jim, up until he went for the operation. Now he's Gill.

Well... back to the original problem... :roll_eyes:

As you have noticed, I have a 2D array of strings (which in fact is a 3D array of characters...).
Now that it compiles properly, my problem is that I can't find how to access the data correctly.

With the following code :

char A[] PROGMEM = "Test 1";		// Definition of a language-dependent string
char B[] PROGMEM = "Test 2";		// Definition of a language-dependent string

const char * CONFIG_NAMES_ARR[CONFIG_NB][CONFIG_SENSNAMES_NB] PROGMEM =
	{	{ A, "1.30", "2.30", "3.30", "4.60"} ,			// Config 1
		{ B, "1.30", "2.30", "3.30", "4.30"} ,			// Config 2
		{ A, "1.25", "2.25", "3.25", "4.50"} ,			// Config 3
		{ A, "1.20", "2.20", "3.20", "4.40"} ,			// Config 4
		{ B, "1.15", "2.15", "3.15", "4.30"} ,			// Config 5
	 };

void setup() {
	Serial.begin(115200);
	Serial.println("The startup...");

	char tmp[20];
	Serial.println("-----------------------------------");
	for (int i=0;i<2;i++) {
		for (int j=0;j<2;j++) {
			Serial.print("i=");
			Serial.print(i);
			Serial.print("  j=");
			Serial.print(j);
			sprintf(tmp,"  --> %p",CONFIG_NAMES_ARR[i][j]);
			Serial.println(tmp);
			strncpy_P(tmp,(char*)pgm_read_word((CONFIG_NAMES_ARR[i][j])),sizeof(tmp));
			Serial.println(tmp);
			Serial.print("Let's try a 1D array: ");
			strncpy_P(tmp,(char*)pgm_read_word((CONFIG_NAMES_ARR[i])),sizeof(tmp));
			Serial.println(tmp);
		}
	}
	Serial.println("-----------------------------------");
}

void loop() {
}

Here is the output of the program:

The startup...
-----------------------------------
i=0  j=0  --> 0
¾Í¿???^?M?Ü????$
Let's try a 1D array: Test 1
i=0  j=1  --> 0x8080
s?? ??O?_?o?$
Let's try a 1D array: Test 1
i=1  j=0  --> 0x8080
s?? ??O?_?o?$
Let's try a 1D array: Test 2
i=1  j=1  --> 0x8080
s?? ??O?_?o?$
Let's try a 1D array: Test 2
-----------------------------------

Clearly the pointers and the output strings show that this is not the result that I expect... And the "Let's try a 1D array" line puzzles me. I thought that with a 2D array A, when accessing A[x] you would get an array of pointers to A[x][y] values?
:~

The closest I have got is to define all the contents of the 2D array outside the array as

const prog_char A[] PROGMEM = "Test 1";		// Definition of a language-dependent string
const prog_char B[] PROGMEM = "Test 2";		// Definition of a language-dependent string
//...

Then build them up into the array as such:

const prog_char * CONFIG_NAMES_ARR[5][5] PROGMEM =
	{	{ A, B, B, A, A } ,			// Config 1
		{ B, B, A, A, B } ,			// Config 2
		{ A, A, A, B, B } ,			// Config 3
		{ A, B, A, B, A } ,			// Config 4
		{ B, A, B, A, B } ,			// Config 5
	 };

Of course, using more than just A and B :wink:

Then you have to ensure you're getting the address of the array entry, not the array entry itself, which is fine with just as it points to an array, but with [j] you're getting an array slice, so you need to get the address, with
*_ <em>*&CONFIG_NAMES_ARR[i][j]*</em> _*
In full:
```
*const prog_char A[] PROGMEM = "Test 1"; // Definition of a language-dependent string
const prog_char B[] PROGMEM = "Test 2"; // Definition of a language-dependent string

const prog_char * CONFIG_NAMES_ARR[5][5] PROGMEM =
{ { A, B, B, A, A } , // Config 1
{ B, B, A, A, B } , // Config 2
{ A, A, A, B, B } , // Config 3
{ A, B, A, B, A } , // Config 4
{ B, A, B, A, B } , // Config 5
};

void setup() {
Serial.begin(115200);
Serial.println("The startup...");

char tmp[20];
Serial.println("-----------------------------------");
for (int i=0;i<2;i++) {
	for (int j=0;j<2;j++) {
		Serial.print("i=");
		Serial.print(i);
		Serial.print("  j=");
		Serial.print(j);
		sprintf(tmp,"  --> %p",&CONFIG_NAMES_ARR[i][j]);
		Serial.println(tmp);
		strncpy_P(tmp,(char*)pgm_read_word((&CONFIG_NAMES_ARR[i][j])),sizeof(tmp));
		Serial.println(tmp);
		Serial.print("Let's try a 1D array: ");
		strncpy_P(tmp,(char*)pgm_read_word((CONFIG_NAMES_ARR[i])),sizeof(tmp));
		Serial.println(tmp);
	}
}
Serial.println("-----------------------------------");

}

void loop() {
}
_
*_ _**_
*The startup...

i=0  j=0  --> 0x68
Test 1
Let's try a 1D array: Test 1
i=0  j=1  --> 0x6a
Test 2
Let's try a 1D array: Test 1
i=1  j=0  --> 0x72
Test 2
Let's try a 1D array: Test 2
i=1  j=1  --> 0x74
Test 2
Let's try a 1D array: Test 2
-----------------------------------
_
*_ _*And expanded with more sensible values:*_ _**_
*The startup...

i=0  j=0  --> 0x68
Slot A 1
Let's try a 1D array: Slot A 1
i=0  j=1  --> 0x6a
Slot A 2
Let's try a 1D array: Slot A 1
i=1  j=0  --> 0x72
Slot B 1
Let's try a 1D array: Slot B 1
i=1  j=1  --> 0x74
Slot B 2
Let's try a 1D array: Slot B 1
-----------------------------------
_
```*_

Excellent !! Thank you so much!! :wink:

I have played a bit with the example and indeed it is funny that you have to define strings outside of the array. But that will be fine for me as long as I can make this work! :grin:

PS: I didn't realize other languages were such a paradise compared to C on a micro controller!!! :stuck_out_tongue: