M2TKLIB - A User-Interface-Toolkit for the Arduino Hardware

Oliver,
I use both character and graphic lcds in different projects, both the expanded libraries are named m2tklib.
How can I install both, or do I have to uninstall one to use the other, then switch when I change displays?
Thanks

@Veco
I have uploaded the prerelease here: Google Code Archive - Long-term storage for Google Code Project Hosting..

@Tumbleweed
You could merge files of two variants of m2tklib (must be the same release number). But in your sketch (.ino file) you must include both, LiquidChrystal and the GLCD header. In fact you must include both, even if you use only one output device.
If you do not include both headers, the Arduino IDE will fail to compile M2tklib: If you have only included LiquidCrystal, Arduino IDE will fail to compile GLCD specific files of M2tklib.
An other option is to install the Arduino IDE twice. Setup one instance for your character displays and the other for graphics displays.

Oliver

Cheers olikraus,

Is there a signed flavour of uint8_t?

Same goal as last time, trying to use a range of -20 to 80 within M2_S8NUM with my rotary encoder.

Finally got a standard rotary encoder, it works very smoothly with your implimentation.
Just begining to put together my menu now i've got my prototyping rig set up since this weekend.

#include <LiquidCrystal.h>
#include "M2tk.h"
#include "utility/m2ghlc.h"

LiquidCrystal lcd(12, 11, 7, 6, 5, 4);

//=================================================
// Forward declaration of the toplevel element
M2_EXTERN_ALIGN(top_menu);

//=================================================
// Simple dialog: Input two values n1 and n2

sint8_t n1 = 0;
uint8_t n2 = 0;

M2_LABEL(el_l1, NULL, "Temperature:");
M2_S8NUM(el_u1, "c2", -20, 80, &n1);
M2_LABEL(el_l2, NULL, "Humidity:");
M2_U8NUM(el_u2, "c2", 0, 100, &n2);

M2_LIST(list) = { &el_l1, &el_u1, &el_l2, &el_u2 };
M2_GRIDLIST(el_gridlist, "c2", list);
M2_ALIGN(top_menu, "-1|1W64H64", &el_gridlist);

// m2 object and constructor
// Note: Use the "m2_eh_4bd" handler, which fits better to the "m2_es_arduino_rotary_encoder" 
M2tk m2(&top_menu, m2_es_arduino_rotary_encoder, m2_eh_4bd, m2_gh_lc);

//=================================================
// Arduino Setup & Loop

void setup() {
  m2_SetLiquidCrystal(&lcd, 20, 4);
  
  // define button for the select message
  m2.setPin(M2_KEY_SELECT, 8); 
  
  // The incremental rotary encoder is conected to these two pins
  m2.setPin(M2_KEY_ROT_ENC_A, 2);
  m2.setPin(M2_KEY_ROT_ENC_B, 3);
}

void loop() {
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey() )
    m2.draw();
  m2.checkKey();
}

Also, what is the purpose of calling "m2.checkKey();" multiple times within the main loop?

I'm also confused about how i pass a value back through the callback procedure. I was looking at the example code and i'm unsure how i would use that. Say for example i wanted to do a Serial.print(); of the current S8NUM value for el_u1.

uint8_t global_value = 0;
 
uint8_t u8_cb(m2_rom_void_p element, uint8_t msg, uint8_t val)
{
  if ( msg == M2_U8_MSG_SET_VALUE )
    global_value = val;
  return global_value;
}

M2_U8NUMFN(el_u8_cb, NULL, 0, 10, u8_cb);

Thank you for putting up with me! I'm a determined Newb i'm afraid.

Hi

Is there a signed flavour of uint8_t?

This should be "int8_t".

Also, what is the purpose of calling "m2.checkKey();" multiple times within the main loop?

A call to "checkKey()" polls the values of the rotary encoder and the other keys. There is no interrupt based check of the rotary encoder, instead M2tklib requires as much calls to "checkKey()" as possible.

I'm also confused about how i pass a value back through the callback procedure. I was looking at the example code and i'm unsure how i would use that. Say for example i wanted to do a Serial.print(); of the current S8NUM value for el_u1.

You need to use global variables to pass values to a callback procedure (as you did correctly in the second code example).

This will do the print for each changed value:

uint8_t global_value = 0;
 
