m2tklib with u8glib on a Teensy 3.1

So, I've read through the m2tklib wiki and I am having a hard time understanding the elements of m2tklib. I've successfully run several of the examples, but I just can't seem to figure out how to get m2tklib do what I want. Here is what I'm trying to accomplish:

  • Hardware: Teensy 3.1, NHD 2.7 OLED display, various sensors, a push button, a rotary encoder - all of this works
  • In "normal" mode - display the output from several sensors - I have this working without issue
  • In "normal" mode, adjust the brightness of the OLED - this is working
  • When the push button is pressed, display a configuration menu ("menu" mode)
  • In "menu" mode, use the rotary encoder and push button to navigate the menu system

The menu system should look like this:

  • Reset trip meter A, with confirmation dialog
  • Reset trip meter B, with confirmation dialog
  • Set timezone
  • Sub menu with a list of timezones
  • Select display layout
  • Sub menu with a list of display layouts
  • Select units
  • Sub menu to select miles or kilometers
  • Exit menu

I could really use some pointers on getting m2tklib configured to do what I want.

Any help is appreciated!

Eric

Hi Eric

I assume the "normal mode" has been implemented without m2tklib. This means you must switch between your "normal mode" and m2tklib.

This is discussed in tutorial 6: https://code.google.com/p/m2tklib/wiki/t06u8g (second part, switch between graphics page and menu).

This means, that you need to setup m2tklib initially in your code, but provide "&m2_null_element" as initial menu. This will force m2tklib to display nothing.

For m2tklib you usually need a good specification of the individual menues. How should the main menu look like to the user? Something like this?

== Main Menu == Reset trip meter Set timezone Select display layout Select units

Exit menu

Another menu could be this:

== Reset Trip Meter == Reset trip meter A Reset trip meter B

Back to main menu

The documentation should be in this form. Then you need to specify the behaviour, maybe like this:

== Main Menu == Reset trip meter Selecting this menu entry will jump to the Reset Trip Meter dialog Set timezone Select display layout Select units

Exit menu Selecting this entry, will assign &m2_null_element as a dialog

and so on...

How is this implemented in m2tklib? I think, the best would be to use M2_ROOT(). This element will display a menu entry and refer controll to a sub menu once selected. As such, M2_ROOT can be used for all the menu entries in your main menu.

The your main menu may look like this (i skipped some menu entries):

M2_ROOT(main_el_reset_trip_meter, NULL, "Reset trip meter", &trip_el_top);
M2_ROOT(main_el_set_time_zone, NULL, "Set time zone", &tz_el_top);
//...
M2_ROOT(main_el_exit, NULL, "Exit menu", &m2_null_element);

M2_LIST(main_list) = { 
    &main_el_reset_trip_meter, 
    &main_el_set_time_zone,
    // ... 
    &main_el_exit
};

M2_VLIST(main_el_top, NULL, main_list);

As soon as you manually assign "main_el_top" as your main menu to m2tklib with the function "m2.setRoot", it will display your own menu,

Let me know if you have further questions.

Oliver

Thank you, that helps a lot!

What does the el mean? element?

How do I deal with the rotary encoder providing two,functions - adjusting OLED brightness when the menu is not active, and navigating the menu when it is active?

Eric

Hi

el means element, but this is just my personal naming convention. The elements can have any names.
A lot of element names will be required, so a good naming convention would be suitable. You could follow the convention here: https://code.google.com/p/m2tklib/wiki/elref#Naming_Convention

