Pages: [1] 2 3   Go Down
Author Topic: What does the F() do exactly?  (Read 6773 times)
0 Members and 1 Guest are viewing this topic.
Central MN, USA
Offline Offline
Tesla Member
***
Karma: 74
Posts: 7234
Phi_prompt, phi_interfaces, phi-2 shields, phi-panels
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I am trying to understand exactly what F() does when its used in something like Serial.print(F("Hello from PROGMEM"));

Here is all I could find in WString.h

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

Found this in pgmspace.h

# define PSTR(s) ((const PROGMEM char *)(s))

There is no other things besides a simple class name and this macro.

Can someone explain to me the syntax in the F() macro please? My OOP is not strong enough to understand this line smiley-confuse

BTW, realized the lib author was using doxgen smiley I can use the code to learn how to use doxgen smiley
Logged


Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 220
Posts: 13836
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


reinterpret_cast is a cast that can convert any type into any pointer type.
In this case it takes care that the PSTR() "pstring" is converted to a pointer to __FlashStringHelper * class.

Most important in practice is that this construction prevents the compiler from throwing all kinds of warnings/errors.


Logged

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 209
Posts: 13015
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Found this in pgmspace.h
# define PSTR(s) ((const PROGMEM char *)(s))

That is the definition used in the documentation.  The definition used when compiling is a few lines below...

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

PSTR works somewhat like a function.  Storage (an array of characters named __c) for the string constant is created in Flash (PROGMEM tells the compiler and linker to place __c in Flash).  The address of the storage (of __c) is "returned".

From Print.h...

Code:
class Print
{
...
    size_t print(const __FlashStringHelper *);
...
    size_t print(const char[]);

Code:
class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<__FlashStringHelper *>(PSTR(string_literal)))

PSTR returns a char*.  If we do nothing special, print(const char[]) would be called which is not what we what.  print(const char[]) does not know how to print strings stored in Flash.

By type-casting, we force the compiler to use print(const __FlashStringHelper *) which does know how to print strings stored in Flash.
Logged

USA
Offline Offline
Full Member
***
Karma: 0
Posts: 238
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Reading data (ex. a string) from FLASH, requires the use of some functions.  The AVR is a Harvard architecture.  Code and Data are stored separately.  The following statement will compile but will not print the string.  The reason is because the member function print is passed an argument of type char* which is the base address of the string that's stored in FLASH.  The problem is that de-referencing a char* returns the char stored in the Data space (RAM) and not from the Code space [FLASH].  The F() macro changes the type from char* to __FlashStringHelper*.  Now, that the argument is a different type, one can create functions that accept that type and called the correct functions to retrieve the data from Code space.  The member functions print and println from the class Serial have these overloads.

This does not work.
Code:
Serial.print(PSTR("Hello, World"));

This works.
Code:
Serial.print(F("Hello, World"));
Logged

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 212
Posts: 8933
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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, which is what the print function does.
Logged

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Adelaide, South Australia
Offline Offline
Full Member
***
Karma: 0
Posts: 144
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Coould this F() syntax be used in place of rather cumbersome:

    strcpy_P(buffer, (char*)pgm_read_word(&(str_table[index])));

Could any of these work for example

    Serial.print(F(str_table(2)));
    or
    Serial.print(F(str_2));

