MENWIZ: yet another character lcd menu wizard library

Ciao,
I made some progress on the Menu, and it's working very well with the Lcd Keypad shield .... this is the code for those with the same LCD Keypad ....

// +++++++ Libaries included
#include <Wire.h>
#include <LiquidCrystal.h>
//INSERT ALL THE LIBARIES AFTER INCLUDING WIRE LIB (MENWIZ request)
//#include <LiquidCrystal_I2C.h>               //eliminato, non più necessario
#include <buttons.h>
#include <MENWIZ.h>
#include <EEPROM.h>
#include <Stepper.h>

// Create global object for menu and lcd
menwiz menu;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution for your motor
Stepper myStepper(stepsPerRevolution, 0,1,2,3);   // initialize the stepper library on pins 0 through 3:

/*                                                eliminato, non più necessario
//Addr, En, Rw, Rs, d4, d5, d6, d7, backlightpin, polarity 4 x 20 LCD
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); 
*/

boolean wr=0;                         // Instantiate global variables to bind to menu

const int buttonPin = A0;             // 4 button switch circuit input connected to analog pin 0
const int led13Pin = 13;              // the number of the LED output pin
int stepCount = 0;                    // number of steps the motor has taken

boolean buttonBlock = 0;
boolean buttonAct = 0;                // flag 1
boolean stopMenu = 0;                 // flag 2
byte buttonPressed = 0;               // which button was pressed
byte lastButtonPressed = 0;           // prev button pressed
int buttonValue = 0;                  // read value from analog port for the 4 navigation pushbuttons
long menuOffTime = 0;                 //used for timer

int list,sp=0;                       // sp variable has 0 as default value (ABS height)

extern byte MW_navbtn;                //needed to run the software key navigation in Menwiz



void setup()
{
  Serial.begin(9600);                  // Output to serial writing

  digitalWrite((54), HIGH);            // enable the 20k internal pullup for MEGA board for analog port A0
  // digitalWrite((A0), HIGH);         // enable the 20k internal pullup for UNO based board for analog port A0

  //++++++++++++++++++++Menu and LCD  
  char b[84];
  _menu *r,*s1,*s2;

                                      // initialize the menu object ( 4 x rows x 20 colums LCD)
  menu.begin(&lcd,16,2);              //initialize the menwiz menu object passing the lcd object and the colums and rows params 
  menu.addUsrNav(navMenu,4);
  MW_navbtn=4;                        // force 4 or 6 buttons mode for menu navigation -> MW_navbtn=4; or MW_navbtn=6;

  //create the menu tree
  r=menu.addMenu(MW_ROOT,NULL,F("MAIN MENU"));                      //create a root menu at first (required)

  //---------------  
  s1=menu.addMenu(MW_SUBMENU,r,F("SAW"));                       //add a submenu node 1 to the root menu (control the heigh of my Saw)
  s2=menu.addMenu(MW_VAR,s1,F("ABS Height"));                   //add a terminal node in the menu tree (that is "variable"); (move at a certain mm height)
     s2->addVar(MW_AUTO_INT,&sp,0,100,1);                   //int type, fd binded variable, rage 0-100, step 1
  s2=menu.addMenu(MW_VAR,s1,F("REL Move"));                     //add a terminal node in the menu tree (that is "variable"); (move of a certain mm the height)
     s2->addVar(MW_AUTO_INT,&sp,0,100,1);                   //int type, fd binded variable, rage -100 +100, step 1
  s2=menu.addMenu(MW_VAR,s1,F("Zero"));                         //add a terminal node in the menu tree (that is "variable");  (set the zero)
      s2->addVar(MW_LIST,&list);
      s2->addItem(MW_LIST, F("Option1"));
      s2->addItem(MW_LIST, F("Option2"));
      s2->addItem(MW_LIST, F("Option3"));
  s2=menu.addMenu(MW_VAR,s1,F("Setup"));                        //add a terminal node in the menu tree (that is "variable");  (set the parameters)

  //---------------
  s1=menu.addMenu(MW_SUBMENU,r,F("PLANNER"));                    //add a submenu node 2 to the root menu (control the heigh of my Planner)
  s2=menu.addMenu(MW_VAR,s1,F("ABS Height"));                         //add a terminal node in the menu tree (that is "variable"); (should move at a certain mm height)
     s2->addVar(MW_AUTO_INT,&sp,0,100,1);                   //int type, fd binded variable, rage 0-100, step 1
  s2=menu.addMenu(MW_VAR,s1,F("REL Move"));                         //add a terminal node in the menu tree (that is "variable"); (should move of a certain mm the height)
     s2->addVar(MW_AUTO_INT,&sp,-100,100,1);                   //int type, fd binded variable, rage 0-100, step 1
  s2=menu.addMenu(MW_VAR,s1,F("Zero"));                         //add a terminal node in the menu tree (that is "variable"); (should set the zero)
  s2=menu.addMenu(MW_VAR,s1,F("Setup"));                        //add a terminal node in the menu tree (that is "variable");  (set the parameters)
  
  //---------------
  s1=menu.addMenu(MW_SUBMENU,r,F("SHAPER"));                    //add a submenu node 3 to the root menu
  s2=menu.addMenu(MW_VAR,s1,F("ABS Height"));                         //add a terminal node in the menu tree (that is "variable"); (should move at a certain mm height)
  s2=menu.addMenu(MW_VAR,s1,F("REL Move"));                     //add a terminal node in the menu tree (that is "variable"); (should move of a certain mm the height)
  s2=menu.addMenu(MW_VAR,s1,F("Zero"));                     //add a terminal node in the menu tree (that is "variable"); (should set the zero)
  s2=menu.addMenu(MW_VAR,s1,F("Setup"));                        //add a terminal node in the menu tree (that is "variable");  (set the parameters)
  
  /*
  //++++++ Splash screen +++++++
  
  sprintf(menu.sbuf,"TEST minimum menu\nwith 4 or 6\nanalog push buttons\n",1);
  menu.addSplash((char *) menu.sbuf, 1000);

  //++++++ Userscreen ++++++++++
  // create an user define screen callback to activate after X secs since last button push
  menu.addUsrScreen(msc,3000);
  */
  pinMode(led13Pin, OUTPUT);                // initialize the led as a output control pin
}

