What's the difference between PROGMEM and F()?

Following an online tutorial, I was told to save my Strings to program memory like this:

const char prgrm1[] PROGMEM = "String1";
const char prgrm2[] PROGMEM = "String2";
const char prgrm3[] PROGMEM = "String3";

const char *const myStrings[] PROGMEM = {
  prgrm1, prgrm2, prgrm3
};

But where is the difference to just doing it like this?:

String myStrings[] = {
 F("String1"), F("String2"), F("String3")
};

Apart from not being able to put it outside of a function, both seem to work, so why bother with the PROGMEM stuff?

I don't think they are quite the same thing.

In your first example, the array myStrings is in PROGMEM (ie, not in RAM) as pointers to char arrays also in PROGMEM. In your second example, the myStrings pointer array is in RAM, pointing to String data in PROGMEM.
I don't really understand how Strings can be in PROGMEM as they use allocated memory (RAM) in order to store their data. Someone else may have the answer for this.

Whether this extra memory used matters depends on the application.

Personally I avoid the use of Strings as they can be unreliable in a low memory environment (which is why we use PROGMEM for static data) in the first place.

Look at the definition (for an AVR processor anyway) of the 'F' macro in WString.h:

#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))

And the 'PSTR' macro in pgmspace.h:

# define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))

So, you see, the 'F' macro uses PROGMEM and ensures that the string literal is placed in Flash and it returns a pointer to an object of the _FlashStringHelper class. This is a special class that all classes that inherit from the Print class know how to print. So, you don't have to use the special 'pgm_read...' functions to access them. You just call a print-type function:

Serial.println(F("Hello"));

Your first example also puts the string literals in Flash. But, then you make an array of pointers to them which is also in flash. So, you'll need to use the 'pgm_read_...' functions yourself (twice) to access your strings.

Your second example is nonsense.

You could do something like:

void setup() {
  Serial.begin(115200);
  delay(1000);

  const __FlashStringHelper * myStrings[] = { F("String1"), F("String2"), F(
      "String3")
                                            };
  const uint8_t numStrings = sizeof(myStrings) / sizeof(myStrings[0]);

  for (uint8_t i = 0; i < numStrings; i++) {
    Serial.println(myStrings[i]);
  }
}

void loop() {
}

But, as you noted, the F macro can only be used inside of a function.

1. Within ATmega328P MCU of UNO Board, we have:
32 Kbyte of Code Memory/Flash Memory/Program Memory
2 Kbyte of RAM Memory

2. The following declaration saves the string "Arduino" within RAM Memory:

char myArray[] = "Arduino";
Serial.print(myArray);  //shows: Arduino

3. The following declaration saves the string "Arduino" in the Program Memory. Here, the keyword PROGMEM is an important factor, which performs the task of saving the string in the read-only Program Memory.

const char myArray[] PROGMEM = "Arduino"; 
Serial.print(myArray); //shows: Arduino

4. The following declaration first saves the string variable in Program Memory and then reads it from the Program Memory and then shows it on Serial Monitor.

Serial.print(F("Arduino"));   //shows: Arduino

5. Questions:
(a) What is the advantage of keeping variables in Program Memory other than RAM?
(b) What does F stand for -- Flash Memory?
(c) Write multi-line codes for the following single line code:

Serial.print(F("Arduino"));   //shows: Arduino

6. Note: People say that F(arg) is a macro. If it is a macro then its 'multi line codes' will be added in line with the main line codes every time we call its name. What are these lines?

(b) What does F stand for -- Flash Memory?

according to the Reference:

The F() macro
When an instruction like :

Serial.print("Write something on the Serial Monitor");
is used, the string to be printed is normally saved in RAM. If your sketch prints a lot of stuff on the Serial Monitor, you can easily fill the RAM. If you have free FLASH memory space, you can easily indicate that the string must be saved in FLASH using the syntax:

Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));

source: PROGMEM - Arduino Reference

GolamMostafa:
6. Note: People say that F(arg) is a macro. If it is a macro then its 'multi line codes' will be added in line with the main line codes every time we call its name. What are these lines?

Go back and read the first part of Reply #2.

GolamMostafa:
3. The following declaration saves the string "Arduino" in the Program Memory. Here, the keyword PROGMEM is an important factor, which performs the task of saving the string in the read-only Program Memory.

const char myArray[] PROGMEM = "Arduino"; 

Serial.print(myArray); //shows: Arduino

You should always test things before teaching.

const char myArray[] PROGMEM = "Arduino";

void setup() {
  Serial.begin(250000);
  Serial.print(F("'"));
  Serial.print(myArray);
  Serial.print(F("'\n'"));
  Serial.print((__FlashStringHelper*)myArray);
  Serial.println(F("'"));
}
void loop() {}
'   '
'Arduino'
1 Like

