It’s 3am but you are not going to bed until you squash this last bug. You sprinkle Serial.print() statements everywhere you can think of, and then that’s when all hell breaks loose: Your code randomly locks up, the LEDs go crazy, and you’ve had it. What’s going on? You’ve run out of RAM!
All of those Serial.print() statements are composed of c-style strings. Which are “constant character arrays.” In order for those arrays to work properly, they get loaded into the ATmega’s RAM before your code starts running. Which can be a problem because 2,048 bytes of RAM doesn’t allow for much.
Introducing the F() Macro
Search the entire Arduino Reference Page, and you won’t find a single mention of the F() macro. Which is unfortunate because it is one of the most powerful functions, which was added with the 1.0 release of the IDE. I keep mixing the terms “macro” and “function.” F() really isn’t a function, it is a #define macro which lives in WString.h
WString.h:#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
That long string of code tells the compiler to keep a string inside of PROGMEM and not allow it to consume RAM.
Using the F() Macro
Here’s an example of how you would use the F() macro with Serial.print() or Lcd.print().
That’s all there is to it. Simply wrap your string (const character array) with F().
Why is the F() Macro Needed?
Remember that the Arduino Uno (and it’s cousins) are based on the ATmega328. This microcontroller only offers 2,048 bytes of RAM. 2k, that’s it. Even the Uno’s big brother, the Mega2560, only has 8K of RAM.
What about using the “const” keyword?
From the #define vs const post
, the const keyword will tell a compiler that a variable is a constant and can’t change. Depending on the optimizations used, the avr-gcc compiler will avoid putting that value into RAM since it knows it’ll never change. But, that technique won’t work with c-style strings or,well, arrays. Since arrays are based around pointers, the compiler needs to put the array into RAM so that pointers work correctly. Which means, all strings need to be put into RAM before they can be used.
What the F() macro Does
The F() macro tells the compiler to leave this particular array in PROGMEM. Then when it is time to access it, one byte of the data is copied to RAM at a time. There’s a small performance overhead for this extra work. However, printing strings over Serial or to a LCD is a really slow process, so a few extra clock cycles really won’t matter.
Tradeoffs to Consider
#1: Can’t Change Data
The key tradeoff to using the F() Macro is that you can’t use it for data you want to change. Anything stored in PROGMEM cannot be changed by the running program. In fact, you would never want that. The only code that can change PROGMEM is code stored in the boot partition, which is where the bootloader lives.
#2: Only works on Strings
Another tradeoff is that this macro only works for strings. While it is useful to use PROGMEM to store stuff like bit-patterns for characters, this macro isn’t going to help with that. If that’s the kind of thing you are trying to store, then you’ll need to use the traditional PROGMEM statements to do so. Which makes this reference on PROGMEM from the Arduino.cc site
a great read.
#3: Not good for big blocks of text (like HTML)
Storing HTML pages is another example where you probably want to keep the bulk of the text in PROGMEM. Unless that HTML is wrapped around Serial.print() or Client.print(), you aren’t going to be able to use the F() macro.
There is no optimization of memory usage with the F() macro. If you use the same string over and over in your code, each instance will consume some PROGMEM. If you’re running short on PROGMEM, you might have to consider not using the F() macro .
Don’t wait until your code starts acting weird. Whenever you use strings in a print() method, get into the habit of wrapping the string with a F() macro. This way you don’t have to worry about RAM getting wasted on something that can’t be changed anyway.