void loop()    
{
// NAVIGATION MANAGEMENT & DRAWING ON LCD. NOT BLOCKING has to be the first in the void loop
  menu.draw();
/*
  put your regular code here
*/
//  ++++++ functions +++++++

void readButtons()

// ++++ Control 4 buttons ++++
{
  lastButtonPressed = buttonPressed;

  /*
  Analog values representing the pushbutton value are depending on the resitor value used.
   Test the values first with the serial.monitor
   */
  buttonValue = analogRead(buttonPin); //analog value to simulate the pushed buttons
    
  if(buttonValue >= 850)
  {
    buttonPressed = 0;
    noButtonPressed(); // is calling an extra fucntion
  }
  else  if(buttonValue >= 380 & buttonValue <= 450)
  {
    buttonPressed = 4;
    buttonAct = 1; // set the menu flag1
  }
  else if(buttonValue >= 200 & buttonValue <=300)
  {    
    buttonPressed = 3;
    buttonAct = 1; // set the menu flag1
  }
  else if(buttonValue >= 0 & buttonValue <=50)
  {    
    buttonPressed = 2;
    buttonAct = 1; // set the menu flag1
  }
  else if(buttonValue >= 80 & buttonValue <=150)
  {    
    buttonPressed = 1;
    buttonAct = 1; // set the menu flag1
  }
}

int noButtonPressed()
{
  return MW_BTNULL;
}

int navMenu() // called from menu.draw
{
  /*
   As soon as a button is pushed the first time flag 1 is set to HIGH and if the buttonnumber is not 0 then a timer is started.
   The menu action then should only be performed once.
   After 2000 msecs the flag will be set to LOW and a new buttonpush can be processed.
   Menu action is blocked for 2000 msec even if the same button is being kept pressed for 2000 msecs by flag2.
   */

  long menublockTime = millis();

  if (buttonAct == 1 && buttonPressed != 0 && stopMenu == 0)  // we have a state were we process a menu navigation step once and block it for 2 secs
  {
    digitalWrite(led13Pin,HIGH);  // set timer led ON
    menuOffTime = menublockTime  + 400; //start the timer. You can change this blocking time to your convenience but do not make it lower aa 200 msecs
    stopMenu = 1;

    switch (buttonPressed)
    {
    case 1: // Up
      return MW_BTU;
      break;
    case 2: // Confirm
      return MW_BTC;
      break;
    case 3: // Down
      return MW_BTD;
      break;    
    case 4: // Escape
      return MW_BTE;
      break;
    }
  }

  if (menuOffTime != 0  &&  menublockTime  > menuOffTime)  //  Reset of the timer so a new menu action can be processed
  {
    buttonAct = 0; // resets the flag 1
    buttonPressed = 0;
    menuOffTime = 0;  // resets timer to zero
    stopMenu = 0;
    digitalWrite(led13Pin,LOW);  // set timer led OFF
  }
}

/*
// user defined default screen.
void msc()
{
  sprintf(menu.sbuf,"User screen\nUptime (s): %ld\nFree mem  : %d\n\n",millis()/1000,(int)menu.freeRam());
  menu.drawUsrScreen(menu.sbuf);
}*/

The stepper part is still to be implemented and also the menu code, but it's a "good" start (well there are maybe some errors but it works)

... And now a little question ...
This shows on the LCD a graphic format like that:
0 [10] 100

     s2->addVar(MW_AUTO_INT,&sp,0,100,1);                   //int type, fd binded variable, rage 0-100, step 1

Is it possible to change that format ?
I would like to display something like:
10 mm without the limit 0 - 100.
thanks

currently it is not possible. But is quite easy to add in next versions an option to avoid limits. It is not planned to add text label on the same variable value row.

Thanks Roberto, just to know if it was possible ... so many things I don't know.

Due to my complete ignorance it took me a while to figure this 3 or 5 button thing out completely. But that is part of the learning process is trying to figure out where my mistakes are.

I have it compiled but not tested. Program is at 30.7k and I have to get my double PID data converted to float for menwiz.h and then converted back to float for the PID_V1.h.

One question I have: does the new "BACK" menu option need to be in each terminal node?

OK, make it 2 questions: Can it be a menu.addItem instead of menu.addMenu? I'll find out in testing for sure.

 s2=menu.addMenu(MW_VAR,s1,F("Back"));     //add a terminal node (that is "variable"); 
          s2->addVar(MW_ACTION,&bte);                    //create a variable of type "action" 
          s2->setBehaviour(MW_ACTION_CONFIRM,false);         // you don't need action confirmation to emulate Escape button!!!

BACK: yes. as far as you do not want use a button for escape, BACK action should be the last option for each submenu. I can work on Menwiz in order to implemnet an "immediate" mode, that is any change is immediately consolidated, with CONFIRM button the only way to go back. But the code changes are not so easy ...

Unfortunately I can associate an action only to a menu node, not to a list item ... that is memory consuming, I know...

The good new: I found many serial debug tracing statements. That is consuming quite a lot of memory. Deleted. Some minor changes to the code: 180 code lines affected just to spare few bytes ....It' a dirty work, but someone has to do it .... :slight_smile:

I'll have to get back to this. I melted my new I2C display due to a wiring mistake. Another learning opportunity.

The use of custom button navigation using addUsrNav is giving me a bit of a headache. I can't see why it doesn't work.

I'm using MENWIZ Version 1.2.0 on IDE 1.0.1 with a breadboarded 328P and Arduino Uno selected as the Board.

What I am doing is basing all my input on a 4x4 Keypad, theKeypad and Keypad_I2C libraries. When I use my own buttons I just emulate a particular position on the keypad by connecting across the appropriate row/col; i.e I have 3 buttons on my device that emaulate the keys 4, 6 and #. Thus the Keypad library takes care of all my edge triggering and debouncing for me and I get up to 16 buttons sharing teh I2C lines via a PCF8574 expander.

For this menu I am choosing 2-UP, 4-LEFT, 6-RIGHT, 8-DOWN, *-ESCAPE, #-CONFIRM, so my custom function looks for these particular keypresses and returns the approrpiate MW_BTU, MW_BTL, MW_BTR, MW_BTD, MW_BTE and MW_BTC. It returns MW_BTNULL if no key is pressed when scanned.

I removed all the defines for the buttons in my sketch:-

// DEFINE ARDUINO PINS FOR THE MENU NAVIGATION BUTTONS
//#define UP_BOTTON_PIN       16
//#define DOWN_BOTTON_PIN     17
//#define LEFT_BOTTON_PIN     18
//#define RIGHT_BOTTON_PIN    19
//#define CONFIRM_BOTTON_PIN  20
//#define ESCAPE_BOTTON_PIN   21

I commented out the navButtons declaration and added a line for addUsrNav

    //menu.navButtons(UP_BOTTON_PIN,DOWN_BOTTON_PIN,LEFT_BOTTON_PIN,RIGHT_BOTTON_PIN,ESCAPE_BOTTON_PIN,CONFIRM_BOTTON_PIN);
    menu.addUsrNav(menuKeys,6);

I created my new function to read the keypad and just check for the keys we are using for menu items

int menuKeys()
{
  char keyPressed = keypad1.getKey();
  
  if (keyPressed != NO_KEY)
  {
    if (keyPressed == '2')
    {
      return MW_BTU;
    }
    else if (keyPressed == '8')
    {
      return MW_BTD;
    }
    else if (keyPressed == '4')
    {
      return MW_BTL;
    }
    else if (keyPressed == '6')
    {
      return MW_BTR;
    }
    else if (keyPressed == '*')
    {
      return MW_BTE;
    }
    else if (keyPressed == '#')
    {
      return MW_BTC;
    }
    else
    {
    return MW_BTNULL;
    }
  }

I also comment out the BUTTON_SUPPORT define in MENWIZ.h

#define EEPROM_SUPPORT     //uncomment if you want to use the readEeprom and writeEeprom methods!
//#define BUTTON_SUPPORT     //uncomment if you want to use the readEeprom and writeEeprom methods!!

(Incidentally, note the incorrect comment, I found the reference to this define and was looking for it up and down the code, but scanning comments too made it look like this section was just for EEPROM)

From what I've seen in previous posts, this should now work and let me navgate the menu structure with the keypad. I can call the function from loop() and Serial.print the returned values to Serial Monitor and I get the values 2,3,4,5,6,7 returned for U,D,L,R,E,C so the function is working fine, it just seems that MENWIZ isn't calling it.

Full sketch code below

//The full code is in library example file Quick_tour.ino
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <buttons.h>
#include <MENWIZ.h>
#include <EEPROM.h>
#include <Keypad_I2C.h>
#include <Keypad.h>

#define keypad1_addr 0x22
#define lcd1_addr 0x27

// DEFINE ARDUINO PINS FOR THE MENU NAVIGATION BUTTONS
//#define UP_BOTTON_PIN       16
//#define DOWN_BOTTON_PIN     17
//#define LEFT_BOTTON_PIN     18
//#define RIGHT_BOTTON_PIN    19
//#define CONFIRM_BOTTON_PIN  20
//#define ESCAPE_BOTTON_PIN   21

int GAMETIME=30, POINTPERIOD=10, POINTSPERPERIODRED=5, POINTSPERPERIODBLUE=5;
int RESETSCOREPERIOD=0, COUNTDOWNTIMER=3, SPLASHSCREEN=1, WIRELESS = 1, DEBUG = 1;
int LEDBLINK =1, LEDBLINKON = 500, LEDBLINKOFF = 500, CURSORBLINKRATE = 500, BACKLIGHTDELAY = 1;
int list, sp;

// Keypad setup
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
//define the symbols on the buttons of the keypads
char hexaKeys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};
byte rowPins[ROWS] = {3, 2, 1, 0}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {7, 6, 5, 4}; //connect to the column pinouts of the keypad

//initialize an instance of class NewKeypad
Keypad_I2C keypad1( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS, keypad1_addr); 

menwiz menu;
// create lcd obj using LiquidCrystal lib
//LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
LiquidCrystal_I2C lcd(lcd1_addr, 4, 5, 6, 0, 1, 2, 3, 7, NEGATIVE); // addr, EN, RW, RS, D4, D5, D6, D7, Backlight, POLARITY

void setup(){
  _menu *r,*s1,*s2,*s3;
  
  keypad1.begin();

  Serial.begin(115200);    
  menu.begin(&lcd,16,2); //declare lcd object and screen size to menwiz lib
  
  menu.setBehaviour(MW_MENU_INDEX,false); // turn off the menu index - 1/3 etc

  r=menu.addMenu(MW_ROOT,NULL,F("MAIN MENU"));
 
    s1=menu.addMenu(MW_SUBMENU,r, F("General"));
      s2=menu.addMenu(MW_SUBMENU,s1, F("Game Setup"));
        s3=menu.addMenu(MW_VAR,s2, F("Game Time (m)"));  
          s3->addVar(MW_AUTO_INT,&GAMETIME,0,5999,1);
        s3=menu.addMenu(MW_VAR,s2, F("Pts Period (s)"));
          s3->addVar(MW_AUTO_INT,&POINTPERIOD,0,3600,1);
        s3=menu.addMenu(MW_VAR,s2, F("Red Points"));
          s3->addVar(MW_AUTO_INT,&POINTSPERPERIODRED,0,1000,5);
        s3=menu.addMenu(MW_VAR,s2, F("Blue Points"));
          s3->addVar(MW_AUTO_INT,&POINTSPERPERIODBLUE,0,1000,5); // min, max, increment
        s3=menu.addMenu(MW_VAR,s2, F("Reset Period"));  
          s3->addVar(MW_LIST,&RESETSCOREPERIOD);
          s3->addItem(MW_LIST, F("No")); // returns index 0
          s3->addItem(MW_LIST, F("Yes")); // returns index 1
        s3=menu.addMenu(MW_VAR,s2, F("Countdown (s)"));
          s3->addVar(MW_AUTO_INT,&COUNTDOWNTIMER,0,30,1);
      s2=menu.addMenu(MW_SUBMENU,s1, F("Device Setup"));
        s3=menu.addMenu(MW_VAR,s2, F("Splash Screen"));  
          s3->addVar(MW_LIST,&SPLASHSCREEN);
          s3->addItem(MW_LIST, F("Off")); // returns index 0
          s3->addItem(MW_LIST, F("On")); // returns index 1

    s1=menu.addMenu(MW_SUBMENU,r, F("Advanced"));
        s2=menu.addMenu(MW_VAR,s1, F("Wireless"));  
          s2->addVar(MW_LIST,&WIRELESS);
          s2->addItem(MW_LIST, F("Off")); // returns index 0
          s2->addItem(MW_LIST, F("On")); // returns index 1 
        s2=menu.addMenu(MW_SUBMENU,s1, F("LED Control"));
          s3=menu.addMenu(MW_VAR,s2, F("LED Flashing"));  
            s3->addVar(MW_LIST,&LEDBLINK);
            s3->addItem(MW_LIST, F("Off")); // returns index 0
            s3->addItem(MW_LIST, F("On")); // returns index 1
          s3=menu.addMenu(MW_VAR,s2, F("On Time (ms)"));  
            s3->addVar(MW_AUTO_INT,&LEDBLINKON,0,1000,100);
          s3=menu.addMenu(MW_VAR,s2, F("Off Time (ms)"));  
            s3->addVar(MW_AUTO_INT,&LEDBLINKOFF,0,1000,100);
         s2=menu.addMenu(MW_VAR,s1, F("Cursor Flash"));  
           s2->addVar(MW_AUTO_INT,&CURSORBLINKRATE,0,1000,100);
 
    s1=menu.addMenu(MW_SUBMENU,r, F("Developer"));
      s2=menu.addMenu(MW_VAR,s1, F("Serial Debug"));  
        s2->addVar(MW_LIST,&DEBUG);
        s2->addItem(MW_LIST, F("Off")); // returns index 0
        s2->addItem(MW_LIST, F("On")); // returns index 1 

    s1=menu.addMenu(MW_VAR,r, F("SAVE & EXIT"));
      s1->addVar(MW_ACTION,save_settings);
      s1->setBehaviour(MW_ACTION_CONFIRM,false); // turn confirmation off

    s1=menu.addMenu(MW_VAR,r, F("EXIT NO SAVE"));
      s1->addVar(MW_ACTION,exit_menu);
      s1->setBehaviour(MW_ACTION_CONFIRM,false); // turn confirmation off

    //menu.navButtons(UP_BOTTON_PIN,DOWN_BOTTON_PIN,LEFT_BOTTON_PIN,RIGHT_BOTTON_PIN,ESCAPE_BOTTON_PIN,CONFIRM_BOTTON_PIN);
    menu.addUsrNav(menuKeys,6);
      
    Serial.println(menu.freeRam());
  }

void loop()
{
  menu.draw(); 
  
  int key = menuKeys();
 
  if (key != MW_BTNULL)
  {
    Serial.println(key);
  } 
  
  //char customKey = keypad1.getKey();
  
  /* if (customKey != NO_KEY){
    Serial.println(customKey);
    if(customKey == '2')
    {
      Serial.println(F("Did the comparison"));
    }
  } */
}
 
 void save_settings()
 {
     Serial.println(F("SAVE SETTINGS TO EEPROM"));
 }
 
 void exit_menu()
 {
     Serial.println(F("EXIT MENU WITHOUT SAVING TO EEPROM"));
 }
 
void myfunc()
{
  Serial.println(F("ACTION FIRED"));
}

int menuKeys()
{
  char keyPressed = keypad1.getKey();
  
  if (keyPressed != NO_KEY)
  {
    if (keyPressed == '2')
    {
      return MW_BTU;
    }
    else if (keyPressed == '8')
    {
      return MW_BTD;
    }
    else if (keyPressed == '4')
    {
      return MW_BTL;
    }
    else if (keyPressed == '6')
    {
      return MW_BTR;
    }
    else if (keyPressed == '*')
    {
      return MW_BTE;
    }
    else if (keyPressed == '#')
    {
      return MW_BTC;
    }
    else
    {
    return MW_BTNULL;
    }
  }
}

what is happening? The steps seems ok.
first aid: do not call inside loop your function. leave loop with only draw call. I suspect that double call (remember that inside draw method a call to your function is done each loop) to your function could affect the keyboard driver

I added the call inside loop() just to prove of the keypad and function were actually working, whih they are.

I'll have a frenzy look at it today. It was 4am when I gave up. LOL

I quickly tried switching back to the normal buttons and that didn't work either. When I switched to the original sketch, without Keypad in, it worked again. The only other differene is me changing the same menu structure from 'tree' to 'menu'.

It may be something else and I'll investigate further today and have another attempt at making it work, before I bring all this code into my main project.

Hopefully I can get it working and you can then use this as your custom keys example if you like. It would be good as it shows how to do it with a standard keypad using another library. That can easily expand to a user creating their own whole button hanging routine for something like analogue buttons.

Amazing what going to getting some sleep and looking at it fresh does.

As I suspected, it was nothing to do with the custom buttons or keypad. It was actually LCD related. DOH!

Bear with me here as I try to explain it.

I have one copy of the sketch with normal buttons, which was saved, and another with the keypad code added; 'menu' and 'menu_keypad'.

I have a 328P and a 1284P on the same cluster of breadboards, with an LCD on each. Each LCD is attached to an I2C backpack, but they have different addresses (0x20 v 0x27).

My 'menu' sketch had been working on the 328P fine, but RAM was low, so I thought I'd need the additional RAM of the 1284, but when trying to run it I got a INPUT_PULLUP related compile error (1284P will be the final platform).

So...I know it all compiles with Uno as the board. I'd had 'menu' running on the 328P (I2C 0x20), but the 'menu_keypad' was first tried on the 1284P (I2C 0x27).

What had happened is that the addr wasn't changed, so the LCD wasn't actually being used, BUT.....as I'd had the original 'menu' running, the display hadn't updated! The LCD's don't reset on MCU reset so it still shows the last thing on the screen, i.e. my root menu, so it seemed like there was no navigation!

I was actually looking at the screen written by the previously loaded 'menu' sketch (using the correct LCD addr), when I switched to running the 'menu_keypad' sketch (using the LCD address on the other MCU). Obviously they are identical as it's the same menu layout.

Now I've found that silly error on my part it all works fine!!! :slight_smile:

Now to add in the EEPROM save and read.

What I'll be doing is declaring those variables at the beginning and then, once MENWIZ is started, doing a readEeprom(). If it's the first time the sketch has ever been run then there will be nothing in EEPROM and I'm assuming the library handles this and leaves the declared variables intact. If there is data in EEPROM then it will replace the values of the declared variables.

When I select SAVE & EXIT I'll simply call writeEeprom() in my ACTION function for that item.

OK, now I seem to be having a problem with the EEPROM saving but first, a tidied up custom key function, in case you want to include it in docs:-

// function to wrap keypad presses and return values to menu as custom keys input
// this uses the logical direction keys and the * and # keys (on a normal 4x4 keypad).
//      2
//   4    6
//      8
//   *    #
int menuKeys()
{
  char keyPressed = keypad1.getKey(); // read the keypad to see if a key has been pressed
  
  switch (keyPressed)
  {
    case NO_KEY:
      return MW_BTNULL; // no key pressed
    case '2':
      return MW_BTU; // key 2 pressed for direction UP
    case '8':
      return MW_BTD; // key 8 pressed for direction DOWN
    case '4':
      return MW_BTL; // key 4 pressed for direction LEFT
    case '6':
      return MW_BTR; // key 6 pressed for direction RIGHT
    case '*':
      return MW_BTE; // key * pressed for ESCAPE
    case '#':
      return MW_BTC; // key # pressed for CONFI|RM
  }

Right, with that out of the way, I am adding the readEeprom() and writeEeprom() methods in my sketch as below:-

void setup(){
  _menu *r,*s1,*s2,*s3; // menu structure, root plus each submenu
  
  keypad1.begin(); // start Keypad number 1

  Serial.begin(115200);
  
  menu.begin(&lcd1,16,2); //declare lcd1 object and screen size to menwiz lib
  
  [b]menu.readEeprom();[/b] // read any stored values in the EEPROM
  
  menu.setBehaviour(MW_MENU_INDEX,false); // turn off the menu index - 1/3 etc

  r=menu.addMenu(MW_ROOT,NULL,F("MAIN MENU"));
 
    s1=menu.addMenu(MW_SUBMENU,r, F("General"));
      s2=menu.addMenu(MW_SUBMENU,s1, F("Game Setup"));
        s3=menu.addMenu(MW_VAR,s2, F("Game Time (m)"));  
          s3->addVar(MW_AUTO_INT,&GAMETIME,0,5999,1);
        s3=menu.addMenu(MW_VAR,s2, F("Pts Period (s)"));
          s3->addVar(MW_AUTO_INT,&POINTPERIOD,0,3600,1);
        s3=menu.addMenu(MW_VAR,s2, F("Red Points"));
          s3->addVar(MW_AUTO_INT,&POINTSPERPERIODRED,0,1000,5);
        s3=menu.addMenu(MW_VAR,s2, F("Blue Points"));
          s3->addVar(MW_AUTO_INT,&POINTSPERPERIODBLUE,0,1000,5); // min, max, increment
        s3=menu.addMenu(MW_VAR,s2, F("Reset Period"));  
          s3->addVar(MW_LIST,&RESETSCOREPERIOD);
          s3->addItem(MW_LIST, F("No")); // returns index 0
          s3->addItem(MW_LIST, F("Yes")); // returns index 1
        s3=menu.addMenu(MW_VAR,s2, F("Countdown (s)"));
          s3->addVar(MW_AUTO_INT,&COUNTDOWNTIMER,0,30,1);
      //s2=menu.addMenu(MW_SUBMENU,s1, F("Device Setup"));
        //s3=menu.addMenu(MW_VAR,s2, F("Splash Screen"));  
          //s3->addVar(MW_LIST,&SPLASHSCREEN);
          //s3->addItem(MW_LIST, F("Off")); // returns index 0
          //s3->addItem(MW_LIST, F("On")); // returns index 1

    s1=menu.addMenu(MW_SUBMENU,r, F("Advanced"));
        s2=menu.addMenu(MW_VAR,s1, F("Wireless"));  
          s2->addVar(MW_LIST,&WIRELESS);
          s2->addItem(MW_LIST, F("Off")); // returns index 0
          s2->addItem(MW_LIST, F("On")); // returns index 1 
        s2=menu.addMenu(MW_SUBMENU,s1, F("LED Control"));
          s3=menu.addMenu(MW_VAR,s2, F("LED Flashing"));  
            s3->addVar(MW_LIST,&LEDBLINK);
            s3->addItem(MW_LIST, F("Off")); // returns index 0
            s3->addItem(MW_LIST, F("On")); // returns index 1
          s3=menu.addMenu(MW_VAR,s2, F("On Time (ms)"));  
            s3->addVar(MW_AUTO_INT,&LEDBLINKON,0,1000,100);
          s3=menu.addMenu(MW_VAR,s2, F("Off Time (ms)"));  
            s3->addVar(MW_AUTO_INT,&LEDBLINKOFF,0,1000,100);
         s2=menu.addMenu(MW_VAR,s1, F("Cursor Flash"));  
           s2->addVar(MW_AUTO_INT,&CURSORBLINKRATE,0,1000,100);
 
    s1=menu.addMenu(MW_SUBMENU,r, F("Developer"));
      s2=menu.addMenu(MW_VAR,s1, F("Serial Debug"));  
        s2->addVar(MW_LIST,&DEBUG);
        s2->addItem(MW_LIST, F("Off")); // returns index 0
        s2->addItem(MW_LIST, F("On")); // returns index 1 

    s1=menu.addMenu(MW_VAR,r, F("SAVE & EXIT"));
      s1->addVar(MW_ACTION,saveSettings);
      //s1->setBehaviour(MW_ACTION_CONFIRM,false); // turn confirmation off

    s1=menu.addMenu(MW_VAR,r, F("EXIT NO SAVE"));
      s1->addVar(MW_ACTION,exitMenu);
      //s1->setBehaviour(MW_ACTION_CONFIRM,false); // turn confirmation off

    //menu.navButtons(UP_BOTTON_PIN,DOWN_BOTTON_PIN,LEFT_BOTTON_PIN,RIGHT_BOTTON_PIN,ESCAPE_BOTTON_PIN,CONFIRM_BOTTON_PIN);
    menu.addUsrNav(menuKeys,6);
      
    Serial.println(menu.freeRam());
  }

The menu now only displays when I want it to, using a displayMenu variable, where 0 = no menu, 1 = display menu.

In the main project this variable will be activated by simultaneously pressing a certain key at the time the device is turned on, a bit like entering the BIOS on a PC.

void loop()
{
  while (displayMenu == 1)
  {
    menu.draw();
  }
 
  lcd1.clear();
  lcd1.setCursor(0,0);
  lcd1.print(F("  Menu  Exited  "));
  lcd1.setCursor(0,1);
  lcd1.print(F("  Game  Starts  "));
  
  while (displayMenu != 1); // do nothing forever, after doing a single print to the LCD; halt changes for test purposes
}

My 'SAVE & EXIT' Option should write the values to Eeprom and then change the value of global displayMenu to 0 so the menu no longer displays and the game starts.

 void saveSettings()
 {
   menu.writeEeprom(); // save the settings to EEPROM
   Serial.println(F("Settings Saved To EEPROM"));
   displayMenu = 0; // set menu to off so it is no longer active and the rest of the sketch continues
 }

When I change a setting and select 'EXIT & SAVE', I then reset the MCU and go back in and the value is back to the originally declared value at the beginning of the sketch. Surely the menu.readEeprom() should update these from stored values?

The most obvious question is; do I have this readEeprom() in the correct place, or does it need to be closer to the beginning of the sketch? I've placed it after the menu class is initialised.

Thanks for the keypad callback!
the readEeprom() for each menwiz declared variable read from eeprom the stored values.
Therefore you need to declare all the variables before, that is readEeprom must be placed after the last addVar call....

Ok, thanks for that.

I've tried that and it looks like the EEPROM is being read. However, when I run this on an MCU that hasn't run it before I get all my declared variables set to -1, presumably as the EEPROM is empty at that point.

I'm sure there are things to do with this so I guess I'll go read up on using EEPROM. I guess it couldn't be that easy. LOL.

As far as the documents go, I think it would benefit new users immensely to have actual examples of how each method is used, and where. The documentation talks in programmer terms and kind of assumes the reader has the same level of knowledge as you do. Most users will be hobbyists and will need actual examples.

Things like this EEPROM stuff. The docs just mention method readEeprom() in class MENWIZ. I almost just tried calling readEeprom() to start, but realised it is a method so needs to be menu.readEeprom() but I think some users would struggle even more.

I'll do some reading on EEPROM use and have another try at fixing my code, to work how I want, tomorrow.

If empty EEPROM returns -1 values then maybe the library could handle that in the background and. He k it against the legal value range for each addVar item. If the retrieved value is outside legally declared range then don't update the live variable.

There will always be abl first time this code is run, and also menu items added or removed from a device, so there will be variables for which there is no corresponding value in EEPROM.

I will suggest you:
1- Load EEPROM erase programm from the Arduino IDE and erase the existing EEPROM in your arduino.
2- Then upload your GAME sketch and first save the EEPROM values.
3- Now just open another sketch from Arduino IDE that load the variables and display on Serial.Print.
4- This will ensure that your Read and Write EEPROM functions doing well.

I actually load the EEPROM variable once just start of the Arduino so that all the variables get the parameters from stored values. If i add another variable in the sketch then i first erase all EEPROM using above method. Then i disable Load EEPROM function in my Setup menu. Then i run the sketch and Save the EEPROM. Then i again enable the save EEPROM function in setup menu.

The use of eeprom methods is up to the programmer. That is not because I'm lazy :slight_smile: but because other approaches could lead to errors very difficult to bug. For instance, If you change the sketch code inserting new variable between the previous variable set, when you use readEeprom some variables get true values and other do'nt. Forcing range values could have no sense at all in a sketch application, where parameter are often related.

There could be an other way: prepend in eeprom to the first variable a user defined check mark (likely the sketch version number) providing a method to verify if the actual eeprom contain the expected value for check mark. If it is equal it means that reading from eeprom make sense, otherwise the sketch will use the default var values: the two added method could be:
void setCM(int);
int getCM();
It is up to menwiz to shift the memory read/write after the check mark location

Yes docs could be improved.. should I have time. Any help is wellcome.

I think I understand.

Maybe I should set up a set of #define like D_GAMETIME etc as the default values.

I'll declare all the variables but not initialise them. I could use a version number to do a check of whether anything has changed. Read EEPROM, check version number in EEPROM against #define, if the same then values are all valid and must have been saved previously. If not equal the. EEPROM is either empty or out of sync with version, so clear EEPROM and load default values. I'll think it though better.

I'll put the version number in my Developer menu and also a 'Clear Saved' and 'Load Defaults' option.

Is there a way to have a read-only menu option? This could be useful for allowing the user to display the version number or author etc.

I'll be using this on a 1284P, which will give the PROGMEM and RAM to implement a comprehensive settings menu on top of my existing project code and all the various options.

Another thing that has occurs to me is an expansion of the increment value option. If the up/down key was the default to increase/decrease a value, then the left/right keys could be used to jump up and down by a larger amount. This would require an additional argument. For example, my gametime variable is from 1-5999 (00:01 to 99:59). If I could specify (1,5999,1,10) to allow up down arrow to increase/decrease by 1 and left/right to increase/decrease by 10, it would be much more flexible for large ranges.

I see in the code provision for a future text item. Again, that would be very useful for me to allow a user to enter themselves as registered owner, or set the device for the name of the game and the location name of the device.

Why a read only menu ? You can create a splash screen for 5-10 seconds at startup with all the usefull infos. It uses less memory
If you prefer to code a menu entry ... create a dummy action variable, with an empty callback and set MW_ACTION_CONFIRM behaviour to false

Differentiation of increment steps for variable limits would need one more pointer in the var structure. many users (e.g. see github) claim about the memory used by menwiz variables... EDIT: the proposed solution also would be usable with 6 buttons only

Yes, Im' trying to imagine a implementation way for imput fields. I need to find some clever way to write compact code. the menwiz footprint is dangerosusly near the usability border.

I've coded a dummy var into the menu to support a 'very' variable containing a current version.

I've also set a #define of D_VERSION as a comparison.

Now, on start I declare all my variables, without values, and a corresponding #define D_ value as the default.

I then set the menu structure up and do a readEeprom. From here I check to see if ver and D_VERSION are equal. If they aren't then I run a loadDefaults() function that assigns all the D_values to the corresponding variables and then does a writeEeprom to set the defaults in EEPROM.

Now, this should mean that on the next reset the 'ver' loaded from EEPROM is the same as D_VERSION and the loadDefaults() doesn't run.

This never seems to work though and defaults are always loaded. It's like the Eeleom is being written too.

A friend said he had a similar issue when using the standard EEPROM library and it was only. Heed when he wait he'd to EEPROMEx.

EEPROMEx has an update() method where it will check the contents of each address and only update if the data has changed.

I need to debug the issue I'm getting anyway, but I'm thinking of trying to modify the library to use EEPROMEx (it's backwards compatible with EEPROM library) and soecifically the writeEeeprom method to actually use the update method In EepromEx library to save wear on the EEPROM.

The other thing I meant to say; from a memory management point of.view, the smallest data in MENWIZ is an int. this is two bytes. Many of my variable values. An be contained in a uint8_t, which is half the size, but MENWIz will use an int for these values.

That's maybe a potential place you could save on some memory by allowing a smaller variable to be used. It could potentially save quite some space for some menus.