Reputation: 443
There are a number of posts about how to define structures that reference each other, but I don't see one about the variables created with these defined structures.
I have a number of menus that can be navigated in my product that are represented by two structures. This code builds the structures that I want:
typedef void (*MENU_FUNCTION_TYPE)();
typedef enum
{
MENU_INPUT_NAV, // Menu just navigates to other menus.
MENU_INPUT_ACTION, // Menu displays the results of an action.
MENU_INPUT_NUM, // Menu take numeric input and processes it.
NUM_MENU_INPUTS
} MENU_INPUT_TYPE;
// A menu is comprised of a list of menu items which you select with the number keys.
// This structure defines a menu and the menu items it contains.
struct MENU_ITEM_TYPE;
struct MENU_TYPE
{
PGM_P text; // Pointer to text to display as menu name.
const MENU_ITEM_TYPE* menu_items; // Pointer to structures describing each menu item in this menu.
uint8_t num_items; // The number of menu item in this menu.
MENU_INPUT_TYPE type; // Specifies how this menu processes keyboard input.
};
// This structure defines a menu item and what it does when you select it.
struct MENU_ITEM_TYPE
{
PGM_P text; // Pointer to text to display as menu item name.
const MENU_TYPE* link; // Pointer to which menu selecting this menu item takes you.
MENU_FUNCTION_TYPE action; // Pointer to function that is executed when this menu item is selected.
};
extern const MENU_TYPE PROGMEM main_menu;
extern const MENU_TYPE PROGMEM todo_menu;
extern const MENU_TYPE PROGMEM todo2_menu;
extern const MENU_TYPE PROGMEM todo3_menu;
extern const MENU_TYPE PROGMEM todo4_menu;
// **************************
// "Main Menu"
const char PROGMEM main_menu_text[] = "Main Menu";
const char PROGMEM main_menu_item_text1[] = "First Menu";
const char PROGMEM main_menu_item_text2[] = "Second Menu";
const char PROGMEM main_menu_item_text3[] = "Third Menu";
const char PROGMEM main_menu_item_text4[] = "Fourth Menu";
const MENU_ITEM_TYPE PROGMEM main_menu_items[] =
{
{main_menu_item_text1, &todo_menu, NULL},
{main_menu_item_text2, &todo2_menu, NULL},
{main_menu_item_text3, &todo3_menu, NULL},
{main_menu_item_text4, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM main_menu =
{
main_menu_text,
main_menu_items,
LENGTH(main_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO Menu"
const char PROGMEM todo_menu_text[] = "TODO Menu";
const char PROGMEM todo_menu_item_text1[] = "todo";
const MENU_ITEM_TYPE PROGMEM todo_menu_items[] =
{
{todo_menu_item_text1, &todo2_menu, NULL}
};
const MENU_TYPE PROGMEM todo_menu =
{
todo_menu_text,
todo_menu_items,
LENGTH(todo_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO2 Menu"
const char PROGMEM todo2_menu_text[] = "TODO2 Menu";
const char PROGMEM todo2_menu_item_text1[] = "todo2";
const MENU_ITEM_TYPE PROGMEM todo2_menu_items[] =
{
{todo2_menu_item_text1, &todo3_menu, NULL}
};
const MENU_TYPE PROGMEM todo2_menu =
{
todo2_menu_text,
todo2_menu_items,
LENGTH(todo2_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO3 Menu"
const char PROGMEM todo3_menu_text[] = "TODO3 Menu";
const char PROGMEM todo3_menu_item_text1[] = "todo3";
const MENU_ITEM_TYPE PROGMEM todo3_menu_items[] =
{
{todo3_menu_item_text1, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM todo3_menu =
{
todo3_menu_text,
todo3_menu_items,
LENGTH(todo3_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO4 Menu"
const char PROGMEM todo4_menu_text[] = "TODO4 Menu";
const MENU_TYPE PROGMEM todo4_menu =
{
todo4_menu_text,
NULL,
0,
MENU_INPUT_NUM
};
The thing that bugs me about this code is the "extern" keyword. I need it to compile, but since the structures are defined in the same file it feels there should be a better way to write the code. I definitely don't want to put all of the declarations in the header file, since none of the code that uses the terminal should ever access these structures directly. Normally I would get over being bugged and keep coding, but this source is actually going to be seen by customers.
Is there a way I can write this code without the "extern" keywords?
Upvotes: 0
Views: 976
Reputation: 39326
If this is user-visible code, and you wish it to compile without errors or warnings using avr-gcc -std=c99 -Wall -Wextra
and avr-g++ -std=c++11 -Wall -Wextra
(plus whatever -O
and -mmcu=
etc. options you need), I suggest you hide the fragile parts in macros, and make the macros robust. For example, using structures that closely match the ones shown in the original question:
#include <avr/pgmspace.h>
#define JOIN2TOKENS(token1, token2) JOIN2TOKENS_(token1, token2)
#define JOIN2TOKENS_(token1, token2) token1 ## token2
struct menu_st {
const char *const title;
unsigned char const items;
const struct menuitem_st *const *const item;
};
struct menuitem_st {
const char *const label;
void (*const action)();
const struct menu_st *const submenu;
};
#define DECLARE_MENU(name, titlestr) \
extern const struct menu_st name PROGMEM; \
static const PROGMEM char JOIN2TOKENS(title_of__, name) [] PROGMEM = titlestr
#define DECLARE_MENUITEM(name, labelstr, funcptr, menuptr) \
static const char JOIN2TOKENS(label_of__, name) [] PROGMEM = labelstr; \
static const struct menuitem_st name [1] PROGMEM = { { JOIN2TOKENS(label_of__, name), funcptr, menuptr } }
#define DECLARE_SUBMENU(name, labelstr, tomenu) \
DECLARE_MENUITEM(name, labelstr, (void (*)())0, &(tomenu))
#define DECLARE_ACTION(name, labelstr, funcname) \
DECLARE_MENUITEM(name, labelstr, funcname, (const struct menu_st *const )0)
#define DECLARE_ACTSUB(name, labelstr, funcname, tomenu) \
DECLARE_MENUITEM(name, labelstr, funcname, &(tomenu))
#define DEFINE_MENU(name, ...) \
static const struct menuitem_st *const JOIN2TOKENS(menu_items_of__, name) [] PROGMEM = { __VA_ARGS__ }; \
const struct menu_st name PROGMEM = { \
JOIN2TOKENS(title_of__, name), \
sizeof (JOIN2TOKENS(menu_items_of__, name)) / sizeof (JOIN2TOKENS(menu_items_of__, name)[0]), \
JOIN2TOKENS(menu_items_of__, name), \
}
/* 0. Declare or define menu action functions.
* These are declared as static, so that they are only visible
* in the current compilation unit.
*
* As an example, action_1() and action_2() are defined here,
* but defaults() is only declared.
*/
static void action_1(void) { return; }
static void action_2(void) { return; }
static void defaults(void);
/* 1. Declare each menu.
*
* DECLARE_MENU(name, string)
* 'name' is the menu variable name, and
* 'string' is the menu title, a string literal.
*/
DECLARE_MENU(main_menu, "Main menu");
DECLARE_MENU(submenu_1, "First submenu");
DECLARE_MENU(submenu_2, "Second submenu");
/* 2. Declare each menu item. Below,
* 'name' is the local variable name (used in DEFINE_MENU()),
* 'string' is the text for this menu item,
* 'function' is the function called when the menu item is activated, and
* 'menu' is the name of the menu the menu item takes to.
*
* DECLARE_SUBMENU(name, string, menu)
* Declare a menu item, that takes to another menu.
*
* DECLARE_ACTION(name, string, function)
* Declare a menu item, that causes a function to be called.
*
* DECLARE_ABTSUB(name, string, function, menu)
* Declare a menu item, that causes a function to be called,
* and then changes to another menu.
*
*/
DECLARE_SUBMENU(to_main_menu, "Back to main menu", main_menu);
DECLARE_SUBMENU(to_submenu_1, "To first submenu", submenu_1);
DECLARE_SUBMENU(to_submenu_2, "To second submenu", submenu_2);
DECLARE_ACTION(do_action_1, "Action 1", action_1);
DECLARE_ACTION(do_action_2, "Action 2", action_2);
DECLARE_ACTSUB(do_defaults, "Reset to defaults", defaults, main_menu);
/* 3. Define which menus have which menu items.
*
* DEFINE_MENU(name, menu_item_1 [, ..., menu_item_N ])
* 'name' is the menu variable name, and should have been
* declared using DECLARE_MENU(name, string) before.
* 'menu_item_1' (and all additional parameters) are
* menu items declared before using
* DECLARE_SUBMENU(), DECLARE_ACTION(), or DECLARE_ACTSUB().
*/
DEFINE_MENU(main_menu, to_submenu_1, to_submenu_2);
DEFINE_MENU(submenu_1, to_submenu_2, do_action_1, do_action_2, to_main_menu);
DEFINE_MENU(submenu_2, to_submenu_1, do_defaults, to_main_menu);
/* 4. If the menu action functions were only declared earlier,
* define them here.
*/
static void defaults(void) { return; }
The DECLARE_MENU()
macro does use extern const
, to forward-declare the menu structures. Only the menu names -- main_menu
, submenu_1
, and submenu_2
, above -- are externally visible. This happens to work correctly in both C99 and C++11. If you wish the symbols to be local, you'll need different code for C99 and for C++11 (doable, via #ifdef __cplusplus
- #else
- #endif
, but ugly, if you ask me).
Both avr-gcc-4.8.2 -std=c99 -Wall -Wextra
and avr-g++-4.8.2 -std=c++11 -Wall -Wextra
store all the data structures in .progmem.data
section.
Note that because GCC does not support multiple address spaces (last I checked was avr-gcc-4.8.2), you'll have to use pgm_read_byte()
and pgm_read_word()
macros defined in <avr/pgmspace.h>
to access the data structures. For example, instead of main_menu.items
you'll need to use pgm_read_byte(&(main_menu.items))
. Something like the following example -- but note that this part is untested, and might contain typos/thinkos, especially regarding dereferencing the pointers:
extern void print_clear_all(void);
extern void print_menu_title(const char *const pgm_string);
extern void print_menu_item(const char *const pgm_string, const unsigned char is_focus);
extern void print_clear_rest();
static const menu_t *current_menu = &main_menu;
static unsigned char current_item = 0;
void menu_refresh(void)
{
const unsigned char n = pgm_read_byte(&(current_menu->items));
const void *const *const itemarray = (const void *const *)pgm_read_word(&(current_menu->item));
unsigned char i;
print_clear_all();
print_menu_title((const char *)pgm_read_word(&(current_menu->title)));
for (i = 0U; i < n; i++)
print_menu_item((const char *)pgm_read_word(itemarray + i), i == current_item);
print_clear_rest();
}
void menu_prev(void)
{
if (current_item > 0U)
--current_item;
menu_refresh();
}
void menu_next(void)
{
if (current_item + 1U < pgm_read_byte(&(current_menu->items)))
++current_item;
menu_refresh();
}
void menu_enter(void)
{
const void *const *const itemarray = (const void *const *const)pgm_read_word(&(current_menu->item));
const menuitem_t *const item = (const menuitem_t *const)pgm_read_word(itemarray + current_item);
void (*const action)() = (void (*)())pgm_read_word(&(item->action));
const menu_t *const submenu = (const menu_t *const)pgm_read_word(&(item->submenu));
if (action)
action();
if (submenu)
current_menu = submenu;
menu_refresh();
}
It seems to me that using pointers makes the code pretty dense. A completely different approach, one that would use an array index to identify the menu, and another to identify the option, seems much more preferable to me.
Upvotes: 0
Reputation: 4877
In standard C a struct is not equivalent to a typedef
.
This amended code compiles on a standard C compiler:
typedef void (*MENU_FUNCTION_TYPE)();
typedef enum
{
MENU_INPUT_NAV, // Menu just navigates to other menus.
MENU_INPUT_ACTION, // Menu displays the results of an action.
MENU_INPUT_NUM, // Menu take numeric input and processes it.
NUM_MENU_INPUTS
} MENU_INPUT_TYPE;
// A menu is comprised of a list of menu items which you select with the number keys.
// This structure defines a menu and the menu items it contains.
typedef struct tag_MENU_ITEM_TYPE MENU_ITEM_TYPE;
typedef struct tag_MENU_TYPE
{
PGM_P text; // Pointer to text to display as menu name.
const MENU_ITEM_TYPE* menu_items; // Pointer to structures describing each menu item in this menu.
uint8_t num_items; // The number of menu item in this menu.
MENU_INPUT_TYPE type; // Specifies how this menu processes keyboard input.
} MENU_TYPE;
// This structure defines a menu item and what it does when you select it.
typedef struct tag_MENU_ITEM_TYPE
{
PGM_P text; // Pointer to text to display as menu item name.
const MENU_TYPE* link; // Pointer to which menu selecting this menu item takes you.
MENU_FUNCTION_TYPE action; // Pointer to function that is executed when this menu item is selected.
} MENU_ITEM_TYPE;
//These now acts as forward declarations
const MENU_TYPE PROGMEM main_menu;
const MENU_TYPE PROGMEM todo_menu;
const MENU_TYPE PROGMEM todo2_menu;
const MENU_TYPE PROGMEM todo3_menu;
const MENU_TYPE PROGMEM todo4_menu;
// **************************
// "Main Menu"
const char PROGMEM main_menu_text[] = "Main Menu";
const char PROGMEM main_menu_item_text1[] = "First Menu";
const char PROGMEM main_menu_item_text2[] = "Second Menu";
const char PROGMEM main_menu_item_text3[] = "Third Menu";
const char PROGMEM main_menu_item_text4[] = "Fourth Menu";
const MENU_ITEM_TYPE PROGMEM main_menu_items[] =
{
{main_menu_item_text1, &todo_menu, NULL},
{main_menu_item_text2, &todo2_menu, NULL},
{main_menu_item_text3, &todo3_menu, NULL},
{main_menu_item_text4, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM main_menu =
{
main_menu_text,
main_menu_items,
LENGTH(main_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO Menu"
const char PROGMEM todo_menu_text[] = "TODO Menu";
const char PROGMEM todo_menu_item_text1[] = "todo";
const MENU_ITEM_TYPE PROGMEM todo_menu_items[] =
{
{todo_menu_item_text1, &todo2_menu, NULL}
};
const MENU_TYPE PROGMEM todo_menu =
{
todo_menu_text,
todo_menu_items,
LENGTH(todo_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO2 Menu"
const char PROGMEM todo2_menu_text[] = "TODO2 Menu";
const char PROGMEM todo2_menu_item_text1[] = "todo2";
const MENU_ITEM_TYPE PROGMEM todo2_menu_items[] =
{
{todo2_menu_item_text1, &todo3_menu, NULL}
};
const MENU_TYPE PROGMEM todo2_menu =
{
todo2_menu_text,
todo2_menu_items,
LENGTH(todo2_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO3 Menu"
const char PROGMEM todo3_menu_text[] = "TODO3 Menu";
const char PROGMEM todo3_menu_item_text1[] = "todo3";
const MENU_ITEM_TYPE PROGMEM todo3_menu_items[] =
{
{todo3_menu_item_text1, &todo4_menu, NULL}
};
const MENU_TYPE PROGMEM todo3_menu =
{
todo3_menu_text,
todo3_menu_items,
LENGTH(todo3_menu_items, MENU_ITEM_TYPE PROGMEM),
MENU_INPUT_NAV
};
// **************************
// "TODO4 Menu"
const char PROGMEM todo4_menu_text[] = "TODO4 Menu";
const MENU_TYPE PROGMEM todo4_menu =
{
todo4_menu_text,
NULL,
0,
MENU_INPUT_NUM
};
Upvotes: 1