M2tklib: Menu and User Interface Library

Hello

Since some days the new version of M2tklib is available for download: Google Code Archive - Long-term storage for Google Code Project Hosting.

M2tklib Features

  • Two to six button support.
  • "Widget concept": Buttons, text&number entry fields, toggle/radio butons, menus, container widgets.
  • Static menu definition: Menues and dialog boxes are avilable immediatly after startup
  • Support for graphics and character LCDs and OLEDs
  • Documentation: Reference manual and many tutorials
  • Available for GLCDv3, U8glib and LiquidCrystal. Support for Doglcd and Dogm128 lib on request.

What's New?

  • File selection box with support for pff, SD and SdFat
  • 2-level menu
  • Input and/or output with the Arduino serial monitor

With the new serial monitor support, M2tklib can be tested without any additional hardware:
Simulation of a 4x20 character LCD:

Same menu with character LCD:

The latest version of U8glib includes several new icons, which are useful for the file selection box:

Oliver

Hi

Unfortunately there has been a bug in the GLCD release of M2tklib. I have uploaded a patched version of M2tklib for GLCD v1.08.1.
The bug only appears for GLCD and affects M2_TEXT and M2_U32NUM elements.

Thanks to kurti for reporting this bug.

Oliver

Hi,

I am using your great library to construct a menu system for reptile climate system I am designing.
After some initial troubles I believe I am on the right track now..
My display is a 192x64 bought of Ebay...
This is the code untill now:

#include <glcd.h>		// inform Arduino IDE that we will use GLCD library
#include "M2tk.h"
#include "utility/m2ghglcd.h"

uint8_t uiKeySelectPin = 3;
uint8_t uiKeyDownPin = 2;
uint8_t uiKeyUpPin = 1;
uint8_t uiKeyExitPin = 0;

uint8_t dt_day = 1;
uint8_t dt_month = 1;
uint8_t dt_year = 12;

uint8_t ti_hour = 1;
uint8_t ti_mins = 1;
uint8_t ti_secs = 12;

uint8_t li_sr_hour = 9;
uint8_t li_sr_mins = 0;
uint8_t li_rt_mins = 30;
uint8_t li_ss_hour = 20;
uint8_t li_ss_mins = 0;


uint8_t li_tl_hour = 2;
uint8_t li_tl_mins = 0;
uint8_t li_ft_mins = 30;

uint8_t th_d_temp = 22;
uint8_t th_n_temp = 18;
uint8_t th_d_humi = 70;
uint8_t th_n_humi = 75;
uint8_t th_b_temp = 5; // basking temp increase
//=================================================

// Forward declaration of the toplevel element
M2_EXTERN_ALIGN(top_el_expandable_menu);
//= Set date menu =
M2_U8NUM(el_dt_day, "c2", 1,31,&dt_day);
M2_LABEL(el_dt_sep1, "b1", "/");                
M2_U8NUM(el_dt_month, "c2", 1,12,&dt_month);
M2_LABEL(el_dt_sep2, "b1", "/");
M2_LABEL(el_dt_sep3, "b1","20");               
M2_U8NUM(el_dt_year, "c2", 12,99,&dt_year);

M2_LIST(list_date) = { &el_dt_day, &el_dt_sep1, &el_dt_month, &el_dt_sep2, &el_dt_sep3, &el_dt_year };
M2_HLIST(el_date, NULL, list_date);

M2_ROOT(el_dt_cancel, NULL, "CANCEL", &top_el_expandable_menu);
M2_BUTTON(el_dt_ok, NULL, "OK", dt_ok_fn);
M2_LIST(list_dt_buttons) = {&el_dt_cancel, &el_dt_ok };
M2_HLIST(el_dt_buttons, NULL, list_dt_buttons);

M2_LIST(list_dt) = {&el_date, &el_dt_buttons };
M2_VLIST(el_top_dt, NULL, list_dt);

//= Set time menu =

M2_U8NUM(el_ti_hour, "c2", 0,23,&ti_hour);
M2_LABEL(el_ti_sep1, "b1", ":");
M2_U8NUM(el_ti_mins, "c2", 0,59,&ti_mins);
M2_LABEL(el_ti_sep2, "b1", ":"); 
M2_U8NUM(el_ti_secs, "c2", 0,59,&ti_secs);

M2_LIST(list_time) = { &el_ti_hour, &el_ti_sep1, &el_ti_mins, &el_ti_sep2, &el_ti_secs };
M2_HLIST(el_time, NULL, list_time);

