Sunday, 23 April 2017

Save some Arduino RAM when using strings with the F macro

Anyone who has ever written out debug messages to themselves while developing on Arduino will know, add too many and all your mcu RAM gets chewed up pretty quickly.

In "production" code, it's quite common to flash an LED to indicate what's going on, but that gets pretty tedious to debug when you're making lots of changes to your code as you develop, so it becomes common to little complex routines will little Serial.Println statements, to show where in the logic control you're up to.

A you might write something like

while(something){
  Serial.println("Here's what's going on");
}

after a few of these (ok, maybe like a couple of dozen or more) you'll find your RAM usage creeping up. Debugging code that uses wasteful libraries means either re-writing someone else's code (negating the benefits of a library-based development system) or reducing the message length (until you're doing little more than an alpha-numeric equivalent to flashing an LED).

More experienced users might write something like

while(something){
  Serial.println(F("Here's what's going on"));
}


At first the difference is difficult to spot. But that all important F macro (which isn't particularly well documented in Arduino help files) makes a massive difference. What that does is write your string message to program ROM rather than fill your RAM with pointers to character arrays that the Arduino string class uses.

Replace all instances of "my string" with F("my string") and you'll find your RAM usage plummets (while your program ROM size increases by roughly the same amount as you've saved with RAM).

We recently played about with the excellent nokia5110-compatible LCD library and built a rotary-encoder based menu system (for the light-up guitar I promised Keith). There are lots of strings of text used, and - sure enough - after coding a few menus, our RAM usage was on the up



While it seems trivial to add the F macro jut before each of our strings, in this particular case, it wouldn't actually work. See, our LcdString function accepts not an Arduino-type string object, but a pointer to a character array.

So if we tried to write LcdString(F(" string "));
it simply wouldn't work (the compile returns a data type mismatch error.

The answer is a quick-and-dirty function into which we can pass a string and return a character array, which we can then pass into our LcdString function.


Now we can write our string calls using the F-macro (to push the strings into program ROM space and free up RAM) but still pass them as character arrays into the functions that prefer character arrays over the Arduino string class.


In our menu test, we managed to conserve over 360 bytes just by implementing the F-macro using a string2char function. Given the atmega328 has 2kb of RAM but a massive 32kB of ROM, wherever possible we try to push our strings into ROM.

We managed to reduce our RAM usage by over a third (34%) for a modest 2% increase in program space. Given there's more to this project than just the menu system, we'd take any chance to reclaim back over 17% of the total available RAM for use in the rest of the program!

So next time you're getting close to using up all your RAM, find all those little debug messages and wrap them in the F-macro. And if you're passing strings into other functions, you can still use it and simply pass your F-strings into the string2char function if the function prefers a character array.