Arduino documentation says, it is possible to keep constants like strings or whatever I don't want to change during runtime in program memory.
All constants are initially in program memory. Where else would they be when the power is off?
I think of it as embedded somewhere in the code segment, which must be fairly possible inside a von-Neumann-architecture.
It's actually Harvard architecture.
Why on earth do I have to copy the damn content to RAM before accessing it?
You don't. In fact there is a hardware instruction (LPM - Load Program Memory) which moves data directly from program memory into a register.
I have an example of this technique in Arduino Uno output to VGA monitor. In that code there is a bitmapped font stored in program memory. It is read from that on-the-fly and copied to the output like this:
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
A disassembly of those lines shows (in part):
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
You can see that a byte of program memory was copied into R30, and then immediately stored into the USART register UDR0. No RAM involved.
However there is a complexity. For normal strings the compiler expects to find data in RAM not PROGMEM. They are different address spaces, and therefore 0x200 in RAM is something different from 0x200 in PROGMEM. Thus the compiler goes to the trouble of copying constants (like strings) into RAM at program startup, so it doesn't have to worry about knowing the difference later on.
How is the code (32kiB) handled then with only 2kiB RAM?
Good question. You won't get away with having more than 2 KB of constant strings, because there won't be room to copy them all.
That's why people who are writing things like menus and other wordy stuff, take extra steps to give the strings the PROGMEM attribute, which disables them being copied into RAM.
But I'm bewildered by those instructions to just read and print data from program memory:
If you add the PROGMEM attribute you have to take steps to let the compiler know that these strings are in a different address space. Making a complete (temporary) copy is one way. Or just print directly from PROGMEM, a byte at a time. An example of that is:
// Print a string from Program Memory directly to save RAM
void printProgStr (const char * str)
{
char c;
if (!str)
return;
while ((c = pgm_read_byte(str++)))
Serial.print (c);
} // end of printProgStr
If you pass this function a pointer to a string in PROGMEM, it does the "special read" (pgm_read_byte) to pull the data from PROGMEM rather than RAM, and the prints it. Note that this takes one additional clock cycle, per byte.
And even more interesting: What happens to literal constants like in this expression a = 5*(10+7)
are 5, 10 and 7 really copied to RAM before loading them into registers? I just can't believe that.
No, because they don't have to be. That would compile into a "load literal into register" instruction. That instruction is already in PROGMEM, so the literal is now dealt with. No need to copy it to RAM and then read it back.
I have a lengthy description of these things on the page Putting constant data into program memory (PROGMEM). That has example code for setting up strings, and arrays of strings, reasonably easily.
It also mentions the F() macro which is an easy way of simply printing from PROGMEM:
Serial.println (F("Hello, world"));
A bit of preprocessor complexity lets that compile into a helper function which pulls the bytes in the string from PROGMEM a byte at a time. No intermediate use of RAM is required.
It is easy enough to use that technique for things other than Serial (eg. your LCD) by deriving the LCD printing from the Print class.
As an example, in one of the LCD libraries I wrote, I did exactly that:
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
The key point here is to derive from Print, and override the "write" function. Now your overridden function does whatever it needs to output a character. Since it is derived from Print you can now use the F() macro. eg.
lcd.println (F("Hello, world"));
string_table
array. That array might be 20KB, and would never fit in memory (even temporarily). You can however load just one index using the above method.