I do not now exactly, whether the rotary decoder software will work with your board, if so, it can be used for the bightness adjustment also. M2tklib translates the rotary encoder to M2_KEY_NEXT and M2_KEY_PREV key events.
You could just check for these two values by calling getKey() (https://code.google.com/p/m2tklib/wiki/fnref#getKey)

Do not call getKey() as long as your menu is visible. However it can be used as long as the menu is not visible.

Oliver

Thank you, that's what I needed!

I’ve started to flesh this out, but I know I’m missing a lot. Any code help is appreciated.

// Display library
#include <U8glib.h>

// Menu library
#include <M2tk.h>
#include <m2ghu8g.h>

const int Knob0A = 3;          // Rotary Encoder 0 Pin A
const int Knob0B = 2;          // Rotary Encoder 0 Pin B
const int SW1pin = 20;         // Push button SW1

// Menu
M2tk m2(&top_menu, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);

M2_ROOT(main_el_reset_tripmeterA, NULL, "Reset trip A", &tripA_el_top);
M2_ROOT(main_el_reset_tripmeterB, NULL, "Reset trip B", &tripB_el_top);
M2_ROOT(main_el_set_timezone, NULL, "Set time zone", &tz_el_top);
M2_ROOT(main_el_set_layout, NULL, "Set layout", &layout_el_top);
M2_ROOT(main_el_set_units, NULL, "Set units", &units_el_top);
M2_ROOT(main_el_exit, NULL, "Exit menu", &m2_null_element);

M2_LIST(main_list) = { 
  &main_el_reset_tripmeterA, 
  &main_el_reset_tripmeterB, 
  &main_el_set_timezone,
  &main_el_set_layout,
  &main_el_set_units,
  &main_el_exit
};

M2_VLIST(main_el_top, NULL, main_list);

void setup() {
  m2_SetU8g(u8g.getU8g(), m2_u8g_box_icon);
  m2.setFont(0, u8g_font_7x13r);
  m2.setFont(1, u8g_profont11);
  m2.setPin(M2_KEY_SELECT, SW1pin);
  m2.setPin(M2_KEY_ROT_ENC_A, Knob0A);
  m2.setPin(M2_KEY_ROT_ENC_B, Knob0B);
}

void loop() {
  // do sensor stuff
  
  // check for rotary enc or button activity
  switch (m2.GetKey()) {
    case M2_KEY_NEXT:
      // call function to increase brightness
      break;
    case M2_KEY_PREV:
      // call function to decrease brightness
      break;
    case M2_KEY_SELECT:
      m2.setRoot(main_el_top);
      break;
  }

  u8g.firstPage();  
  do {
    m2.checkKey();
    m2.draw();
  } 
  while(u8g.nextPage());


}

Are there any compiler warings or errors?

Here are two things which i noticed during review:

void loop() {
  // do sensor stuff
  
  // check for rotary enc or button activity
  switch (m2.GetKey()) {
    case M2_KEY_NEXT:
      // call function to increase brightness
      break;
    case M2_KEY_PREV:
      // call function to decrease brightness
      break;
    case M2_KEY_SELECT:
      m2.setRoot(main_el_top);
      break;
  }

  u8g.firstPage();  
  do {
    m2.checkKey();
    m2.draw();
  } 
  while(u8g.nextPage());
}

This code should include a check, whether the menu is visible or not. Additionally a call to handleKey is required.

void loop() {

  // always call checkKey() 
  m2.checkKey();

  // do sensor stuff

  if ( m2.getRoot() == &m2_null_element ) {

  // draw something if required..

     u8g.firstPage();  
     do {
       // your own drawing procedure
     } 
     while(u8g.nextPage());

  
    // check for rotary enc or button activity
    switch (m2.GetKey()) {
      case M2_KEY_NEXT:
        // call function to increase brightness
        break;
      case M2_KEY_PREV:
        // call function to decrease brightness
        break;
      case M2_KEY_SELECT:
        m2.setRoot(main_el_top);
        break;
    } // switch
  }
  else
  {
   if ( m2.handleKey() != 0 ) {
     u8g.firstPage();  
     do {
       m2.draw();
     } // do
     while(u8g.nextPage());
   } // if
  } // else
}

Another issue might be:

M2tk m2(&top_menu, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);

top_menu does not exist, i guess you may want to use main_el_top (menu should be visible first) or m2_null_element (normal screen graphics should be visible first). In either case you need to do a forward declaration of main_el_top otherwise the compiler will complain about an unkown object.

// forward declarations
M2_EXTERN_VLIST(main_el_top);

// Menu
M2tk m2(&main_el_top, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);

Oliver

I didn't try to compile what I had, as I knew I was missing some pieces. Thanks again for the pointers!

OK, I was able to get back to this and I'm still having a hard time understanding the structure of m2tklib. I've read the element reference, function reference, and tutorial several times, and it just doesn't make sense to me. I have a couple of questions that might help me understand better.

1) In my code above, what defines the structure of the top level menu? M2_LIST(main_list)? M2_VLIST?

2) In this declaration - M2_ROOT(main_el_reset_tripmeterA, NULL, "Reset trip A", &tripA_el_top); - how do I define tripA_el_top to do what I want? In this instance, if "Reset Trip A" is selected, I want a new dialog to come up asking for verification of the reset action (e.g. yes or no). If yes is selected, then execute a function, if no is selected, return to the previous menu.

I would really be helpful if the documentation clearly laid out the structure of m2tklib. The current documentation does this (I think), but the relationship between the elements and functions is not terribly clear to me. For example:

  • the M2tk m2() function should be before setup() and should contain x, y, and z - this is documented but I think it needs clarification
  • create an element defining the top level menu, then describe and demonstrate it.
  • create second (and third) level menus
  • how to create a confirmation dialog for a menu selection

Once again, any help or suggestions are appreciated. Thank you!

Hi

Let me introduce and define some keywords. The two words "dialog" and "menu" will be equivalent, i will try to stick to "menu" in the remaining part of this answer.

Definition: A menu is a hierarchy of elements, that defines the complete content of the display.

Let me make an example: A yes/no menu. There are different ways how a yes/no menu could look like. There might be a checkbox with confirm button or there might be two confirm buttons. I choose a the two confirm buttons, so it might look like this:

+-----+  +----+
! yes !  ! no !
+-----+  +----+

Both fields can be selected and activated. So, in principle this menu contains two elements: Two buttons with two different text inside ("yes" and "no"). However in M2tklib such a menu must be a hierarchy. This means, the two elements (yes and no) must be grouped together. For this purpose m2tklib has an additional (invisible) kind of elements. These are the so called container or grouping elements. This container element defines how the child elements are grouped together and how the layout will look like for the child elements:

+---------------+
!+-----+  +----+!
!! yes !  ! no !!
!+-----+  +----+!
+---------------+

Let me move to some code. The yes/no menu will look like this:

M2_BUTTON( el_yes, NULL, "yes", fn_yes );
M2_BUTTON( el_no, NULL, "no", fn_no );

M2_LIST(list_yes_no_menu) = { &el_yes, &el_no };
M2_HLIST(el_hlist, NULL, list_yes_no_menu);

In general the first argument is the element name. For example in the first line of the code "el_yes" is the name of the "yes" box.

So far we discussed three elements: The yes and no buttons and the parent container element. You sould be able to identify this: "el_yes" is the "yes" button, "el_no" is the "no" button and "el_parent" is the parent container which defines the layout for the childs.