uint8_t u8_cb(m2_rom_void_p element, uint8_t msg, uint8_t val)
{
  if ( msg == M2_U8_MSG_SET_VALUE )
  { 
    global_value = val;
    Serial.print(global_value);
  }
  return global_value;
}

Oliver

Hi,

For a project I use an old loran C navigator box with intergrated keypad. It has 20 keys in a 5x8 matrix, yes very inefficient.

How should I modify the library to be able to use a matrix keypad?
So if I look deeper into the code I should change m2_arduino_get_key in m2esarduino.c to return the messages on result of a keypress on my matrix keypad. Is that the only place where I should change the code?
Would it be possible to make extra messages for the numbers 0...9?

Cheers,

Marijn

Hi

  1. Extend the event source to generate the new events (M2_EP_MSG_KEY_1, M2_EP_MSG_KEY_2, ...)

  2. Introduce a new (corresponding) element msg, e.g. M2_EL_MSG_KEY_1
    Extend one of the event handlers to process the new key event, for example extend

uint8_t m2_eh_4bs(m2_p ep, uint8_t msg, uint8_t arg1, uint8_t arg2)

Then add this code to the event handler (for each event):

  case M2_EP_MSG_KEY_1:
    m2_nav_prepare_fn_arg_parent_element(nav);
    m2_fn_arg_call(M2_EL_MSG_KEY_1);			// assign the char 
    return m2_nav_user_next(nav);				// go to next position
  1. Next step is to make the elements aware about the new element messages.
    For example M2_TEXT would make sense here (m2eltext.c):

Extend the char processing function: M2_EL_FN_DEF(m2_el_char_fn)

  case M2_EL_MSG_KEY_1:
      cp = m2_get_char_ptr(fn_arg->nav);
      *cp = '1';
      return 1;

Of course: not tested...

Oliver

Thanks Oliver,

I will try to extend the lib that way.
The matrix keypad question was a RTFM problem :blush:
I used m2.SetKey to store the result of the matrix into the que.
I'm still amazed about this lib...
Cheers,

Jelbert

Yes, it should be possible to use setKey() if you do not want to rewrite the event source.

Oliver

Hi Jelbert

I have added the mentioned code for the keypad support. See Google Code Archive - Long-term storage for Google Code Project Hosting..
Instead of extending the existing handlers, i have introduced m2_eh_4bks and m2_eh_6bks (and extended m2_eh_4bsts and m2_eh_6bsts) to support the keypad values.

Just for reference, the new code in the event handler was wrong. Correct code is:

  case M2_EP_MSG_KEY_1:
    m2_nav_prepare_fn_arg_current_element(nav);
    m2_fn_arg_call(M2_EL_MSG_KEY_1);			// assign the char 
    return m2_nav_user_next(nav);				// go to next position

I have introduced the following constants (which can be used with setKey or as return value of the event source):
#define M2_KEY_HASH 35
#define M2_KEY_STAR 42
#define M2_KEY_0 48
#define M2_KEY_1 49
#define M2_KEY_2 50
#define M2_KEY_3 51
#define M2_KEY_4 52
#define M2_KEY_5 53
#define M2_KEY_6 54
#define M2_KEY_7 55
#define M2_KEY_8 56
#define M2_KEY_9 57

Additionally you can use any other ASCII code like 'a', 'b', 'c', etc.., so m2.setKey('B') will send the ASCII B to the element. At the moment only M2_TEXT will be able to handle these new codes.

Let me know your variant of m2tklib, then i will upload a prerelease of m2tklib including keypad support.

Oliver

Hi Oliver and others,

Based on the 2L example I went on with my code. And made some modifications in oa the used font to create a space on the top of the display for a status line.
At frst I had troubles with the display suddenly blanking but the arduino kept on running.
That was fixed with a buffer that was too long in the drawStatus routine and moving the fontsetting to setup.
After that everything ran fine for a long while.

But now, when just waiting in the main menu, the display blanks again after 8 hours or so.
Is there a way to reset the display system (software part), without resetting the arduino?

Besides the drawstatus routine I included most of the rest for completeness sake.