M2_ROOT(el_ti_cancel, NULL, "CANCEL", &top_el_expandable_menu);
M2_BUTTON(el_ti_ok, NULL, "OK", ti_ok_fn);
M2_LIST(list_ti_buttons) = {&el_ti_cancel, &el_ti_ok };
M2_HLIST(el_ti_buttons, NULL, list_ti_buttons);

M2_LIST(list_ti) = {&el_time, &el_ti_buttons };
M2_VLIST(el_top_ti, NULL, list_ti);

//= Set lighting menu 
  
M2_LABEL(el_li_tit1,"b1", "Set sunrise  : ");
M2_U8NUM(el_li_sr_hour, "c2", 0,23,&li_sr_hour);
M2_LABEL(el_li_sep1, "b1", ":");
M2_U8NUM(el_li_sr_mins, "c2", 0,59,&li_sr_mins);
M2_LIST(list_li_sr) = { &el_li_tit1, &el_li_sr_hour, &el_li_sep1, &el_li_sr_mins };
M2_HLIST(el_li1, NULL, list_li_sr);

M2_LABEL(el_li_tit2,"b1", "Set risetime : ");
M2_U8NUM(el_li_rt_mins, "c2", 1,59,&li_rt_mins);
M2_LIST(list_li_rt) = { &el_li_tit2, &el_li_rt_mins};
M2_HLIST(el_li2, NULL, list_li_rt);

M2_LABEL(el_li_tit3,"b1", "Set sunset   : ");
M2_U8NUM(el_li_ss_hour, "c2", 0,23,&li_ss_hour);
M2_LABEL(el_li_ss_sep, "b1", ":");
M2_U8NUM(el_li_ss_mins, "c2", 0,59,&li_ss_mins);
M2_LIST(list_li_ss) = { &el_li_tit3, &el_li_ss_hour, &el_li_ss_sep, &el_li_ss_mins };
M2_HLIST(el_li3, NULL, list_li_ss);

M2_LABEL(el_li_tit4,"b1", "Set twilight : ");
M2_U8NUM(el_li_tl_hour, "c2", 0,23,&li_tl_hour);
M2_LABEL(el_li_tl_sep, "b1", ":");
M2_U8NUM(el_li_tl_mins, "c2", 0,59,&li_tl_mins);
M2_LIST(list_li_tl) = { &el_li_tit4, &el_li_tl_hour, &el_li_tl_sep, &el_li_tl_mins };
M2_HLIST(el_li4, NULL, list_li_tl);

M2_LABEL(el_li_tit5,"b1", "Set fadetime : ");
M2_U8NUM(el_li_ft_mins, "c2", 1,59,&li_ft_mins);
M2_LIST(list_li_ft) = { &el_li_tit5, &el_li_ft_mins};
M2_HLIST(el_li5, NULL, list_li_ft);

M2_LABEL(el_li_spacer,"b1", "       ");
M2_ROOT(el_li_cancel, NULL, "CANCEL", &top_el_expandable_menu);
M2_LABEL(el_li_spacer2,"b1", "   ");
M2_BUTTON(el_li_ok, NULL, "OK", li_ok_fn);
M2_LIST(list_li_buttons) = {&el_li_spacer, &el_li_cancel, &el_li_spacer2, &el_li_ok };
M2_HLIST(el_li_buttons, NULL, list_li_buttons);  
  
M2_LIST(list_li) = {&el_li1, &el_li2, &el_li3, &el_li4, &el_li5, &el_li_buttons };
M2_VLIST(el_top_li, NULL, list_li); 
  
// == Set temp & humidity
M2_LABEL(el_th_tit1,"b1", "Set day temperature   : ");
M2_U8NUM(el_th_d_temp, "c2", 18,55,&th_d_temp);
M2_LABEL(el_th_unit1,"b1", "C");
M2_LIST(list_th_dt) = { &el_th_tit1, &el_th_d_temp, &el_th_unit1};
M2_HLIST(el_th1, NULL, list_th_dt);

M2_LABEL(el_th_tit2,"b1", "Set night temperature : ");
M2_U8NUM(el_th_n_temp, "c2", 15,55,&th_n_temp);
M2_LABEL(el_th_unit2,"b1", "C");
M2_LIST(list_th_nt) = { &el_th_tit2, &el_th_n_temp, &el_th_unit2};
M2_HLIST(el_th2, NULL, list_th_nt);

M2_LABEL(el_th_tit3,"b1", "Set day humidity      : ");
M2_U8NUM(el_th_d_humi, "c2", 55,99,&th_d_humi);
M2_LABEL(el_th_unit3,"b1", "%");
M2_LIST(list_th_dh) = { &el_th_tit3, &el_th_d_humi, &el_th_unit3};
M2_HLIST(el_th3, NULL, list_th_dh);

M2_LABEL(el_th_tit4,"b1", "Set night humidity    : ");
M2_U8NUM(el_th_n_humi, "c2", 55,99,&th_n_humi);
M2_LABEL(el_th_unit4,"b1", "%");
M2_LIST(list_th_nh) = { &el_th_tit4, &el_th_n_humi, &el_th_unit4};
M2_HLIST(el_th4, NULL, list_th_nh);

M2_LABEL(el_th_spacer,"b1", "       ");
M2_ROOT(el_th_cancel, NULL, "CANCEL", &top_el_expandable_menu);
M2_LABEL(el_th_spacer2,"b1", "   ");
M2_BUTTON(el_th_ok, NULL, "OK", th_ok_fn);
M2_LIST(list_th_buttons) = {&el_th_spacer, &el_th_cancel, &el_th_spacer2, &el_th_ok };
M2_HLIST(el_th_buttons, NULL, list_th_buttons);  
  
M2_LIST(list_th) = {&el_th1, &el_th2, &el_th3, &el_th4, &el_th_buttons };
M2_VLIST(el_top_th, NULL, list_th); 


// Left entry: Menu name. Submenus must have a '.' at the beginning
// Right entry: Reference to the target dialog box (In this example all menus call the toplevel element again
m2_menu_entry m2_2lmenu_data[] = 
{
  { "Set time & date", &top_el_expandable_menu },
  { ". Set time", &el_top_ti },
  { ". Set date", &el_top_dt },
  { "Set temp & humidity", &el_top_th },
  { "Set lighting", &el_top_li },
  { "Review settings", &top_el_expandable_menu},
  { NULL, NULL },
};

// The first visible line and the total number of visible lines.
// Both values are written by M2_2LMENU and read by M2_VSB
uint8_t m2_2lmenu_first;
uint8_t m2_2lmenu_cnt;

// M2_2LMENU definition
// Option l4 = four visible lines
// Option e15 = first column has a width of 15 pixel
// Option W43 = second column has a width of 43/64 of the display width

M2_2LMENU(el_2lmenu,"l5e10W54",&m2_2lmenu_first,&m2_2lmenu_cnt, m2_2lmenu_data,'+','-','\0');
M2_SPACE(el_space, "W1h1");
M2_VSB(el_vsb, "l5W2r1", &m2_2lmenu_first, &m2_2lmenu_cnt);
M2_LIST(list_2lmenu) = { &el_2lmenu, &el_space, &el_vsb };
M2_HLIST(el_hlist, NULL, list_2lmenu);
M2_ALIGN(top_el_expandable_menu, "-1|1W192H64", &el_hlist);

// m2 object and constructor
M2tk m2(&top_el_expandable_menu, m2_es_arduino, m2_eh_4bs, m2_gh_glcd_ffs);


//==========================  
  void setup() {
  m2.setPin(M2_KEY_SELECT, uiKeySelectPin);
  m2.setPin(M2_KEY_NEXT, uiKeyDownPin);
  m2.setPin(M2_KEY_PREV, uiKeyUpPin);
  m2.setPin(M2_KEY_EXIT, uiKeyExitPin);
 
}

void loop() {

  m2.checkKey();
  if ( m2.handleKey() ) {
      m2.draw();
  }
}
void dt_ok_fn(m2_el_fnarg_p fnarg)  {
  m2_SetRoot(&top_el_expandable_menu);
}
void ti_ok_fn(m2_el_fnarg_p fnarg)  {
  m2_SetRoot(&top_el_expandable_menu);
}
void li_ok_fn(m2_el_fnarg_p fnarg)  {
  m2_SetRoot(&top_el_expandable_menu);
}
void th_ok_fn(m2_el_fnarg_p fnarg)  {
  m2_SetRoot(&top_el_expandable_menu);
}

I still have some issues though:

In the 'set lightning' menu I run out of space on my LCD... How (if any) can I add a scrollbar to this?
The last option in the main menu is to review all the settings. Here I want to list all the values I have entered in the previous menus.
So do I make a whole new label array or can I use the ones created before? and again... a scrollbar

Please let me know what you think of the code so far and any remarks/corrections you might have.
Thanks.

Hi

Excellent code with a well structured menu. I have only one little remark:
The CANCEL and OK buttons do not have a meaning at the moment. Both behave in the same way, because all variables are immediatly modified by the menu. If you want a suitable meaning for CANCEL and OK, you need all variables twice: One set of variables is used to control your climate system, the other set of varibles will be used by the menu. "OK" will copy the "menu set" to the "system set" of variables. Tutorial 8 convers some of these ideas.

'set lightning' menu: Maybe the simplest solution is to split the menu into two menus "sunrise" and "sunset". These two menues could be called from the 2L menu. Another idea is to use smaller fonts. Third idea: If it is only too large by some pixel, then using a different overall style might help (something else than m2_gh_glcd_ffs, because the shadow occupies one additional pixel per line)

review all the settings:
You could use the STRLIST () element. With sprintf you can create a different information for each line (index argument). Use the select message to jump back to the main menu:

const char *el_strlist_getstr(uint8_t idx, uint8_t msg) {
  static char s[32]; // large enough to hold the longest string
  if  ( idx == 0 )
    sprintf(s, "Date: %d/%d/%d", dt_day, dt_month, dt_year); // 15 chars 
  else if ( idx == 1 )
    ...
  else if ( idx == 2 )
    ...
  else if ( idx == 3 )
    ...
  else if ( idx == 4 )
    ...
  if (msg == M2_STRLIST_MSG_GET_STR) {
    // just return the string
  } else if ( msg == M2_STRLIST_MSG_SELECT ) {
    m2_SetRoot(&top_el_expandable_menu);
  }
  return s;
}

All in all, amazing work.

Oliver

The release 1.09 of the menu-library for graphics and character displays is available for download: Google Code Archive - Long-term storage for Google Code Project Hosting.

Changes:

  • Experimental support for incremental rotary encoder (enhancement issue 71)
  • New event: XBM_KEY_HOME (ienhancement ssue 75)
  • Change-Root callback procedure (enhancement issue 76)
  • Initial focus changeable (enhancement issue 77)
  • U8glib: XBM-Icons as label or button: M2_XBMLABEL, M2_XBMROOT, M2_XBMBUTTON (enhancement issue 80)
  • More examples: Bookmarks.pde, RotEnc.pde, XBM.pde (u8glib)

I have created release v1.10 of M2tklib: Google Code Archive - Long-term storage for Google Code Project Hosting.

New to M2tklib:

  • Works with Arduino Due (IDE 1.5.2)
  • Touch screen support
  • New variant for NewLiquidCrystal lib
  • New menu elements (M2_X2LMENU, M2_HIDE, M2_S8NUM, M2_BOX)

M2tklib is a menu library for graphics and character devices.

Oliver

Hi. I'm new here and I have this (1.8 Color TFT LCD display with MicroSD Card Breakout [ST7735R] : ID 358 : $19.95 : Adafruit Industries, Unique & fun DIY electronics and kits) screen. Is it compatible with a version of this graphics library? It looks to be exactly what I need, but I don't know what to look for to check compatibility.

EDIT:
I should mention, your icons look great :smiley:

Hi shadowkat10

Unfortunately the answer is no. Neither U8glib nor GLCD support this kind of display.
UTFT might be a better starting point: Electronics - Henning Karlsen

Oliver

Thanks for your reply. I also found this (Graphic ST7565 Positive LCD (128x64) with RGB backlight + extras [ST7565] : ID 250 : $17.95 : Adafruit Industries, Unique & fun DIY electronics and kits) screen. Would it be compatible?

Thanks in advance.

Yes, a display with ST7565 controller is supported by U8glib (Google Code Archive - Long-term storage for Google Code Project Hosting.) and M2tklib is available for U8glib.

Oliver

Thank you for this information. :smiley:

Hi! Im using this library to create a interface for my project.

I have a question. I'm using m2tk with liquidqrystal library to show some info on an 4x20 LCD. On the screen i display time so it updates once a second. My problem is that the screen flickers, you can notice it going blank and then update the screen, everytime i call the draw() function.

Is there any solution to this? I'm guessing that the draw function clears the screen then writes it out. For the information screen i would like it to not clear the screen but just write over the whole screen with the new info to avoid flicker.

Best regards
Nicklas Haraldsson

Your analysis is completly right. First the screen is cleard and then the menu is drawn completly. Currently there is no solution for this, but you could follow the ideas of this tutorial: Google Code Archive - Long-term storage for Google Code Project Hosting.. It shows how to draw directly to the screen.

Oliver

Works like a charm! Thanks for the help and a great library!!

Oliver, I have a completed Menu in Arduino LCD code for a stepper motor controller of a Woodshop Jig. I recently decided that the menu screen needs to be bigger than two rows! I purchased a 4 row LCD. What I received was a ST7920; which I have successfully connected and am rewriting the LCD code using the u8g.lib for the serial connected controller on the module.(LCD12864)....
The selection is shown and the wiring I'm using for UNO r3. are all in the code. My problem with the current rendition is I have several debugging activities to complete or I can use the M2tk.lib and rewrite the code once more, after learning the new library, or I can continue debugging and fix the existing. I'd like your opinion on what I should do?

