Create a Macro or Template to Make Things More Readable

Hi All.

I have an application where text strings could be stored in Flash (“static const char string1 PROGMEM = ….”), as a constant in RAM (“static const char string2 = ….”), or in a RAM “char” buffer that’s loaded dynamically at runtime.

Issue is, the function that prints these strings only gets a pointer to them. So, it doesn’t know at compile-time which overloaded version of Serial.print (or any Stream object) to use. My idea was to put the pointer to the string as well as an indication of the type being pointed to into a struct. I’d then pass a pointer to the stuct to the printing routing.

Here’s a simplified sample code that works:

enum stringType {
  FLASH, RAM
};

struct Record {
  stringType type;
  void *stringPtr;
};

void printRecord(Record *);

static const char string1[] PROGMEM = "This is a string in Flash";
static const char string2[] = "This is a string in RAM";
static char buffer[40];
static Record records[] = { { FLASH, (void *) string1 },
  { RAM, (void *) string2 }, {
    RAM, (void *) buffer
  }
};
const uint8_t numRecords = sizeof(records) / sizeof(Record);

void setup() {
  uint8_t index;
  Serial.begin(115200);
  delay(1000);
  strcpy(buffer, "This is a string from a buffer");
  for (index = 0; index < numRecords; index++) {
    printRecord(records + index);
  }
}

void loop()
{
}

void printRecord(Record *r) {
  if (r->type == RAM) {
    Serial.println((char *) r->stringPtr);
  } else {
    Serial.println((__FlashStringHelper *) r->stringPtr);
  }
}

Now I’d like to make the definitions more compact and readable. I’m wondering if there’s a way to do this with function macros or — neither of which I’m very skilled at. I’m imagining three different macros like maybe:

FLASH_STRING_RECORD
RAM_STRING_RECORD
RAM_BUFFER_RECORD

Then, the definition of my records array might look like this:

static Record records[] =
{ {FLASH_STRING_RECORD("This is a string in Flash")},
  {RAM_STRING_RECORD("This is a string in RAM")},
  {RAM_BUFFER_RECORD(buffer}
};

Is such a thing possible?

Thanks!

But it seems like you try to reinven the wheel... If you just use the __FlashStringHelper type for PROGMEM strings the compiler know what to do. And instead of a single function that excepts just char pointers you make an overloaded function which accepts __FlashStringHelper pointers and make it do the same thing only with strings from PROGMEM.

But, how do I do that when each element of my records[] array can contain either a RAM string or a Flash string? Also, my real application has many of these records[] arrays each with a different mix of Flash and RAM strings. All the printing function gets is a pointer to the record to print. The compiler has no way of knowing at compile-time which overloaded function to call. That’s why I included the type tag in the stuct.

gfvalvo:
But, how do I do that when each element of my records array can contain either a RAM string or a Flash string?

You have to create more than one version of the structure, each supporting a different data type, and all inheriting from a common base class. When you process the data, you have to figure out, on-the-fly, the exact type of each element, and handle it accordingly. This can be done largely transparently using virtual member functions.
Regards,
Ray L.

gfvalvo:
Issue is, the function that prints these strings only gets a pointer to them. So, it doesn’t know at compile-time which overloaded version of Serial.print (or any Stream object) to use.

Maybe I don’t understand your issue?

const char* const text PROGMEM = "Some Text";

void setup() 
{
  Serial.begin(9600);
  printThingy(Serial, "HardCoded");
  char myBuffer[] = "Hello World";
  printThingy(Serial, myBuffer);
  printThingy(Serial, text);
}

void loop() {
  // put your main code here, to run repeatedly:

}

void printThingy(Stream& stream, const char* text){
  stream.println(text);
}

output:

HardCoded
Hello World
Some Text

Thanks, I'll play with these a little. Perhaps I'm solving a problem that doesn't exist.

OK, now I’m puzzled. This modified version of @BulldogLowell’s code definitely works:

const char* const text PROGMEM = "Some Text";
char myBuffer[] = "Hello World";

char *thingyArray[] = { (char *) text, myBuffer };
void printThingy(Stream&, const char*);

void setup()
{
 Serial.begin(115200);
 for (uint8_t index = 0; index < 2; index++) {
 printThingy(Serial, thingyArray[index]);
 }
}

void loop() {
}

void printThingy(Stream& stream, const char* text) {
 stream.println(text);
}

But, I don’t understand how. Meaning, how does the correct overloaded method of ‘stream.println()’ get called to pull the string out of Flash?

Also, the below code does NOT correctly print the string from flash:

const char text[] PROGMEM = "Some Text";
char myBuffer[] = "Hello World";

char *thingyArray[] = { (char *) text, myBuffer };
void printThingy(Stream&, const char*);

void setup()
{
 Serial.begin(115200);
 for (uint8_t index = 0; index < 2; index++) {
 printThingy(Serial, thingyArray[index]);
 }
}

void loop() {
}

void printThingy(Stream& stream, const char* text) {
 stream.println(text);
}

The only difference between the two codes is the definition of the ‘text’ variable. So, that’s the second thing I’m puzzled about. What’s the difference between these two definitions?

const char* const text PROGMEM = "Some Text";

and

const char text[] PROGMEM = "Some Text";

Thanks again.

BTW, I'm testing this on an Uno-compatible board. Namely, this one because I have it handy: https://www.sparkfun.com/products/13261 So, definitely an AVR architecture.

But mixing PROGMEM and non-PROGMEM (even non-const) strings in he same array seems to me like the real cause of confusion. In the first place because you fool the compiler by casting const char pointers into char pointers. But it's not very transparent from the programming point of view. Would be the same as mixing floats, int's and bytes pointers in the same array...

Confusing perhaps. But, I believe I have a good reason. The structs represent options in my Menu system. Each struct is one option in the menu and an array of structs represents the entire Menu page.

Each struct contains a char * that points to the text for that particular menu item. It also contains a token value that’s returned to identify the menu option chosen. Finally, the struct also has a pointer to a callback function to be executed when the option is selected.

Many times, the menu item text is static. So, it makes sense to point to a char array in Flash. But, sometimes, I want the displayed text to be dynamic. For example, when I select the menu option to adjust a temperature setpoint. I do this by descending down another layer to a new menu page. This new page displays the current setting and it then changes dynamically as the user hits the Up / Down adjustment buttons. Obviously, I need to be able to change this menu item’s display text. I do that by having the char * in the menu item’s struct point to a buffer in RAM. The RAM buffer gets loaded with the current set point when the item is first displayed and gets updated as the user selects Up / Down.

Another use for dynamic menu item text would be a confirmation page that flashes up when the use changes a setting via the menu.

So, either static or dynamic, my menu library consistently treats all menu entries as the same “thing”.

I’ve already gotten this menu library to work like I want to using only char * pointers to strings and buffers in RAM. My first project to use it is on an ESP8266 platform with generous (relatively speaking) RAM. But, should I want to use the library on a more resource-limited processor, I’ll probably need to move the static menu strings to Flash.

Anyway, I have a few ideas for implementing these dual pointer types. I’ll report back with progress.

In my opinion still not valid. You're mixing menu text and menu variables. Just split it. In a menu construction of mine each menu item has room for text (pointer to PROGMEM), callback (function pointer) and a variable (pointer). Or even, because the different nature of the variables, I use a callback for the variable as well which formats it in the correct way.

No mixing of variables and const and non const :)

septillion:
In my opinion still not valid.

Opinion noted.