user1522973
user1522973

Reputation:

Clean and tidy string tables in PROGMEM in AVR-GCC

I'm looking for a way to cleanly define an array of strings in PROGMEM for an AVR project. I have a command line processor that needs a list of command strings.

The traditional way to do it on the AVR architecture is to define each string separately, then an array of pointers to those strings. This is extremely verbose and ugly:

typedef struct 
{
    PGM_P   str;        // pointer to command string
    uint8_t str_len;    // length of command string
    uint8_t id;         // CLI_COM_* ID number
} CLI_COMMAND_t;

const char CLI_STR_TEMP[]   PROGMEM = "TEMP";
const char CLI_STR_POWER[]  PROGMEM = "POWER";
...

const CLI_COMMAND_t cli_cmd_table[] = { { CLI_STR_TEMP,     sizeof(CLI_STR_TEMP),   CLI_COM_TEMP },
                                        { CLI_STR_POWER,    sizeof(CLI_STR_POWER),  CLI_COM_POWER },
                                        ...
                                      };

(CLI_COM_* are enum'ed indicies, but could be replaced by function pointers or something)

This mess could be reduced using macros to define the strings and build the table, something like:

#define FLASH_STRING(NAME...)    const char CLI_STR_ ## NAME [] PORGMEM = #NAME;
#define FSTR(NAME...)            { CLI_STR_ ## NAME, sizeof(CLI_STR_ ## NAME), CLI_COM_ ## NAME) }

FLASH_STRING(TEMP);
FLASH_STRING(POWER);

CLI_COMMAND_t cli_cmd_table[] = { FSTR(TEMP), FSTR(POWER) };

(untested, btw, but should be fine)

However, I would like to define all my strings only once and have a macro generate both the individual strings and the array of pointers/sizes/enum references.

On the Arduino platform there is a FLASH_STRING_ARRAY macro which I can't quite figure out, but which doesn't seem to compile either. You can see it here: http://pastebin.com/pMiV5CMr Maybe it's C++ only or something. Also it seems like it can only be used inside a function, not globally.

String tables on AVR have long been a pain and inelegant. Short of writing a little program to generate the necessary code it would be nice to have a way to define it with macros.

Bonus points: Generate the CLI_COM_* constants with the same macro, either as an enum or with #defines.

EDIT: I suppose another name for this would be iterative declaration via a macro.

SOLUTION: Thanks to luser, I came up with this solution:

typedef struct 
{
    PGM_P   str;        // pointer to command string
    uint8_t str_len;    // length of command string
    uint8_t id;         // CLI_COM_* ID number
} CLI_COMMAND_LUT_t;



#define COMMAND_TABLE \
        ENTRY(testA) \
        ENTRY(testB) \
        ENTRY(testC)

enum {
#define ENTRY(a) CLI_COM_ ## a,
    COMMAND_TABLE
#undef ENTRY    
};


#define ENTRY(a)    const char CLI_STR_ ## a PROGMEM = #a;
COMMAND_TABLE
#undef ENTRY


CLI_COMMAND_LUT_t command_lut[] PROGMEM = {
#define ENTRY(a) {CLI_STR_ ## a, sizeof(CLI_STR_ ## a), CLI_COM_ ## a},
    COMMAND_TABLE
#undef ENTRY
};

The produces the following output from the preprocessor:

typedef struct
{
 PGM_P str;
 uint8_t str_len;
 uint8_t id;
} CLI_COMMAND_LUT_t;

enum {
 CLI_COM_testA, CLI_COM_testB, CLI_COM_testC,
};

const char CLI_STR_testA PROGMEM = "testA"; const char CLI_STR_testB PROGMEM = "testB"; const char CLI_STR_testC PROGMEM = "testC";

CLI_COMMAND_LUT_t command_lut[] PROGMEM = {

 {CLI_STR_testA, sizeof(CLI_STR_testA), CLI_COM_testA}, {CLI_STR_testB, sizeof(CLI_STR_testB), CLI_COM_testB}, {CLI_STR_testC, sizeof(CLI_STR_testC), CLI_COM_testC},

};

So all that lot can be wrapped up an a region and I end up with just a simple and most importantly single definition of each command that serves as both its string name and the reference for the code.

Thanks a lot guys, much appreciated!

Upvotes: 6

Views: 2426

Answers (1)

user694733
user694733

Reputation: 16047

X-Macros might help.

strings.x:

X(TEMP, "Temp")
X(POWER, "Power")

Usage:

// String concatenation macros
#define CONCAT(a, b)   CONCAT2(a, b)
#define CONCAT2(a, b)  a ## b

// Generate string variables
#define X(a, b)      const char CONCAT(CLI_STR_, a) []   PROGMEM = b;
#include "strings.x"
#undef X

// Generate enum constants
#define X(a, b)      CONCAT(CLI_COM_, a),
enum {
#include "strings.x"
};
#undef X

// Generate table
#define X(a, b)      { CONCAT(CLI_STR_, a),     sizeof(CONCAT(CLI_STR_, a)),   CONCAT(CLI_COM_, a) },
const CLI_COMMAND_t cli_cmd_table[] = { 
#include "strings.x"
};
#undef X

This is untested.

Upvotes: 2

Related Questions