Am I using flash stored strings and lookups to my array efficiently and correctly?

I've cobbled together a bunch of information I've learned recently about where I can store constant strings to save memory, how I need to populate the array and how I can store them and retrieve them properly to use in Adafruit OLED/Graphics libraries. The code works, but I want to make sure I understood and handled this properly. I'm a bit worried how I use/set the global dispBuffer variable setting it in a function.

Here is my truncated code for brevity..

char dispBuffer[30];
const char dispText0[] PROGMEM = "Muting";
const char dispText1[] PROGMEM = "Up: End / Down: Edit";
//etc... 
const char* const TEXT[] PROGMEM = { dispText0, dispText1 };

void getString(byte stringID) {
	strcpy_P(dispBuffer, (char*)pgm_read_word(&(TEXT[stringID])));
}

void showScreen() {
	getString(0);
	oled.setTextSize(1);
	oled.getTextBounds(dispBuffer, 0, 0, &textX, &textY, &textWidth, &textHeight);
	oled.setCursor((SCREEN_WIDTH - textWidth) / 2, textHeight + textBottom + 5);
	oled.println(dispBuffer);
}

If that strcpy copies a string longer than 29 characters the execution will/might get in trouble. Remember the NULL character marking the end of the string.

Is it also tested code?

Yeah, I just removed a bunch of the string definitions and ancillary stuff. It is tested, it works, it solved my issue. I was running out of ram for the Adafruit SSD1306 library to load and instantiate the display, something new for me (never used a display before) and didn't realize how much memory is takes (makes sense now). Also never really came up against any memory issues with my projects because they were smaller so even though we mostly use Nanos I never had to be concerned with flash, ram or eprom usage. Let alone worry about heap and such. All my programming experience is with abstracted "languages" for web use so learning C++/Arduino has been fun.

Thanks, yeah, I was going to space align my strings like I've seen others do with padding to define the length but 30 is way more than my longest string so I'm good. It all runs. I need to play around with free mem or other utils to understand more about where my memory usage goes eventually but I was just tweaking/polishing this project when I ran out of ram for the display. Luckily there is enough info out there that I was able to figure that out and put all my other usage in flash (like debug output). This bit was a bit more challenging since I was way less familiar with char arrays and pointers combined with the requirements of the getTextBounds() and PROGMEM and F() but it's all making a bit more sense now. I just want to make sure I'm not doing anything obviously stupid.

Flash memory manages quite some writings but it's not unlimited so watch out. Storing constant strings ought to be safe.

I would change getString() a bit so it wouldn't copy the same string twice and would return the buffer pointer:

cont char * getString(byte stringID) 
{
  static char dispBuffer[30];
  static int lastID = -1;

  if (stringID != lastID)
  {
    lastID = stringID;
    strcpy_P(dispBuffer, (char*)pgm_read_ptr(&(TEXT[stringID])));
  }
  return dispBuffer;
}

That would allow you to use "getString()" in most cases where you would use a string literal:

void showScreen(byte stringID ) {
	oled.setTextSize(1);
	oled.getTextBounds(getString(stringID), 0, 0, &textX, &textY, &textWidth, &textHeight);
	oled.setCursor((SCREEN_WIDTH - textWidth) / 2, textHeight + textBottom + 5);
	oled.println(getString(stringID));
}

Ah duh, makes total sense. Thanks for the note.

Is this usage counted on a "per run" basis? So like each flash storage, each time the device is powered on it will use a flash write cycle? Because the device will turn on and off frequently (let's say daily). Aside from the main string array writing to flash I am doing this for debug:

#define DEBUG 1

#if DEBUG == 1
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif

DEBUG_PRINTLN(F("defaultScreen()"));

Of course, in production DEBUG=0 so it does not print however I assume the F() still stores that string to flash? Or maybe the compiler sees that it will never be used? I can remove all those calls if it would reduce the write cycles, I just like to leave them for future debugging if needed. There are maybe 20 of them. I guess I could do the same thing and storage a single array in flash with all those strings which I assume would reduce each individual write to a single one for the whole array.

No, it's specified as the number of writes. How, where or when they are done is up to You.

The flash only (generally) gets written when you program the sketch into the arduino, from then on you are only reading from flash. Most arduono boards do not have a mechanism for writing to flash from running code outside of the bootloader.

That's what I was assuming, cool.

Could you explain why you used the lastID the way you did? I assume it was supposed to be used more like this...

int lastID = -1;
char dispBuffer[30];

const char * getString(byte stringID = 1) {
  if (stringID != lastID) {
    lastID = stringID;
    strcpy_P(dispBuffer, (char*)pgm_read_ptr(&(TEXT[stringID])));
  }
  return dispBuffer;
}

void showScreen() {
	oled.setTextSize(1);
	oled.getTextBounds(getString(1), 0, 0, &textX, &textY, &textWidth, &textHeight);
	oled.setCursor((SCREEN_WIDTH - textWidth) / 2, textHeight + textBottom + 5);
	oled.println(getString());
}

So in subsequent calls to the function, if I pass nothing then it returns the last string and prevents the strcpy_P() from running. Otherwise, I don't know if I understand the reasoning behind it.

That block of code isn't reusable in showScreen because each position and text size has to be set and I increment the position of the text in some cases, but not all. So I deferred to just duplicating the blocks of code so I could easily control the position without having to do too much conditional code to fine tune the positioning. In some cases, I set the position statically and some cases it builds upon previous positions. Seemed easier this way for the 8 or 9 strings I output.

*Actually I guess it doesn't matter if the stringID is passed a second time or not because it will do the same thing if the ID is the same as the last or isn't passed.

lastID and buffer[] are only used in one function so there is no need to make them global. The 'static' keyword makes them statically allocated so they retain their value when out-of-scope (like a global).

Ah ok, thanks. I should read up on static more. I have never used it.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.