Whandall:
You should always test things before teaching.

void setup()
{
  Serial.begin(9600);
  const char myArray[] PROGMEM = "Arduino";
  Serial.print(myArray); //shows: Arduino  // put your setup code here, to run once:
}

void loop() 
{
  
}

sm101x.png

Am I here for teaching or for sharing?

sm101x.png

1 Like

GolamMostafa:

void setup()

{
 Serial.begin(9600);
 const char myArray[] PROGMEM = "Arduino";
 Serial.print(myArray); //shows: Arduino  // put your setup code here, to run once:
}

void loop()
{
 
}

Relying on compiler optimization is not a good idea, writing correct code seems better to me.

GolamMostafa:
Am I here for teaching or for sharing?

The code you shared "works" only if you're lucky and the code is very simple. I'd rather be good (i.e. know what I'm doing) than lucky.

The compiler has corrected your mistake though optimization. As @Whandall showed, that won't happen if the code is even a little more complicated.

gfvalvo:
The compiler has corrected your mistake though optimization.

On the AVR processor the SRAM, FLASH, and EEPROM are in separate address spaces. Instructions are fetched from FLASH (a.k.a. Program Memory or PROGMEM). Data is read from and written to SRAM. You have to use special machine instructions for reading FLASH or reading and writing EEPROM. Any initialized variables will be initialized by copying the data from FLASH to the variable's location in SRAM before the sketch starts. This is great for initialized variables that will be modified by the sketch but for constant (read-only) variables it is a waste of precious SRAM space. For larger initialized read-only variables, such as lookup tables, it is good to tell the compiler to keep the data in FLASH.

Unfortunately the compiler will only keep track of the address of the variable, not which address space it is in. It won't remember that your variable is in FLASH and if you try to use it like you would any other variable it will use that FLASH address to fetch data from SRAM and get the wrong data. It is up to you to add the special function calls to fetch data from FLASH whenever you want to fetch data from that variable.

String literals are a special case of read-only data. They are treated like initialized read-only variables and their space in SRAM is initialized by copying the data from FLASH. Because they are often used for text output to Serial, LCD, Ethernet, WiFi, etc. there is a special trick used to make keeping them in FLASH easier.

There is a macro named "F" in the standard include file Wstring.h:
Code: [Select]
class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))

The F() macro uses the macro PSTR() to tell the compiler to keep the string in FLASH and then changes the value type from 'char *' (character pointer) to a '__FlashStringHelper *'. The class __FlashStringHelper has no body, just a type. The "reinterpret_cast" tells the compiler that you know that the value being cast is not compatible with the destination type. Unlike a regular cast, no conversion is done. The ONLY safe operation is to cast the value BACK to what it was before.

In the Print class there are .print() and .println() methods similar to the 'char *' methods that accept a '__FlashStringHelper *' instead. The compiler chooses those methods when you pass the '__FlashStringHelper *' created by the F() macro. The methods then cast the __FlashStringHelper pointer BACk to a character pointer and fetch each character of the string from FLASH. Serial, LCD, Ethernet, WiFi, etc are all objects that inherit behavior from the Print class so their .print() and .println() methods can all accept the '__FlashStringHelper *' type and fetch the string from the FLASH address space.

If you write your own function and one of the arguments is often a fairly large string literal you might want to make a version of the function that accepts the '__FlashStringHelper *' type. Then you can use the F() macro to keep those string literals in FLASH. See Print.cpp in the Arduino core sources for examples of how you would treat the argument differently.

@GolamMostafa there is no need to post that, we know it already.

Your code snippet #3 is wrong (in general) and should not be presented as an example.

Whandall:
@GolamMostafa there is no need to post that, we know it already.

Your code snippet #3 is wrong (in general) and should not be presented as an example.

How do I know that you know it because you have not explained why my code works though it has not been written in a better way -- the Compiler has taken care of it.

Because, the string is in the flash, the value must be retrieved from flash -- but it may not always be true due to reasons being explained in the quoted excerpt which I have known now.

How many of us make things correct at the very first attempt?

1 Like

GolamMostafa:
How do I know that you know it because you have not explained why my code works though it has not been written in a better way -- the Compiler has taken care of it.

Whandall:
Relying on compiler optimization is not a good idea, writing correct code seems better to me.

That's what I told you. What makes it so hard to admit your error?

void setup()
{
  Serial.begin(9600);
  const char myArray[] PROGMEM = "Arduino";
  Serial.print(myArray); //shows: Arduino  // put your setup code here, to run once:
}

PROGMEM has no effect on non-static local variables.

1 Like