But what is "list_yes_no_menu"? It is a C list, which helps us to construct the parent element. In fact almost all container element require M2_LIST. M2_LIST is not an element. It is neither a visible element nor a container, it just belongs to a container. I general M2_LIST and the container element (hier this is M2_HLIST) are written next to each other. In the code example you also see, that the name "list_yes_no_menu" of M2_LIST appears as last argument in M2_HLIST. It is like this: M2_LIST defines the a sequence of child elements, where M2_HLIST defines the actual layout. Especially M2_HLIST defines a horizontal arrangement of the child elements.

If we just replace M2_HLIST with M2_VLIST (a different parent container type)

M2_BUTTON( el_yes, NULL, "yes", fn_yes );
M2_BUTTON( el_no, NULL, "no", fn_no );

M2_LIST(list_yes_no_menu) = { &el_yes, &el_no };
M2_VLIST(el_parent, NULL, list_yes_no_menu);

then the layout will look like this:

+-------+
!+-----+!
!! yes !!
!+-----+!
!+----+ !
!+ no + !
!+----+ !
+-------+

Now, let me introduce the "root element".

Definition: The "root element" is the root element of a menu (see definition above) hierarchy.

Example: The root element for the yes no menu above is "el_parent"

Why do i need the root element? Well, a root element is the key to know all the other elements. From the root element, m2tk knows all the child elements. If you want to draw the menu on the screen, you just need to provide the root element and m2tklib will draw the entire element hierarchy. The function which will do this is "setRoot". "setRoot" actually means: Use the provided root element from now on for drawing the entire menu on the screen.

Now we come to the final property of m2tklib. A user may not only want one single menu. Usually a lot of menues are required in an application. As such, there will be multiple menus and multiple root elements (one root element per menu).

M2tklib can handle and switch between multiple menues. All this is done through the "root element". The "root element" represents the complete menu.

Let me create another example:

M2_BUTTON( el_menu_1, NULL, "click me", fn_1 );

This code just creats one single clickable button on the screen. It will have the label "click me". This menu only contains a single element. It does not contain any container. As such the menu hierarchy is very simple (the tree of the hierarchy has only one element). So the root element is also "el_menu_1". In order to make this menu visible, i just say "m2.setRoot(&el_menu_1)".

Let me introduce another menu:

M2_BUTTON( el_menu_2, NULL, "click me too", fn_2 );

Now, i want to implement this behavior: If "click me" (menu 1) is selected, menu 2 should appear. How can this be done? This is handled by the last part of M2_BUTTON: fn_1. fn_1 is a user define c function. It has a fixed prototype, but you can define the content:

void fn_1(m2_el_fnarg_p fnarg) {
    m2.setRoot(&el_menu_2);    
}

fn_1 will be called as soon as you select/click/activate the button "click me" in el_menu_1. "fn_1" is a normal c function. You can do anything here. Start a motor, modify a global variable, get an analog value or (like here) assign a different menu by providing a different root element.

Let me finish the example, by saying that from menu 2 the user should be able to jump back to menu 1. Then the complete example will look like this:

void fn_1(m2_el_fnarg_p fnarg) {
    m2.setRoot(&el_menu_2);    
}
M2_BUTTON( el_menu_1, NULL, "click me", fn_1 );

void fn_2(m2_el_fnarg_p fnarg) {
    m2.setRoot(&el_menu_1);    
}
M2_BUTTON( el_menu_2, NULL, "click me too", fn_2 );

However, this code has a problem: In fn_1 i need to know "el_menu_2", but it will be defined later, so this is not (yet) known to the compiler. This problem of a forward declaration will unfortunately appear more often. It is solved with a forward declaration of the menu. The correct code will look like this:

M2_EXTERN_BUTTON(el_menu_2);

void fn_1(m2_el_fnarg_p fnarg) {
    m2.setRoot(&el_menu_2);    
}
M2_BUTTON( el_menu_1, NULL, "click me", fn_1 );

void fn_2(m2_el_fnarg_p fnarg) {
    m2.setRoot(&el_menu_1);    
}
M2_BUTTON( el_menu_2, NULL, "click me too", fn_2 );

Does this make sense to you so far? Here is one more optimization: Very often the user defined function will only change the root element (call setRoot). To simplify this, there is a special element, that includes the user defined function already: M2_ROOT. M2_ROOT is just M2_BUTTON with a user define function, that calls "setRoot". The example can be simplified like this:

M2_EXTERN_BUTTON(el_menu_2);
M2_ROOT( el_menu_1, NULL, "click me", &el_menu_2 );
M2_ROOT( el_menu_2, NULL, "click me too", &el_menu_1 );

Ah, this looks much simpler but is identical to the previous example.

Before i complete this post, here is one more note and one suggestion.

Note: Each application (usually) needs to start with a startup or main menu. The root element of this main menu is given to the constructor as first argument.

M2tk m2(&el_menu_1, m2_es_arduino, m2_eh_2bs, m2_gh_u8g_fb);

Suggestions: Have a look at this tutorial: https://code.google.com/p/m2tklib/wiki/t03 It will repeat some the things, i wrote here.

Let me know if you have further questions. I am sure many things are still unclear, but i hope i have covered the basics.

Oliver

Edit: Fixed yes/no example

That was very helpful! That really helped me to understand the structure of m2tklib. Thank you!