here is the drawStatus routine

    void drawStatus(void)
{

  static long bmpTime=0;
  static char StatBuf[26];
  static float temprature;
  static float pressure;
  static int hi, lo;

     hi=int(RATemp.getAverage());
    lo=(RATemp.getAverage()-float(hi))*100;

  String line =  String(rtc.getTimeStr());// + bmp.readPressure() + " " + bmp.readTemperature()+" C");
  line += " ";
  line +=  (int(RADruk.getAverage())); //
  if(statMsgStat==1 && m2.getRoot() == &m2_null_element)
  {
    switch (Step)
    {
    case 1:
      line += " 12u  ";
      break;
    case 2:
      line += " 1d   ";
      break; 
    case 3:
      line += " 1.5d ";
      break; 
    case 4:
      line += " 2d   ";
      break; 
    case 5:
      line += " 2.5d ";
      break; 
    case 6:
      line += " 3d   ";
      break; 
    case 7:
      line += " 3.5d ";
      break;
    case 8:
      line += " 4d   ";
      break;
    default:
      line += "      ";
      break;      
    }
  }
  else
    line += "      ";
  line.concat(hi);
  line += '.';
  line.concat(lo);
  if(lo<10)
    line +='0';
  line += "C";
  line.toCharArray(StatBuf,26);
  u8g.drawStr(0,7, StatBuf);

}

The menu side. The stuff from the example has been cut out because the post exeded the maximum length.

U8GLIB_KS0108_128 u8g(27, 28, 29, 30, 31, 32, 33, 34, 26, 35, 36, 24, 25,37); 		// 8Bit Com: D0..D7: 8,9,10,11,4,5,6,7 en=18, cs1=14, cs2=15,di=17,rw=16

//=================================================
// Forward declaration of the toplevel element
M2_EXTERN_ALIGN(top_el_expandable_menu);


/*Create empty screen for graph barrograaf*/
M2_ROOT(el_barrograaf_goto_top, NULL, "t ",&top_el_expandable_menu);

//here very much code has been deleted to fit forum post
//these were modifications of the demo routines to do my menu things

