Reputation: 1731
I'm trying to do multiple Menus in Arduino. Each menu has many lines. Each line may have variables needing to be displayed. I can't get my head around how to:
a) define my menu structure properly b) load starting data into it c) use it
My code below is almost there I think - see my comments (look for "// HELP"):
// Example for doing Menus for JB
#define MAX_MENU_LINES 3 // How many lines are on each of your menu screens
typedef struct menu_item_def {
byte x; byte y; // Coordinates of the start for the line of test
byte selected; // set to 1 if the menu buttons have this option selected
char *mtext; // What to say, including sprintf placeholders: eg: "STOP TIMER...% 4.2i MIN"
byte mdatatype1; // 0 means this is actual data to print. 1 means go call the supplied function to get the data when needed.
void *mdata1; // Where to get any data from for the menu (upto 2 different bits allowed per line)
byte mdatatype2;
void *mdata2;
} menu_item_type;
// I do not want to do this:-
struct MenuT {
menu_item_type mline;
} MainMenu[MAX_MENU_LINES] = {
{1,2,0, "STOP TIMER....% 5.1i MIN" ,0,(void *)85,0,0},
{1,2,1, "CHEM RATE....% 5.2f Lt/Hr" ,1,(void *)DemoData,0,0},
{1,2,0, "CHEM PUMP.... %s" ,0,(void *)"ON/OFF",0,0},
};
// HELP
/* I would prefer to do something like this:-
typedef struct menu_def {
menu_item_type mline[MAX_MENU_LINES];
} menu_type;
menu_type MainMenu = {
{1,2,0, "STOP TIMER....% 5.1i MIN" ,0,(void *)85,0,0},
{1,2,1, "CHEM RATE....% 5.2f Lt/Hr" ,0,(void *)85,0,0},
{1,2,0, "CHEM PUMP.... %s" ,0,(void *)"ON/OFF",0,0},
};
menu_type SubMenu = {
{1,2,0, "MOTOR RPM.....% 5.1i RPM" ,0,(void *)8500,0,0},
{1,2,1, "ALARM MAX....% 5.2f Lt/Hr" ,0,(void *)85,0,0},
{1,2,0, "ALARM MIN.... %s" ,0,(void *)"ON/OFF",0,0},
};
*/
#define MAX_WIDTH 256 // This is the max width of anything you need to print - increase this if your LCD is wider
char buf[MAX_WIDTH];
char *p(char *fmt, ... ){ // Helper routine for putting numbers/readings/etc into printable strings
va_list args; va_start (args, fmt );
vsnprintf(buf, MAX_WIDTH, fmt, args);
va_end (args);
return buf;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600); // I have no LCD, so, my output is to serial (Hit Ctrl+Shit+M or go Tools->Serial Monitor to see output). Note: Seral-Monitor will reset board.
Menu(MainMenu); // HELP - I don't know how to pass whatever got defined above
}
void loop() {
// put your main code here, to run repeatedly:
// Does nothing - the setup outputted the menu already
}
int Menu( /* // HELP - I don't know what to put here to recevied a passed-in menu */ ) {
for(int i=0;i<MAX_MENU_LINES;i++) {
// Serial.println(mymenu[i].mtext); // Help - I don't know how to reference the bit I need!
};
Serial.print(p("STOP TIMER...% 4.2i MIN",5));
}
int DemoData() {
return 85;
}
So sorry to sound like a n00b - somewhere since I learned this 30 years ago, the specifics got garbage-collected out of my brain:-(
Upvotes: 1
Views: 3308
Reputation: 1731
Here's how I ended up doing it. Thanks to everyone who supplied the clues I needed!!!
Best I can tell - Macros must be used - it is impossible any other way owing to the architecture of the chip and the necessity to use PROGMEM.
Be warned: there are all kinds of size and line-end limits and bugs in arduino: if you're using this, compile often and frequently - it's impossible to debug macro errors, so you must not allow mistakes to creep in.
For any of you scratching your head: PROGMEM lets you put some stuff in FLASH (which you've got lots of) instead of storing it in SRAM (which is severely limited). When you run out of space using arduino variables, you need to do this stuff.
#include <avr/pgmspace.h> // This lets us store static variables in FLASH instead of SRAM
#define MAX_MENU_WIDTH 64 // Max number of characters on a single menu line, PLUS 1
#define SERIAL_RATE 115200 // The BAUD rate of the serial port
// This is the definition of each line in a menu. Each line uses 14 bytes of SRAM
typedef struct menu_item_def {
byte x; byte y; // Coordinates of the start for the line of test
byte selected; // set to 1 if the menu buttons have this option selected
byte mdatatype1; // This number explains how to use the following 2 mdata* as output when needed:
char *mtext; // What to say, including sprintf placeholders: eg: "STOP TIMER...% 4.2i MIN"
void *mdata1; // Refer to the "int Menu()" function for details.
void *mdata2;
void *mdata3;
void *mdata4;
} menu_item_type;
#define Pv(a,b) a ## _ ## b
#define Ev(a,b) Pv(a,b)
// The following macro makes menu definitions easy-to-type, and stores menu text in (unchangeable) FLASH and menu data in (precious but small) SRAM
// These numbers should range from 1 to MAX_MENU_LINES. Best not to edit these, or add more than 12: they're already at the absolute max size that an arduino macro can be.
#define FLASH_PART(n, m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11) \
const char Ev(n,0)[] PROGMEM=m0; \
const char Ev(n,1)[] PROGMEM=m1; \
const char Ev(n,2)[] PROGMEM=m2; \
const char Ev(n,3)[] PROGMEM=m3; \
const char Ev(n,4)[] PROGMEM=m4; \
const char Ev(n,5)[] PROGMEM=m5; \
const char Ev(n,6)[] PROGMEM=m6; \
const char Ev(n,7)[] PROGMEM=m7; \
const char Ev(n,8)[] PROGMEM=m8; \
const char Ev(n,9)[] PROGMEM=m9; \
const char Ev(n,10)[] PROGMEM=m10; \
const char Ev(n,11)[] PROGMEM=m11;
#define MAX_MENU_LINES 12 // How many lines are on each of your menu screens
#define MAKE_MENU(n, x0,y0,s0,m0,t0,f0,d0,z0,q0, x1,y1,s1,m1,t1,f1,d1,z1,q1, x2,y2,s2,m2,t2,f2,d2,z2,q2, x3,y3,s3,m3,t3,f3,d3,z3,q3, x4,y4,s4,m4,t4,f4,d4,z4,q4, x5,y5,s5,m5,t5,f5,d5,z5,q5, x6,y6,s6,m6,t6,f6,d6,z6,q6, x7,y7,s7,m7,t7,f7,d7,z7,q7, x8,y8,s8,m8,t8,f8,d8,z8,q8, x9,y9,s9,m9,t9,f9,d9,z9,q9, x10,y10,s10,m10,t10,f10,d10,z10,q10, x11,y11,s11,m11,t11,f11,d11,z11,q11) \
FLASH_PART(n, m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11) \
struct menu_item_def n[MAX_MENU_LINES] = { {x0,y0,s0,t0,(char *)Ev(n,0),f0,d0,z0,q0},{x1,y1,s1,t1,(char *)Ev(n,1),f1,d1,z1,q1},{x2,y2,s2,t2,(char *)Ev(n,2),f2,d2,z2,q2},{x3,y3,s3,t3,(char *)Ev(n,3),f3,d3,z3,q3},{x4,y4,s4,t4,(char *)Ev(n,4),f4,d4,z4,q4},{x5,y5,s5,t5,(char *)Ev(n,5),f5,d5,z5,q5},{x6,y6,s6,t6,(char *)Ev(n,6),f6,d6,z6,q6},{x7,y7,s7,t7,(char *)Ev(n,7),f7,d7,z7,q7},{x8,y8,s8,t8,(char *)Ev(n,8),f8,d8,z8,q8},{x9,y9,s9,t9,(char *)Ev(n,9),f9,d9,z9,q9},{x10,y10,s10,t10,(char *)Ev(n,10),f10,d10,z10,q10},{x11,y11,s11,t11,(char *)Ev(n,11),f11,d11,z11,q11}, };
//FINALLY - Here is the "readable" way to initialize the structures (all the 7,8,9 stuff are placeholders for future expansion):
MAKE_MENU (main_menu_items,
1,1,1, "STOP TIMER.....% 5.1i MIN" ,0, (void *)83, (void *)7, (void *)8, (void *)9,
1,2,1, "CHEM ON/OFF....%s" ,2, (void *)"ON/OFF", (void *)7, (void *)8, (void *)9,
1,3,0, "MOTOR RPM......% s RPM" ,1, (void *)ReadRPM, (void *)7, (void *)8, (void *)9,
1,4,1, "TOT CHEM RATE..% 5.2i L/Hr" ,1, (void *)DemoData, (void *)7, (void *)8, (void *)9,
1,5,0, "TOT CHEM USED..% 5.2i L/Hr" ,1, (void *)DemoData, (void *)7, (void *)8, (void *)9,
1,6,1, "CHEM 1 RATE....% 23.6i L/Hr" ,0, (void *)83, (void *)7, (void *)8, (void *)9,
1,7,1, "CHEM 2 RATE....% 16.3i L/Hr" ,0, (void *)84, (void *)7, (void *)8, (void *)9,
1,8,1, "CHEM 3 RATE....% 34.9i L/Hr" ,0, (void *)85, (void *)7, (void *)8, (void *)9,
1,9,0, "PH READ........% s PH" ,1, (void *)ReadpHVolts, (void *)7, (void *)8, (void *)9,
1,10,1, "PH: Min: %f Max: %f" ,1, (void *)ReadpHMin, (void *)7, (void *)ReadpHMax, (void *)9,
1,11,0, "BATT VOLTS.....% s V" ,1, (void *)ReadBatt, (void *)7, (void *)8, (void *)9,
1,12,0, "Msg:%s" ,1, (void *)DemoData, (void *)7, (void *)8, (void *)9
);
To USE that stuff, I'm simply doing this for simple datatypes:
long myval=(long)mymenu[i].mdata1;
And this to call my functions:
long myval=(*reinterpret_cast<long (*)()>(mymenu[i].mdata1))();
Upvotes: 0
Reputation: 1731
Not the answer yet, but, most of the problems fixed now. What remains - is how I use unions, or if not using unions, how I initiate the call to the function that returns the data. This, is seems, is not it:-
long myint=(*(mymenu[i].mdata1))();
Here's the code - search for "// Help"
// Example for doing Menus for JB
#define MAX_MENU_LINES 3 // How many lines are on each of your menu screens
// This is unused - I think it's imposible to static-init using these?
union multi_data {
int d_int; // mdatatype 0
void *d_function; // 1
float d_float; // 2
};
// This is the definition of each line in a menu
typedef struct menu_item_def {
byte x; byte y; // Coordinates of the start for the line of test
byte selected; // set to 1 if the menu buttons have this option selected
char *mtext; // What to say, including sprintf placeholders: eg: "STOP TIMER...% 4.2i MIN"
byte mdatatype1; // 0 means this is actual data to print. 1 means go call the supplied function to get the data when needed.
void *mdata1; // Where to get any data from for the menu (upto 2 different bits allowed per line)
byte mdatatype2;
void *mdata2;
} menu_item_type;
// All menus are a collection of lines (exactly MAX_MENU_LINES (3 in this example) of them to be exact)
struct menu_item_def main_menu_items[MAX_MENU_LINES] = {
{ 1,2,0, "STOP TIMER....% 5.1i MIN" ,0, (void *)85,0,0 },
{ 1,2,1, "CHEM RATE....% 5.2f Lt/Hr" ,1, (void *)DemoData,0,0 },
{ 1,2,0, "CHEM PUMP.... %s" ,0, (void *)"ON/OFF",0,0 },
};
struct menu_item_def sub_menu_items[MAX_MENU_LINES] = {
{1,2,0, "MOTOR RPM.....% 5.1i RPM" ,0,(void *)8500,0,0},
{1,2,1, "ALARM MAX....% 5.2f Lt/Hr" ,0,(void *)85,0,0},
{1,2,0, "ALARM MIN.... %s" ,0,(void *)"ON/OFF",0,0},
};
#define MAX_WIDTH 256 // This is the max width of anything you need to print - increase this if your LCD is wider
char buf[MAX_WIDTH];
char *p(char *fmt, ... ){ // Helper routine for putting numbers/readings/etc into printable strings
va_list args; va_start (args, fmt );
vsnprintf(buf, MAX_WIDTH, fmt, args);
va_end (args);
return buf;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600); // I have no LCD, so, my output is to serial (Hit Ctrl+Shit+M or go Tools->Serial Monitor to see output). Note: Seral-Monitor will reset board.
Menu(main_menu_items);
Menu(sub_menu_items);
}
void loop() {
// put your main code here, to run repeatedly:
// Does nothing - the setup outputted the menu already
}
int Menu( struct menu_item_def *mymenu ) {
for(int i=0;i<MAX_MENU_LINES;i++) {
if(mymenu[i].mdatatype1==0) { // an int
long myint=(long)mymenu[i].mdata1;
Serial.println(p(mymenu[i].mtext,42));
} else {
long myint=(*(mymenu[i].mdata1))(); // Help - gives: "JB_Menus.ino:67:37: error: 'void*' is not a pointer-to-object type"
Serial.println(p(mymenu[i].mtext,myint));
}
};
Serial.print(p("STOP TIMER...% 4.2i MIN",5));
MenuLine(5,6);
}
int MenuLine(int x, int y) {
Serial.print(x);
}
int DemoData() {
return 86;
}
Upvotes: 0
Reputation: 3335
You are almost there.
Your menu data structure is an array of struct MenuT
items. The function prototype for your Menu()
function is then
int Menu(struct MenuT menu[])
{
...
}
(You probably might want to add a second parameter to have the number of valid elements in the array available within your function).
There are still (kindly spoken) some inconsistencies left in your code for you to fix (functions called before declaration, for example) and some - ummh - ugly parts (function pointers and data on the same structure member using voids, this is what unions are meant for), but I guess you can fix that on your own.
[edit: added this as requirements where made more clear with additional comments]
if you need to have more than a single menu, you can set up your data structure as follows:
struct MenuT {
int num_items; /* number of items in mline-array */
menu_item_type *mline;
};
struct menu_item_type main_menu_items[] = {
{ 1,2,0, "STOP TIMER....% 5.1i MIN" ,0, (void *) 85,0,0 },
{ 1,2,1, "CHEM RATE....% 5.2f Lt/Hr" ,1, (void *) DemoData,0,0 },
{ 1,2,0, "CHEM PUMP.... %s" ,0, (void *) "ON/OFF",0,0 },
};
struct menu_item_type other_menu_items[] = {
{ .... },
};
struct MenuT main_menu = {
3,
main_menu_items
};
struct MenuT other_menu = {
2,
other_menu_items
};
referencing the items in Menu()
would then just become
int Menu(struct MenuT *menu) {
for (int i = 0; i < menu->num_items; i++) {
Serial.println(menu->items[i].mtext);
}
}
when called from main()
like
Menu(&main_menu);
Menu(&other_menu);
Upvotes: 1