(where
    str_2 prog_char str_2[]  PROGMEM = "Hello World"smiley-wink
Logged

0
Offline Offline
God Member
*****
Karma: 2
Posts: 596
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This thread is the best explanation of the F() macro I found so far... Should be made sticky or turned into some official doc page...
Logged

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 212
Posts: 8933
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Coould this F() syntax be used in place of rather cumbersome:

    strcpy_P(buffer, (char*)pgm_read_word(&(str_table[index])));

I don't think so because the argument to F() has to be a string constant.
What I think you CAN do is:

   Serial.print(reinterpret_cast<__FlashStringHelper *>(str_table[index]));

Passing  __FlashStringHelper * to .print or .println tells it that the argument is a pointer to a character string in FLASH.
Logged

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 209
Posts: 13015
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Coould this F() syntax be used in place of rather cumbersome:

    strcpy_P(buffer, (char*)pgm_read_word(&(str_table[index])));

This may help...
http://arduiniana.org/libraries/flash/
Logged

Central MN, USA
Offline Offline
Tesla Member
***
Karma: 74
Posts: 7234
Phi_prompt, phi_interfaces, phi-2 shields, phi-panels
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

This thread is the best explanation of the F() macro I found so far... Should be made sticky or turned into some official doc page...

I second that! Thanks so much everyone! So it comes down to:

ATMEGA328P uses Harvard architecture thus separates data and code memory space into SRAM and PROGMEM (AKA FLASH). Accessing data in data memory requires nothing special. Accessing data in code memory requires special functions such as pgm_read_byte and pgm_read_word. String pointers to address in SRAM (data memory) are proper for print but string pointers to PROGMEM (code memory) are not proper for print because the pointer points to code memory. A special print version that handles code memory pointers with pgm_read_byte or pgm_read_word needs to be called. So the F() tells the compiler, through a complex but understandable ( smiley-sweat smiley-wink) way, that the string is to be stored only in PROGMEM (code memory) to save SRAM (data memory) and the special print that handles code space pointer is to be called.

I'll continue to work on this explanation for a typical arduino user to understand. I for one, have finally understood it (minus how to tell compiler to only store a string in PROGMEM and whether the compiler optimizes duplicates) thanks to the dozen replies!
Logged


Adelaide, South Australia
Offline Offline
Full Member
***
Karma: 0
Posts: 144
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

as one who's been a light weight dabbler in code over the years, but never C or C++ until recently, I am constantly amazed at the depth of the C language, how compact yet powerful it is, and how much I still have to learn when I see a command like this (which I understand what it does in principle, but not all the components):

Serial.print(reinterpret_cast<__FlashStringHelper *>(str_table[index]));

does C need, or interpret variables with leading underscores, like ___THIS, or is this just a software weenie posey naming convention ?
« Last Edit: February 11, 2012, 05:55:36 am by ninja2 » Logged

Leighton Buzzard, UK
Offline Offline
Edison Member
*
Karma: 21
Posts: 1339
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I believe it's just a technique to avoid using "words" that people might already have in their program
who in their right mind would use variables like __fred?!? smiley
Logged

there are only 10 types of people
them that understands binary
and them that doesn't

Adelaide, South Australia
Offline Offline
Full Member
***
Karma: 0
Posts: 144
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

ahah, that makes sense.

although ... the technique seems to be used by lots of C coders in lots of programs, which theoretically undermines their objective  for using it smiley-small
Logged

0
Offline Offline
Faraday Member
**
Karma: 24
Posts: 3495
20 LEDs are enough
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

If you want to learn if the compiler optimizes duplicates you could just create a sketch that contains duplicates. Then use avr-objdump in order to look into the .elf file and see if the output contains the duplicates.
Logged

Check out my experiments http://blog.blinkenlight.net

Leighton Buzzard, UK
Offline Offline
Edison Member
*
Karma: 21
Posts: 1339
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

If you want to learn if the compiler optimizes duplicates you could just create a sketch that contains duplicates. Then use avr-objdump in order to look into the .elf file and see if the output contains the duplicates.

or create a sketch with duplicates
then make a one character change
if the sketch stays the same size you had duplicates
if it increases by the length of the string - then it was being optimised

Code:
void setup(void)
{
  Serial.begin(19200);
}
void loop(void)
{
  Serial.println(F("abcdefg"));
  Serial.println(F("abcdefg"));
}
and
Code:
void setup(void)
{
  Serial.begin(19200);
}
void loop(void)
{
  Serial.println(F("abcdefg"));
  Serial.println(F("xbcdefg"));
}
both compiled to 2040 bytes
so it looks like no optimisation
Logged

there are only 10 types of people
them that understands binary
and them that doesn't

Pages: [1] 2 3   Go Up
Jump to: