Go Down

Topic: M2tklib: Menu and User Interface Library (Read 7291 times) previous topic - next topic



Since some days the new version of M2tklib is available for download: http://code.google.com/p/m2tklib/downloads/list

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:




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.




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:
Code: [Select]

#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
//= 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() {

  if ( m2.handleKey() ) {
void dt_ok_fn(m2_el_fnarg_p fnarg)  {
void ti_ok_fn(m2_el_fnarg_p fnarg)  {
void li_ok_fn(m2_el_fnarg_p fnarg)  {
void th_ok_fn(m2_el_fnarg_p fnarg)  {

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.



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:
Code: [Select]

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 ) {
  return s;

All in all, amazing work.



The release 1.09 of the menu-library for graphics and character displays is available for download: http://code.google.com/p/m2tklib/downloads/list

   * 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: http://code.google.com/p/m2tklib/downloads/list

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.



Apr 05, 2013, 03:51 pm Last Edit: Apr 05, 2013, 04:18 pm by shadowkat10 Reason: 1
Hi. I'm new here and I have this (http://www.adafruit.com/products/358) 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.

I should mention, your icons look great :D


Hi shadowkat10

Unfortunately the answer is no. Neither U8glib nor GLCD support this kind of display.
UTFT might be a better starting point: http://henningkarlsen.com/electronics/library.php?id=52



Thanks for your reply. I also found this (http://www.adafruit.com/products/250) screen. Would it be compatible?

Thanks in advance.


Yes, a display with ST7565 controller is supported by U8glib (http://code.google.com/p/u8glib/) and M2tklib is available for U8glib.




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: http://code.google.com/p/m2tklib/wiki/t06glcd. It shows how to draw directly to the screen.



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?


Go Up