menus call the toplevel element again
m2_menu_entry m2_2lmenu_data[] = 
{
//  { "Menu",  NULL },
  { "Show Graph", &m2_null_element },  //disable menu system
//  { "Show Graph", &list_element },  //disable menu system
  { "Navtex", &top_el_expandable_menu },
  { ". Allerts", &top_el_expandable_menu },
  { ". All msgs", &top_el_expandable_menu },
  { "Navtex Setup", &top_el_expandable_menu },
  { ". Set Area", &top_el_muse },
  { ". Set MSG", &top_el_muse1 },
  { "WIFI Setup", &top_el_expandable_menu },
  { ". Radio", &el_rb_grid },
  { ". Combo", &el_top_combo },  
  { "Serial setup", &el_setspeed_top_combo},
  { "Serial Monitor", NULL },
  { ". COM1", NULL },
  { ". COM2", NULL },
  { ". COM3", NULL },
  { ". COM4", NULL },
  { "Set Date", &el_top_date_menu },
  { "Set Time", &el_top_time_menu },
  { "Numbers ", &el_top_num_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(el_2lmenu,"l5F1e0W43",&m2_2lmenu_first,&m2_2lmenu_cnt, m2_2lmenu_data,65,102,'\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|0W64H64", &el_hlist);

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

//key matrix

byte key_rows[] = {
  40,38,42,43,44,45,46,47};
byte key_cols[] = {
  10,11,12,41,39};


SPISRAM myRAM(7); // CS pin
char buffer[128];



long Pressure;


void setup() {

   //enable 5V dc/dc converter  
  pinMode(DC5V, OUTPUT);
  digitalWrite(DC5V,LOW);
  pinMode(CSRAM, OUTPUT);
  pinMode(Backlight, OUTPUT);
  analogWrite(Backlight,128);

  //setup keypad
  for(int i=0;i<8;i++)
  {
    //    Serial.println(key_rows[i]);
    pinMode(key_rows[i],OUTPUT);
    digitalWrite(i,HIGH);
  }
  for(int i=0;i<5;i++)
  {
    //    Serial.println(key_cols[i]);
    pinMode(key_cols[i],INPUT);
    digitalWrite(key_cols[i],HIGH);
  }
  
    if ( u8g.getMode() == U8G_MODE_R3G3B2 ) 
    u8g.setColorIndex(255);     // white
  else if ( u8g.getMode() == U8G_MODE_GRAY2BIT )
    u8g.setColorIndex(3);         // max intensity
  else if ( u8g.getMode() == U8G_MODE_BW )
    u8g.setColorIndex(1);         // pixel on


  // Connect u8glib with m2tklib
  m2_SetU8g(u8g.getU8g(), m2_u8g_box_icon);

  // Assign u8g font to index 0
  m2.setFont(0, u8g_font_5x8r);

  // Assign icon font to index 3
  m2.setFont(3, u8g_font_m2icon_7);
    
  u8g.setFont(u8g_font_5x7r);

  Wire.begin();
  Serial.begin(115200);


  if (!bmp.begin()) 
  {
    Serial.println("Could not find a valid BMP085 sensor, check wiring!");
    while (1) {
    }
  }
 //Setup SPI for SRAM
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setDataMode(SPI_MODE0);
 
 //some test on the RTC for rome unresolved error
 Serial.println(rtc.getTimeStr());  //deze tijd is onjuist. Meestal 00:00:00
 Serial.println(rtc.getDateStr());
 delay(1000);
 Serial.println(rtc.getTimeStr()); //deze tijd is juist
 Serial.println(rtc.getDateStr());
 


 //init Flagspace
  getFlagSpace();

  if(flagSpace.regs.dataPointer<START_DATA||flagSpace.regs.dataPointer>END_DATA)
    flagSpace.regs.dataPointer=START_DATA;
//fat starts on 0x0000 so no check needed for msgPointer



//  Timeadjust(DateTime(__DATE__, __TIME__));

}

// Draw procedure, Arduino Setup & Loop


void draw(void) {
  drawStatus();
  drawGraphics();
  m2.draw();
}


void loop(){
  long r,timer=0;
 // float temp;
  while(1)
  {
    scheduler();

    KeyMatrix();

    m2.checkKey();

    if ( m2.handleKey() != 0 || millis()>timer ) 
    {
      timer=millis()+1000;
      u8g.firstPage();  
      do 
      {
        KeyMatrix();
        m2.checkKey();
          draw();
   
      } 
      while( u8g.nextPage() );
    }
  }
 }

And for completeness here is the head of my code.

#include <Wire.h>
#include <SPI.h>
#include "U8glib.h"
#include <Adafruit_BMP085.h>
#include <SPISRAM.h>
#include <stdlib.h>		// for itoa
#include "M2tk.h"
#include "utility/m2ghu8g.h"
#include <DS1302.h>
#include <DateTime.h>
#include "RunningAverage.h"

#define END_FAT         0x00BBF
#define START_DATA      0x00BC0
#define END_DATA        0x02BBF
#define MSG_BUF_START   0x02BC0
#define MSG_BUF_END     0x03CAF
#define MSG_START       0x03CB0
#define MSG_LAST        0x1EFFF
#define MSG_STOP        0x1EFFF
#define FLAGSPACE       0xFFF0

#define DC5V 22
#define CSRAM 7
#define Backlight 8

#define M2_KEY_0 20
#define M2_KEY_1 21
#define M2_KEY_2 22
#define M2_KEY_3 23
#define M2_KEY_4 24
#define M2_KEY_5 25
#define M2_KEY_6 26
#define M2_KEY_7 27
#define M2_KEY_8 28
#define M2_KEY_9 29

#define BARROGRAAF 1
#define THERMOGRAAF 2
#define PT_GRAPH 3
long currMsg=MSG_START;
long nu;
int sampleTime=5;
byte drawFunction=0;
byte Step=8; //Geeft de stap grootte aan voor het tekenen van het grafiek max END_DATA-START_DATA/128=65
char tMaxBuf[8];
char tMinBuf[8];
char dMaxBuf[8];
char dMinBuf[8];
char statMsgStat=1;

  float maxTempDraw;
  float minTempDraw;
  float maxDrukDraw;
  float minDrukDraw;

  long minMaxTimeout=0;

#define FLAGSPACE_UNION_SIZE 16

union bla{
  struct blie{
  unsigned int dataPointer; //2bytes
  unsigned int msgFATPointer;  //2bytes
  byte maxTemp;  //
  byte minTemp;
  byte test3;
  byte test4;
  int maxDruk; 
  int minDruk;
  byte test5;
  byte test6;
 
    } 
  regs;
  byte flagMap[FLAGSPACE_UNION_SIZE-1];
} 
flagSpace;  //regU

#define REG_UNION_SIZE 8

union regUnion {
  struct regStruct{
    float temp;
    float druk;
    
  } 
  regs;
  byte registerMap[REG_UNION_SIZE-1];
} 
dataUnion;  //regU

#define EEPROM_UNION_SIZE 8

union{
  struct{
   unsigned int com1Baud; 
   unsigned int com2Baud; 
   unsigned int com3Baud; 
   unsigned int com4Baud; 
  }
  regs;
  byte PROM[EEPROM_UNION_SIZE-1];
}
EE;

Is there a way to reset the display system (software part), without resetting the arduino?

There is the procedure "begin" (Google Code Archive - Long-term storage for Google Code Project Hosting.) which does a reset of the display for newer versions of U8glib.

Oliver

Today, I have released version 1.11 of M2tklib.

M2tklib is a menu/GUI library for graphics and character displays.
Projekt page: Google Code Archive - Long-term storage for Google Code Project Hosting.

The new version is available for download:
@Google: Google Code Archive - Long-term storage for Google Code Project Hosting.
@Bintray: Service End for Bintray, JCenter, GoCenter, and ChartCenter | JFrog

From the ChangeLog:

  • Keypad Support for M2_TEXT and M2_U32NUM (Issue 108)
  • Fixed XBM code (Issues 110, 111)
  • Touch screen support for M2_STRLIST and M2_VSB (Issue 112)
  • New color graphics handler for U8glib (Issue 113)
  • Quick and Home keys (Issues 114 and 115)
  • Changed default behavioir for M2_ALGIN (Issue 121)
  • New element: M2_COMBOFN (Issue 122)
  • New tutorial on event sources (Issues 117 and 123)

Oliver

Hi Oliver,
I like your work quite a lot, but I feel kinda lost now.
Is it possible to declare conditional elements and what is the best approach you'd recommend?
I'd like to create some menu entries, which are dependent on other settings entered in other menu points (global variables).
Menu A (something)
Menu B (something to change a global variable)
Menu C (only visible, if variable in Menu B == 1)
Menu D (only visible, if variable in Menu C == 1)
Menu E (something else)
I guess I should start around STRLIST, but I was wondering if there's a better solution.
Your insight is much appreciated.

Hi

STRLIST is one option, but might be limited. Another option is to create several dialog boxes, which only differe in the visibility of some elements. Then use M2_BUTTON with conditoinal setRoot command in the callback action to jump to the one dialog, which has the intended visible elements. Note, that you can reuse elements (or complete element trees) in more than one dialog.
Then, finally, there is M2_HIDE(). I still did not document M2_HIDE, but i think there should be an example (HideS8Num).

https://code.google.com/p/m2tklib/source/browse/arduino/u8g/HideS8Num/HideS8Num.pde

Oliver

Oliver, thank you so much for your prompt reply, it was most helpful!
M2_HIDE() seems to be perfect, it is the way to go. Wish I had found it sooner. :slight_smile:
Are there any other notable undocumented functions?

I think M2_HIDE is the only undocumented element.

Oliver

M2_HIDE does what it says and only that, it hides an element, but it cannot remove the space it leaves behind and it does not work with U32NUMFN, so one needs to hide a V/H/GRIDLIST in that case.
STRLIST turned out to be the least hassle-free solution to hide some top level menu elements, if you need VSB.
Different strings are shown according to the state of some variables and lower menu entries are assigned with setRoot in the callback procedure.
I think creating different dialog boxes would have been way too over-complicated with Menu(X)2L.
Is there any way to adjust the vertical spacing in-between strings of STRLIST?

Mit freundlichen Grüßen

I reviewed my own code.

M2_HIDE(element_name, fmt, &ctl_var, &child_name);

I think if ctl_var is 2 or higher, then the size will be reduced to zero.
So actually ctl_var can have three values:
0: child is visible
1: child is not visible, but space will be occupied
2: child is not visible and also no space is reserved for the child

The vertical spacing within STRLIST depends on the font only.

Oliver

Hmm, it's good to know, it may come handy later on. I'll report back with the results.

By the way, it would be useful to revise the description of VSB element:
"The vertical scroll bar is an element which is usually used together with STRLIST or INFO elements. VSB will display a vertical scroll bar."
Actually VSB works only with STRLIST or INFO elements. And of course with Menu(X)2L, but that's automatically implemented.
This may help others.

Regarding VSB element you are probably right.

With the M2_HIDE: If you place M2_HIDE inside an XYLIST, the size of M2_HIDE should not matter. Infact child elements of XYLIST may overlap.

Oliver