Great, let me know if there are further doubts. BTW: I fixed the copy/paste error in the yes/no menu example.

Oliver

I made great progress thanks to your last help. I have one more problem I can't seem to figure out.

I start the program displaying data from sensors. I want the menu to become active when I push a push button. With debugging statements, I can see the button press is seen by M2tklib, but the menu never shows, it just keeps displaying the data screen. On the reverse, if I start the program displaying the menu, when I exit the menu system, I just get a blank screen when I should get sensor data. I'm sure I'm missing something simple....

// Menu
M2tk m2(&m2_null_element, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);

M2_EXTERN_VLIST(el_main);
M2_EXTERN_HLIST(el_resetA);
M2_EXTERN_HLIST(el_resetB);
M2_EXTERN_VLIST(el_tz);
M2_EXTERN_VLIST(el_layout);
M2_EXTERN_VLIST(el_units);

M2_ROOT(main_el_top, "f0", NULL, &el_main);
M2_ROOT(main_el_reset_tripmeterA, "f0", "Reset trip A", &el_resetA);
M2_ROOT(main_el_reset_tripmeterB, "f0", "Reset trip B", &el_resetB);
M2_ROOT(main_el_set_timezone, "f0", "Set time zone", &el_tz);
M2_ROOT(main_el_set_layout, "f0", "Set layout", &el_layout);
M2_ROOT(main_el_set_units, "f0", "Set units", &el_units);
M2_ROOT(main_el_exit, "f0", "Exit menu", &m2_null_element);

// Cancel button
void fn_cancel(m2_el_fnarg_p fnarg) {
    m2.setRoot(&el_main);    
}
M2_BUTTON(button_cancel, "f0", "cancel", fn_cancel);

// Main menu 
M2_LIST(main_list) = { 
  &main_el_reset_tripmeterA, 
  &main_el_reset_tripmeterB, 
  &main_el_set_timezone,
  &main_el_set_layout,
  &main_el_set_units,
  &main_el_exit
};
M2_VLIST(el_main, "f0", main_list);

// Trip meter mennu
void fn_yesA(m2_el_fnarg_p fnarg) {
  ResetTrip(0);
  m2.setRoot(&main_el_top);    
}
void fn_yesB(m2_el_fnarg_p fnarg) {
  ResetTrip(1); 
  m2.setRoot(&main_el_top);  
}

M2_BUTTON(button_yesA, "f0", "yes", fn_yesA );
M2_BUTTON(button_yesB, "f0", "yes", fn_yesB );
M2_LIST(list_resetA) = { &button_yesA, &button_cancel };
M2_HLIST(el_resetA, "f0", list_resetA);
M2_LIST(list_resetB) = { &button_yesB, &button_cancel };
M2_HLIST(el_resetB, "f0", list_resetB);
M2_LABEL(label_resetA, "f0", "Reset Trip A?");
M2_LABEL(label_resetB, "f0", "Reset Trip B?");

// Time zone menu
M2_LIST(list_tz) = { &button_cancel };
M2_VLIST(el_tz, "f0", list_tz);

// Layout menu
M2_LIST(list_layout) = { &button_cancel };
M2_VLIST(el_layout, "f0", list_layout);

// Units menu
M2_LIST(list_units) = { &button_cancel };
M2_VLIST(el_units, "f0", list_units);

void setup() {
  m2_SetU8g(u8g.getU8g(), m2_u8g_box_icon);
  m2.setFont(1, u8g_font_7x13r);
  m2.setFont(0, u8g_font_profont11);
  m2.setPin(M2_KEY_SELECT, SW1pin);
  m2.setPin(M2_KEY_ROT_ENC_A, Knob0A);
  m2.setPin(M2_KEY_ROT_ENC_B, Knob0B);
}

void loop() {
  m2.checkKey();

  if ( m2.getRoot() == &m2_null_element ) {
    updateDisplay(layoutVer);  // This does the u8g.firstPage() stuff inside the function

    switch (m2.getKey()) {
    case M2_KEY_NEXT:
      SetBrightness(BrightStep);
      break;
    case M2_KEY_PREV:
      SetBrightness(-BrightStep);
      break;
    case M2_KEY_SELECT:
      Serial.println("MENU: button pressed");
      m2.setRoot(&el_main);
      break;
    }
  } 
  else {
    if ( m2.handleKey() != 0 ) {
      u8g.firstPage();  
      do {
        m2.draw();
        m2.checkKey();
      } 
      while(u8g.nextPage());
    } 
  }
}

Hi

You did not post the full code. Actually i have not seen a bug in the code above, but the problem might be in "updateDisplay". I also would like to upload this on my Uno to see what exactly goes wrong.

Oliver

The full code is 83,556 bytes in 17 tabs. Here is the updateDisplay() code:

