Program memory storage of menu data...

I thought I’d take a shot at designing a non-blocking menu system for arduino, but I’m running into problems that seem to have to do with c++ and program memory data structures. I’m not a C++ programmer (just C), so I thought I’d ask for help.

I have narrowed it down to a nice sample program:

#include "WProgram.h"
#include <avr/pgmspace.h>

void menu_up(void) {};
void menu_boot(void) {};
void menu_yboot(void) {};
void menu_zboot(void) {};
void setup(void) {}
void loop(void) {}


#define MENU_SUBMENU 0x80            /*  */
#define MENU_ACTION 0

typedef const struct PROGMEM menu_item_ {
    const unsigned char menu_key;
    const void *menu_action;                  /* function or menu pointer */
} menu_item_t;

const menu_item_t menu_3deep[] PROGMEM = {
    { 'X' + MENU_ACTION, menu_up },
    { 'A' + MENU_ACTION, menu_boot},
    { 'Y' + MENU_ACTION, menu_yboot},
    { 'Z' + MENU_ACTION, menu_zboot},
    {0, 0}
};

This compiles successfully as a C program, but not in the C++ environment that Arduino defaults to. I get errors about invalid conversion from 'void ()()’ to 'const void:

cd ~/Documents/Arduino/cppissue/
make -k cppissue-cpp.o
/Downloads/arduino-0008/tools/avr/bin/avr-g++ -c -mmcu=atmega8 -I. -DF_CPU=16000000 -I/Downloads/arduino-0008/lib/targets/arduino -Os cppissue-cpp.cpp -o cppissue-cpp.o 

cppissue-cpp.cpp:26: error: invalid conversion from 'void (*)()' to 'const void*'
cppissue-cpp.cpp:26: error: invalid conversion from 'void (*)()' to 'const void*'
cppissue-cpp.cpp:26: error: invalid conversion from 'void (*)()' to 'const void*'
cppissue-cpp.cpp:26: error: invalid conversion from 'void (*)()' to 'const void*'
make: *** [cppissue-cpp.o] Error 1

Compilation exited abnormally with code 2 at Mon Sep  3 02:45:28

(This used the cli makefile, but I get variations of the same error from within the arduino environment.)
Any thoughts? Perhaps it’s just easier to leave the menu definitions in a .c file (although I wouldn’t like explaining that to beginning users)? Can I just mix c and c++ without problems?
(Note that slight variations yield similar errors. Removing the somewhat questionable “const” from inside the structure definition results in “invalid conversion from 'void ()()’ to 'void” I thought (as a C programmer) that I could assign anything to a void* field…)

Thanks
Bill W

You want to declare your menu_action as:

const void menu_action();

That gives it the type pointer-to-a-function-returning-void as opposed to void *.

It's not clear from my example, but menu_action can be either a pointer to a function or a pointer to a new menu, one of the classic uses for C's "generic" (void *) pointer. I can accept a solution with a union as well, but I had trouble getting that initialized, even with plain C.

Even with the limited example, though, it didn't like your solution much better than what I had; I get "error: too many initializers for 'const menu_item_t, which I understand even less than the mismatch complaints.

(as a sometimes-assembly programmer, It's SO frustrating to have an obvious and not too complex structure in mind and have SO much trouble getting a HLL to produce what I had in mind. Grr.)

extern const void menu_up(void) {};
extern const void menu_boot(void) {};
extern const void menu_yboot(void) {};
extern const void menu_zboot(void) {};


#define MENU_SUBMENU 0x80            /*  */
#define MENU_ACTION 0

typedef const struct PROGMEM menu_item_ {
    unsigned char menu_key;
    const void menu_action(void);                  /* function or menu pointer */
} menu_item_t;

const menu_item_t menu_3deep[] PROGMEM = {
    { 'X' + MENU_ACTION, menu_up },
    { 'A' + MENU_ACTION, menu_boot},
    { 'Y' + MENU_ACTION, menu_yboot},
    { 'Z' + MENU_ACTION, menu_zboot},
    {0, 0}
};

Ok; next question. Assuming I’ve given up on c++, it looks like the ardino environment will happily let me write libraries in C if I just give them a “.c” file type (yeah!); looking at the library files, it’s pretty clear how to get the c++ code to call C functions. Can I get C to call C++ function as well? (in particular, assume that the menu code wants to call serial.* to do the actual IO. I see that there are also C functions in wiring_serial.c, but I’d just as soon avoid them as being undocumented for the casual arduino user…)

Thanks
Bill W

You're probably best off just writing C++ and using a .cpp extension. Or is there some reason you need it to be pure C?

Also, I got your code to compile by changing it to:

extern const void menu_up(void) {};
extern const void menu_boot(void) {};
extern const void menu_yboot(void) {};
extern const void menu_zboot(void) {};


#define MENU_SUBMENU 0x80            /*  */
#define MENU_ACTION 0

typedef const struct menu_item_ {
    unsigned char menu_key;
    const void (*menu_action)(void);                  /* function or menu pointer */
} menu_item_t;

const menu_item_t menu_3deep[] = {
    { 'X' + MENU_ACTION, menu_up },
    { 'A' + MENU_ACTION, menu_boot},
    { 'Y' + MENU_ACTION, menu_yboot},
    { 'Z' + MENU_ACTION, menu_zboot},
    {0, 0}
};

void setup() {}
void loop() {}

I had to take out the PROGMEM's though. Not quite sure why.

Ah hah. Thanks! Got it working, I think. C++ seems to find "const" sufficient for putting things in progmem. I made it work for either C or C++ and hid all the dirt in macros, so now user code looks like:

MENU_START(menu_C)
    MENU_ACT('X', menu_up)
    MENU_ACT('Z', menu_zboot)
MENU_END(menu_C)

MENU_START(menu_D)
    MENU_ACT('X', menu_up)
    MENU_ACT('A', menu_aboot)
MENU_END(menu_D)

MENU_START(topmenu)
    MENU_SUB('A', menu_A)
    MENU_SUB('B', menu_B)
    MENU_SUB('C', menu_C)
    MENU_SUB('D', menu_D)
MENU_END(menu_D)

It seems to work OK compiling a test program on unix. Now all I have to do is get off my butt and get one of my actual arduino boards working to do real tests.