Currently; I can't seem to find the right combination for getting the loopback to the main menu working in the first sub menu and I know I am the worlds newest code writer in c . But I learn fast and I need to get enough corrections made for that one part of the code so I can replicate and be done. Any suggestions?

David

bjjcMenu.ino (20.8 KB)

Ok, first, let me try to clarify some things.

  1. The ST7920 is not a character LCD. It is a graphical LCD, usually 128x64 pixel size. The number of lines will depend on the selected font.
  2. U8glib does not maintain a local buffer of the GRAM in the RAM of the controller. For that reason it has something what is called a "picture loop". You need to understand this concept first (but i see, that you have used this correctly in your code)
  3. I do not know, what you mean by debugging. You can always use the Arduino Serial Monitor in parallel. Additionally you can print any information on your ST7920. I personally usually introduce a debugging page, which shows important information for me.
    Please clarify debugging: Debugging of what?
  4. In your example you have not used M2tklib. Instead you extended some of the U8glib examples. This is fully ok, the question is only: Do you want to migrate to M2tklib or stay with the existing u8glib menus? The point is this: I can fully support you on U8glib graphics output. But i will not be able to tell you how to build our own menu library on top of U8glib. Instead, if you want to try M2tklib (which is a full features menu library for U8glib) then i can support you on how to create nice powerful menues.

So, as a conclusion:
Unless you have some specific questions to u8glib graphics procedures, i probably can not help you with your existing code.
If you consider to use M2tklib, here are some arguments for using m2tklib:

  • key handling included, can be taken over from your code directly
  • much simpler code, much less to code compared to your current implementation

Suggestion:
Let us design a simple menu. You provide the menu, i provide the code.
Could be like this:

PWM: [value]
Time: [mm]:[ss]
[Start] [Home]

Work with M2tklib starts by designing such menues. Then these menues are implemented and connected with each other. M2tk buttons could be connected to some external hardware and trigger some events.

Oliver

Thanks You for the reply so soon!

You know! I really like the m2tk Menu design! I think by the time I finish the u8g.lib code for the Glcd I'd be over the Arduinos limited amount of RAM anyway.

I do know some about the picture buffer for GLCD hardware but not much!

The use of the GLCD was not my choice as this is what I have! I'm in the process of getting an LCD with 4 rows. I want to make the GLCD work for the challenge!!! I'll read up some more on the definition of the M2tk and here's a block of the MENU tree. There's not much to the menu but I will probably add as I learn the code.......... I'd appreciate any help you may lend ... and the time for me to do this project.

As for debugging: I can't get the sub menus to refresh properly and I tried to filter the change from menu to sub menu but I still get variables that are common to the two levels that are not clearing and I can't figure out which ones they are! I have checked the code but not finding any loose ends right now.

For Instance I start the Menu then select Steps submenu go down to return and select it and the main menu shows up but I still am in the sub menu cause when I hit the down button on the main menu the sub menu POPs up. But the buttons are not responsive telling me that the screen buffer was loaded and I just dumped it into the Display right??? OR am I not thinking which I do a lot!!!

Thanks again for the reply and I am in awe of the effort you must have gone through to build such a tool as the M2tk..lib great job!!!

David

Presentation3.pdf (265 KB)

As for debugging: I can't get the sub menus to refresh properly and I tried to filter the change from menu to sub menu but I still get variables that are common to the two levels that are not clearing and I can't figure out which ones they are! I have checked the code but not finding any loose ends right now.

Inside the submenu if's of the loop() procedure you should not do

      menu_redraw_required = 0;

Instead, do this in the submenu procedures.

Oliver

Taking the nested calls out of the code, I noticed that I was getting erratic button signals...... I'm going to put the recall logic in the main loop to get rid of the time lag! I think, I'll resend when I get it together .... thanks for the tip... It helped some and I found some typos!!! I'm not the world's best at this!!! But I'll get there.......

Oliver, I thank you for the use of the ug8.lib I have completed the code for the menu I needed, and I am testing the stepper motor input. There are still a couple of minor button glitches.
One, is there a reason the pins are held high for a low button input vs the opposite?
Two, I get a blank at the end of the draw string loop on every menu???? Here's the code....

B2JCMENU.ino (20.7 KB)

Do we have any support for openGLCD library for GLCD ks0108?