void updateDisplay(int layoutVer) {
  u8g.firstPage();  
  do {
    utc = now();

    switch (layoutVer) {
    case 1:
      displaySpeed(53,42,9,u8g_font_fub25n);
      displaySpeedUnit(54,54,u8g_font_tpssr);
      displayAltitude(107,52,6,u8g_font_tpssr);
      displayHeading(58,11,4,u8g_font_helvR10r);
      displayLat(0,63,u8g_font_profont11);
      displayLon(69,63,u8g_font_profont11);
      displayTrip(98,26,6,1,valTripA,u8g_font_tpssr);
      displayTrip(98,39,6,2,valTripB,u8g_font_tpssr);
      displayTime((*tz).toLocal(utc),0,26,6,u8g_font_tpssr);
      displayDate((*tz).toLocal(utc),0,39,6,u8g_font_tpssr);
      displayTemp(0,11,8,u8g_font_helvB10r);
      displayVoltage(89,11,29,u8g_font_helvB10r);
      displaySDstatus(0,52,u8g_font_profont11);
      break;
    case 2:
      displaySpeed(53,42,9,u8g_font_fub25n);
      displaySpeedUnit(54,54,u8g_font_tpssr);
      displayAltitude(107,52,6,u8g_font_tpssr);
      displayHeading(58,11,4,u8g_font_helvR10r);
      displayTrip(98,26,6,1,valTripA,u8g_font_tpssr);
      displayTrip(98,39,6,2,valTripB,u8g_font_tpssr);
      displayTime((*tz).toLocal(utc),0,26,6,u8g_font_tpssr);
      displayDate((*tz).toLocal(utc),0,39,6,u8g_font_tpssr);
      displayTemp(0,11,8,u8g_font_helvB10r);
      displayVoltage(89,11,29,u8g_font_helvB10r);
      displaySDstatus(0,52,u8g_font_profont11);
      displayGPSstatus(55,63,u8g_font_profont11);
    }
  } 
  while(u8g.nextPage());
}

ok, i see, but i have not seen the problem in the code. Best would be if i could see the problem on my board. Is it possible to cut down the code to one .ino file, but with the problem still appearing?

Oliver

olikraus: ok, i see, but i have not seen the problem in the code. Best would be if i could see the problem on my board. Is it possible to cut down the code to one .ino file, but with the problem still appearing?

Oliver

I'll see what I can do. If I can't get to it tomorrow, it may be a week before I can do it. Thank you for all your help!

OK, here is a stripped down set of code that still has the problem. There is a bit of Teensy specific code in it, SPI and ADC stuff, but for an UNO you can use the regular SPI.h and strip the ADC stuff out, I think.

Edit: Slimmed down the code some more.

// SPI library - Teensy 3.x specific
#include <spi4teensy3.h>
//#include <SPI.h>

// Display library
#include <U8glib.h>

// ADC library
#include <ADC.h>
#include <ADC_Module.h>
#include <RingBuffer.h>
#include <RingBufferDMA.h>

// Menu library
#include <M2tk.h>
#include <utility/m2ghu8g.h>

#define DEBUG

U8GLIB_NHD27OLED_GR u8g(10, 15);   // U8GLIB_NHD27OLED_GR(cs, a0 [, reset]) - Hardware SPI

// Analog pin definitions
const int TempPin = 16;        // 16=A2 on Teensy 3.1  // Pin for input from TMP36
const int VMpin = 14;          // 14=A0 on Teensy 3.1  // Pin for input from 0-30V after voltage divider - don't exceed 3.3V actual to pin

// Digital pin definitions
const int Knob0A = 3;          // Rotary Encoder 0 Pin A
const int Knob0B = 2;          // Rotary Encoder 0 Pin B
const int SW1pin = 20;            // Push button SW1

// Voltmeter
float VMout;

// ADC
ADC *adc = new ADC();

// Thermometer
float DegF;
bool tempErr = false;

// Menu
//M2tk m2(&m2_null_element, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);
M2_EXTERN_VLIST(el_main);
M2tk m2(&el_main, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);

M2_EXTERN_HLIST(el_resetA);
M2_EXTERN_HLIST(el_resetB);
M2_EXTERN_VLIST(el_tz);
M2_EXTERN_VLIST(el_layout);
M2_EXTERN_VLIST(el_units);

M2_ROOT(main_el_top, "f0", NULL, &el_main);
M2_ROOT(main_el_reset_tripmeterA, "f0", "Reset trip A", &el_resetA);
M2_ROOT(main_el_reset_tripmeterB, "f0", "Reset trip B", &el_resetB);
M2_ROOT(main_el_set_timezone, "f0", "Set time zone", &el_tz);
M2_ROOT(main_el_set_layout, "f0", "Set layout", &el_layout);
M2_ROOT(main_el_set_units, "f0", "Set units", &el_units);
M2_ROOT(main_el_exit, "f0", "Exit menu", &m2_null_element);

// Cancel button
void fn_cancel(m2_el_fnarg_p fnarg) {
  m2.setRoot(&el_main);    
}
M2_BUTTON(button_cancel, "f0", "cancel", fn_cancel);

// Main menu 
M2_LIST(main_list) = { 
  &main_el_reset_tripmeterA, 
  &main_el_reset_tripmeterB, 
  &main_el_set_timezone,
  &main_el_set_layout,
  &main_el_set_units,
  &main_el_exit
};
M2_VLIST(el_main, "f0", main_list);

// Trip meter mennu
void fn_yesA(m2_el_fnarg_p fnarg) {
  //ResetTrip(0);
  m2.setRoot(&el_main);    
}
void fn_yesB(m2_el_fnarg_p fnarg) {
  //ResetTrip(1); 
  m2.setRoot(&el_main);  
}

M2_BUTTON(button_yesA, "f0", "yes", fn_yesA );
M2_BUTTON(button_yesB, "f0", "yes", fn_yesB );
M2_LIST(list_resetA) = { 
  &button_yesA, &button_cancel };
M2_HLIST(el_resetA, "f0", list_resetA);
M2_LIST(list_resetB) = { 
  &button_yesB, &button_cancel };
M2_HLIST(el_resetB, "f0", list_resetB);
M2_LABEL(label_resetA, "f0", "Reset Trip A?");
M2_LABEL(label_resetB, "f0", "Reset Trip B?");

// Time zone menu
M2_LIST(list_tz) = { 
  &button_cancel };
M2_VLIST(el_tz, "f0", list_tz);

// Layout menu
M2_LIST(list_layout) = { 
  &button_cancel };
M2_VLIST(el_layout, "f0", list_layout);

// Units menu
M2_LIST(list_units) = { 
  &button_cancel };
M2_VLIST(el_units, "f0", list_units);

void setup() {

  Serial.begin(115200);
  Serial1.begin(19200); // GPS
#ifdef DEBUG
  delay(2000); // startup delay 
#endif

#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Begin setup"));
#endif

#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Display start"));
#endif
  // Start the display
  u8g.begin();
  //u8g.setRot180(); // Rotate screen 180 deg
  u8g.setColorIndex(15); // 15 = max intensity for GR display, 1 = on for BW
  u8g.setContrast(200);

#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Pin init"));
#endif
  pinMode(VMpin, INPUT);   // Input pin for voltmeter
  pinMode(TempPin, INPUT); // Input pin for thermometer

  //
  // ADC setup
  //
#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Start ADC stuff"));
#endif
  adc->setAveraging(16, ADC_0);
  adc->setAveraging(16, ADC_1);

  //
  // Menu
  //
#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Menu setup"));
#endif
  m2_SetU8g(u8g.getU8g(), m2_u8g_box_icon);
  m2.setFont(1, u8g_font_7x13r);
  m2.setFont(0, u8g_font_profont11);
  m2.setPin(M2_KEY_SELECT, SW1pin);
  m2.setPin(M2_KEY_ROT_ENC_A, Knob0A);
  m2.setPin(M2_KEY_ROT_ENC_B, Knob0B);


#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("End setup"));
#endif

} // End setup()

// Main body where we actually do stuff
void loop() { 

  CalculateVoltage();
  CalculateTemp();
  m2.checkKey();

  if ( m2.getRoot() == &m2_null_element ) {
    u8g.firstPage();  
    do {
      displayTemp(0,11,8,u8g_font_helvB10r);
      displayVoltage(89,11,29,u8g_font_helvB10r);

      switch (m2.getKey()) {
      case M2_KEY_NEXT:
#ifdef DEBUG
        Serial.println("DISPLAY: Brightness up");
#endif
        //SetBrightness(BrightStep);
        break;
      case M2_KEY_PREV:
#ifdef DEBUG
        Serial.println("DISPLAY: Brightness down");
#endif
        //SetBrightness(-BrightStep);
        break;
      case M2_KEY_SELECT:
#ifdef DEBUG
        Serial.println("MENU: button pressed");
#endif
        m2.setRoot(&el_main);
        break;
      }
    }
    while(u8g.nextPage());
  } 
  else {
    if ( m2.handleKey() != 0 ) {
      u8g.firstPage();  
      do {
        m2.draw();
        m2.checkKey();
      } 
      while(u8g.nextPage());
    } 
  }
}


void displayVoltage(int posH, int posV, int inc, const unsigned char* font) {
  u8g.setFont(font); 
  u8g.setPrintPos(posH, posV); 
  u8g.print(VMout,1); // Print the voltage with one decimal place
  u8g.setPrintPos((posH + inc), posV); // Set the position for the following
  u8g.print("V"); // Print V next to the voltage reading
}

void displayTemp(int posH, int posV, int inc, const unsigned char* font) { 
  u8g.setFont(font); // Set the font
  if (tempErr == false) {
    if ( DegF < 100.0f && DegF >= 10.0f ) { // adjust the print position depending on number of digits
      u8g.setPrintPos((posH + inc), posV); 
    }
    else if ( DegF < 10.0f && DegF >= 0.0f ) {
      u8g.setPrintPos((posH + (inc * 2)), posV); 
    }
    else {
      u8g.setPrintPos(posH, posV); 
    }
    u8g.print(DegF,0); // Print the temperature with no decimal fraction
  } 
  else {
    u8g.setFont(u8g_font_tpssr);
    u8g.setPrintPos(posH, posV - 1);
    u8g.print("ERR");
  }
  u8g.setPrintPos((posH + (inc * 3) + 1), posV); 
  u8g.setFont(font);
  u8g.print("F"); // Print F next to the temp reading
}

void CalculateTemp() {
  // Read the voltage from the LM34 and calcuate the temperature based on the voltage
  float voltagein = adc->analogRead(TempPin, ADC_1); // Read the voltage using ADC_1 
  float tempVolt = voltagein / 310; // For 3.3v power 
  float DegC = (100 * tempVolt) - 50;  // TMP36 voltage = temp in C with a 50 deg offset
  tempErr = false;
  DegF = (DegC * 9.0 / 5.0) + 32.0; // Convert to Farenheit
#ifdef DEBUGTEMP
  Serial.print(F("TEMP: "));
  Serial.print(F("Temp: "));
  Serial.println(DegF);
#endif
}

