Conserving SRAM : Storing Display Text In Flash

Since I've had success with developing an idea to preproduction prototype stage using Arduinos, I'm giving back to the community by spamming several subfora with things I've worked out through my dev process that I know people have asked about. ("I know people asked about" because I also asked, and found posts here where others have asked before me.) I'll be posting various things in various places, so hopefully I'll get the locations right, and if not, if a mod could make moves as needed that'd be lovely.

I'm going to start off with one of the big ones that's relevant to the use of LCD/OLED displays, character displays in particular, with Arduinos: reducing SRAM usage by storing text strings for the LCD in flash memory (program space).

The first thing to wrap your head around is how Arduinos (well, Microchip/Atmel AT/ATMega series 8-bit microcontrollers) store strings. The way it's done is that the declared variable is stored in flash memory and loaded into SRAM during the startup of the program. Since a 20x4 LCD can require 80 bytes of straight character data (plus any overhead for write functions to send the data to the display), a program can burn through SRAM in a real hurry if there's a lot of text, and this can quickly lead to stack collisions, memory fragmentation, errant behavior, crashes, carnage, death and destruction, universal implosion, etc. etc. etc.

So, instead of doing the flash-to-SRAM dance, just stick the strings that won't be changing into flash space and pull out what you need when you need it.

How do we do this? Well, you've come to the right thread.

First, we need to declare the text strings and create a string table. This will need to be done from global space, so place the declares before any functions or procedures, after any relevant #includes and #defines. You can create more than one string table; I create one per "screen" of text. Here's a real-world example from working code:

const char str_bar[]   PROGMEM = "--------------------";
const char str_dbar[]  PROGMEM = "====================";
const char* const bar_table[] PROGMEM = {
  str_bar,
  str_dbar
};

In the first two lines, we're declaring two char-array "strings" (const char _var_[]), declaring they need to be stored in program memory (PROGMEM), and then declaring the actual text for later display that will get stored in the char array. (NOTE: These should be no longer than the line length for the display, or strange things may happen when the string is sent to the display.)

In line three, we create another array to hold the two char-array "strings" (const char* const _var_[] - YES, there are two "const" mentions, and YES, they both need to be there!). We also declare them as needing to be stored in program memory (PROGMEM). Then, we simply place the string vars inside the array in the order we prefer.

Using the above sample code, we now have "str_bar" as "bar_table[0]" and "str_dbar" as "bar_table [1]" and the strings are accessible through their index order in the array. Poof, string table.

Now that we have strings stored in an accessible format but in flash memory only, we have to employ special tactics to get the info back out of program memory as the usual copy/concat routines don't work on program memory as Harvard-architecture processors keep program and variable memory areas separated. However, there is a special variant of strcpy that does reach into program memory: strcpy_P.

So, here's how we can get the string back out of program memory and into a local buffer that we can then send to the LCD:

char buf[21];

strcpy_P(buf, (char*)pgm_read_word(&(bar_table[0]);

LCD.write(buf);

We start by declaring a local char array to use as a buffer. This must be one byte larger than the largest string it will receive, which should logically be the length of a line on the LCD in use, so a 20x4 LCD will need a 21-element array of chars. This is required because C strings end with a null (0x00) so this must be accommodated.

We use pgm_read_word to get the pointer to the string we want to fetch, pass that as a pointer to strcpy_P, and that pulls the string out of the program space and shoves it into the local char-array buffer.

Once it's in the buffer it can be manipulated as required, e.g. sent to the LCD. The buffer can also be reused, which is where the memory savings over time will come into play. For example, to write text to all four lines in a 20x4 LCD, read one string out of program memory, write it to the LCD, then read the second and write that to the LCD, then the third line, then the fourth. Instead of burning up to 80 bytes of SRAM just to hold the text for a 20x4 LCD, this approach limits that to 21 bytes, the size of the buffer. In a function that spams loads of text to a display, the savings can become enormous - I puled five kilobytes out of SRAM and dropped into flash on my Arduino Mega based project, which made all sorts of difference in how well it ran.

The only caveat to watch for with this method is that it's slightly slower than storing text in SRAM, as strcpy_P likes to use an extra clock cycle per byte copied. As long as the code isn't timing-critical (and to be fair, if you're writing something that requires accurate enough timing that this is an issue, you're probably not going to want to use an Arduino for it) this shouldn't pose any obvious problems.

Oh, before I forget, the above is basically "how to create and access program-memory string tables on Arduinos" in a general sense, and can be expanded to other applications. It's super-useful for character displays, though, thus its posting in this subforum. Have any thoughts on how to store other data types in program memory, and fetch the values later? Post 'em below!