void CalculateVoltage() {
  float VMsum = adc->analogRead(VMpin); // Read the voltage
  // Calculate the voltage
  float VMvoltage = VMsum / 310.0;  // For 3.3V system 
  // Send voltage for display - the number requires tweaking to suit your individual setup 
  // (voltage divider resistor value and variance, etc.)
  VMout = VMvoltage * 11.0312;
#ifdef DEBUGVOLT
  Serial.print(F("VOLT: "));
  Serial.print(F("Volt: "));
  Serial.println(VMout);
#endif
}

A little debugging shows that when in graphics mode, the M2_KEY_SELECT action is seen, however, the m2.setRoot(&el_main) does not take effect and the m2 root remains set to m2_null_element.

When in menu mode, and the menu option to exit back to graphics is selected, m2 root does change to m2_null_element but the low level graphics are not displayed.

Hi

I tracked down the problems.

  1. Problem: After “Exit Menu” nothing is displayed.
    This is a known problem. M2tk alters the foreground color, so you u8glib picture is not visible. You neet to reassign the foreground color:
  if ( m2.getRoot() == &m2_null_element ) {
    u8g.setDefaultForegroundColor();
    u8g.firstPage();
  1. Problem: Going back to menu does not work.
    Additionally to setRoot, you need to call two more functions:
      case M2_KEY_SELECT:
#ifdef DEBUG
        Serial.println("MENU: button pressed");
#endif
        m2.setRoot(&el_main);
        m2.handleKey();             // update internal menu
        m2.setKey(M2_KEY_REFRESH);  // force a refresh

Below you will find my test code.

Oliver

// SPI library - Teensy 3.x specific
//#include <spi4teensy3.h>
//#include <SPI.h>

// Display library
#include <U8glib.h>

// ADC library
//#include <ADC.h>
//#include <ADC_Module.h>
//#include <RingBuffer.h>
//#include <RingBufferDMA.h>

// Menu library
#include <M2tk.h>
#include <utility/m2ghu8g.h>

//#define DEBUG

//U8GLIB_NHD27OLED_GR u8g(10, 15);   // U8GLIB_NHD27OLED_GR(cs, a0 [, reset]) - Hardware SPI
U8GLIB_DOGM128 u8g(13, 11, 10, 9);		// SPI Com: SCK = 13, MOSI = 11, CS = 10, A0 = 9


// Analog pin definitions
const int TempPin = 16;        // 16=A2 on Teensy 3.1  // Pin for input from TMP36
const int VMpin = 14;          // 14=A0 on Teensy 3.1  // Pin for input from 0-30V after voltage divider - don't exceed 3.3V actual to pin

// Digital pin definitions
const int Knob0A = 7;          // Rotary Encoder 0 Pin A
const int Knob0B = 3;          // Rotary Encoder 0 Pin B
const int SW1pin = 2;            // Push button SW1

// Voltmeter
float VMout;

// ADC
//ADC *adc = new ADC();

// Thermometer
float DegF;
bool tempErr = false;

// Menu
//M2tk m2(&m2_null_element, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);
M2_EXTERN_VLIST(el_main);
M2tk m2(&el_main, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_u8g_bf);

M2_EXTERN_HLIST(el_resetA);
M2_EXTERN_HLIST(el_resetB);
M2_EXTERN_VLIST(el_tz);
M2_EXTERN_VLIST(el_layout);
M2_EXTERN_VLIST(el_units);

M2_ROOT(main_el_top, "f0", NULL, &el_main);
M2_ROOT(main_el_reset_tripmeterA, "f0", "Reset trip A", &el_resetA);
M2_ROOT(main_el_reset_tripmeterB, "f0", "Reset trip B", &el_resetB);
M2_ROOT(main_el_set_timezone, "f0", "Set time zone", &el_tz);
M2_ROOT(main_el_set_layout, "f0", "Set layout", &el_layout);
M2_ROOT(main_el_set_units, "f0", "Set units", &el_units);
M2_ROOT(main_el_exit, "f0", "Exit menu", &m2_null_element);

// Cancel button
void fn_cancel(m2_el_fnarg_p fnarg) {
  m2.setRoot(&el_main);    
}
M2_BUTTON(button_cancel, "f0", "cancel", fn_cancel);

// Main menu 
M2_LIST(main_list) = { 
  &main_el_reset_tripmeterA, 
  &main_el_reset_tripmeterB, 
  &main_el_set_timezone,
  &main_el_set_layout,
  &main_el_set_units,
  &main_el_exit
};
M2_VLIST(el_main, "f0", main_list);

// Trip meter mennu
void fn_yesA(m2_el_fnarg_p fnarg) {
  //ResetTrip(0);
  m2.setRoot(&el_main);    
}
void fn_yesB(m2_el_fnarg_p fnarg) {
  //ResetTrip(1); 
  m2.setRoot(&el_main);  
}

M2_BUTTON(button_yesA, "f0", "yes", fn_yesA );
M2_BUTTON(button_yesB, "f0", "yes", fn_yesB );
M2_LIST(list_resetA) = { 
  &button_yesA, &button_cancel };
M2_HLIST(el_resetA, "f0", list_resetA);
M2_LIST(list_resetB) = { 
  &button_yesB, &button_cancel };
M2_HLIST(el_resetB, "f0", list_resetB);
M2_LABEL(label_resetA, "f0", "Reset Trip A?");
M2_LABEL(label_resetB, "f0", "Reset Trip B?");

// Time zone menu
M2_LIST(list_tz) = { 
  &button_cancel };
M2_VLIST(el_tz, "f0", list_tz);

// Layout menu
M2_LIST(list_layout) = { 
  &button_cancel };
M2_VLIST(el_layout, "f0", list_layout);

// Units menu
M2_LIST(list_units) = { 
  &button_cancel };
M2_VLIST(el_units, "f0", list_units);

void setup() {

  Serial.begin(115200);
  //Serial1.begin(19200); // GPS
#ifdef DEBUG
  delay(2000); // startup delay 
#endif

#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Begin setup"));
#endif

#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Display start"));
#endif
  // Start the display
  u8g.begin();
  //u8g.setRot180(); // Rotate screen 180 deg
  //u8g.setColorIndex(15); // 15 = max intensity for GR display, 1 = on for BW
  //u8g.setContrast(200);

#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Pin init"));
#endif
  pinMode(VMpin, INPUT);   // Input pin for voltmeter
  pinMode(TempPin, INPUT); // Input pin for thermometer

  //
  // ADC setup
  //
#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Start ADC stuff"));
#endif
  //adc->setAveraging(16, ADC_0);
  //adc->setAveraging(16, ADC_1);

  //
  // Menu
  //
#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("Menu setup"));
#endif
  m2_SetU8g(u8g.getU8g(), m2_u8g_box_icon);
  m2.setFont(1, u8g_font_7x13r);
  m2.setFont(0, u8g_font_profont11);
  m2.setPin(M2_KEY_SELECT, SW1pin);
  m2.setPin(M2_KEY_NEXT, Knob0A);
  m2.setPin(M2_KEY_PREV, Knob0B);


#ifdef DEBUG
  Serial.print(F("DEBUG: "));
  Serial.println(F("End setup"));
#endif

} // End setup()

// Main body where we actually do stuff
void loop() { 

  CalculateVoltage();
  CalculateTemp();
  m2.checkKey();

  if ( m2.getRoot() == &m2_null_element ) {
    u8g.setDefaultForegroundColor();
    u8g.firstPage();  
    do {
      m2.checkKey();
      displayTemp(0,11,8,u8g_font_helvB10r);
      displayVoltage(89,11,29,u8g_font_helvB10r);

    }
    while(u8g.nextPage());
    
      switch (m2.getKey()) {
      case M2_KEY_NEXT:
#ifdef DEBUG
        Serial.println("DISPLAY: Brightness up");
#endif
        //SetBrightness(BrightStep);
        break;
      case M2_KEY_PREV:
#ifdef DEBUG
        Serial.println("DISPLAY: Brightness down");
#endif
        //SetBrightness(-BrightStep);
        break;
      case M2_KEY_SELECT:
#ifdef DEBUG
        Serial.println("MENU: button pressed");
#endif
        m2.setRoot(&el_main);
        m2.handleKey();             // update internal menu
        m2.setKey(M2_KEY_REFRESH);  // force a refresh
        
        break;
      }

    
  } 
  else {
    if ( m2.handleKey() != 0 ) {
      u8g.firstPage();  
      do {
        m2.draw();
        m2.checkKey();
      } 
      while(u8g.nextPage());
    } 
  }
}


void displayVoltage(int posH, int posV, int inc, const unsigned char* font) {
  u8g.setFont(font); 
  u8g.setPrintPos(posH, posV); 
  u8g.print(VMout,1); // Print the voltage with one decimal place
  u8g.setPrintPos((posH + inc), posV); // Set the position for the following
  u8g.print("V"); // Print V next to the voltage reading
}

void displayTemp(int posH, int posV, int inc, const unsigned char* font) { 
  u8g.setFont(font); // Set the font
  if (tempErr == false) {
    if ( DegF < 100.0f && DegF >= 10.0f ) { // adjust the print position depending on number of digits
      u8g.setPrintPos((posH + inc), posV); 
    }
    else if ( DegF < 10.0f && DegF >= 0.0f ) {
      u8g.setPrintPos((posH + (inc * 2)), posV); 
    }
    else {
      u8g.setPrintPos(posH, posV); 
    }
    u8g.print(DegF,0); // Print the temperature with no decimal fraction
  } 
  else {
    u8g.setFont(u8g_font_tpssr);
    u8g.setPrintPos(posH, posV - 1);
    u8g.print("ERR");
  }
  u8g.setPrintPos((posH + (inc * 3) + 1), posV); 
  u8g.setFont(font);
  u8g.print("F"); // Print F next to the temp reading
}

void CalculateTemp() {
  // Read the voltage from the LM34 and calcuate the temperature based on the voltage
  float voltagein = 1023; // Read the voltage using ADC_1 
  float tempVolt = voltagein / 310; // For 3.3v power 
  float DegC = (100 * tempVolt) - 50;  // TMP36 voltage = temp in C with a 50 deg offset
  tempErr = false;
  DegF = (DegC * 9.0 / 5.0) + 32.0; // Convert to Farenheit
#ifdef DEBUGTEMP
  Serial.print(F("TEMP: "));
  Serial.print(F("Temp: "));
  Serial.println(DegF);
#endif
}

void CalculateVoltage() {
  float VMsum = 888; // Read the voltage
  // Calculate the voltage
  float VMvoltage = VMsum / 310.0;  // For 3.3V system 
  // Send voltage for display - the number requires tweaking to suit your individual setup 
  // (voltage divider resistor value and variance, etc.)
  VMout = VMvoltage * 11.0312;
#ifdef DEBUGVOLT
  Serial.print(F("VOLT: "));
  Serial.print(F("Volt: "));
  Serial.println(VMout);
#endif
}